Lints and suggestions for the Nix programming language
1use std::{
2 fs,
3 io::{self, Error, ErrorKind},
4 path::{Path, PathBuf},
5};
6
7use crate::dirs;
8
9use ignore::{
10 Error as IgnoreError, Match,
11 gitignore::{Gitignore, GitignoreBuilder},
12};
13
14#[derive(Debug)]
15pub struct Walker {
16 dirs: Vec<PathBuf>,
17 files: Vec<PathBuf>,
18 ignore: Gitignore,
19}
20
21impl Walker {
22 pub fn new<P: AsRef<Path>>(target: P, ignore: Gitignore) -> io::Result<Self> {
23 let target = target.as_ref().to_path_buf();
24 if !target.exists() {
25 Err(Error::new(
26 ErrorKind::NotFound,
27 format!("file not found: {}", target.display()),
28 ))
29 } else if target.is_dir() {
30 Ok(Self {
31 dirs: vec![target],
32 files: vec![],
33 ignore,
34 })
35 } else {
36 Ok(Self {
37 dirs: vec![],
38 files: vec![target],
39 ignore,
40 })
41 }
42 }
43}
44
45impl Iterator for Walker {
46 type Item = PathBuf;
47 fn next(&mut self) -> Option<Self::Item> {
48 self.files.pop().or_else(|| {
49 while let Some(dir) = self.dirs.pop() {
50 if dir.is_dir()
51 && let Match::None | Match::Whitelist(_) = self.ignore.matched(&dir, true)
52 {
53 let mut found = false;
54 for entry in fs::read_dir(&dir).ok()? {
55 let entry = entry.ok()?;
56 let path = entry.path();
57 if path.is_dir() {
58 self.dirs.push(path);
59 } else if path.is_file()
60 && let Match::None | Match::Whitelist(_) =
61 self.ignore.matched(&path, false)
62 {
63 found = true;
64 self.files.push(path);
65 }
66 }
67 if found {
68 break;
69 }
70 }
71 }
72 self.files.pop()
73 })
74 }
75}
76
77pub fn build_ignore_set<P: AsRef<Path>>(
78 ignore: &[String],
79 target: P,
80 unrestricted: bool,
81) -> Result<Gitignore, IgnoreError> {
82 let gitignore_path = target.as_ref().join(".gitignore");
83
84 // Looks like GitignoreBuilder::new does not source globs
85 // within gitignore_path by default, we have to enforce that
86 // using GitignoreBuilder::add. Probably a bug in the ignore
87 // crate?
88 let mut gitignore = GitignoreBuilder::new(&gitignore_path);
89
90 // if we are to "restrict" aka "respect" .gitignore, then
91 // add globs from gitignore path as well
92 if !unrestricted {
93 gitignore.add(&gitignore_path);
94
95 // ignore .git by default, nobody cares about .git, i'm sure
96 gitignore.add_line(None, ".git")?;
97 }
98
99 for i in ignore {
100 gitignore.add_line(None, i.as_str())?;
101 }
102
103 gitignore.build()
104}
105
106pub fn walk_nix_files<P: AsRef<Path>>(
107 ignore: Gitignore,
108 target: P,
109) -> Result<impl Iterator<Item = PathBuf>, io::Error> {
110 let walker = dirs::Walker::new(target, ignore)?;
111 Ok(walker.filter(|path: &PathBuf| matches!(path.extension(), Some(e) if e == "nix")))
112}