diff options
author | Akshay <[email protected]> | 2021-11-04 13:16:03 +0000 |
---|---|---|
committer | Akshay <[email protected]> | 2021-11-04 13:16:03 +0000 |
commit | c0487a3b8bb3d9df1e290579bbbd425f7707b5bd (patch) | |
tree | 3be2fa52ec7e48d094f45e3c8f85b1819bac6e66 | |
parent | 5b87c6feb3e4a2fcc30ad94125be3dcd4e554754 (diff) |
use ignore crate to enforce simpler ignore rulesfix-ignores
- also respects .gitignore by default
- adds new flag `-u` to unrestrict statix
-rw-r--r-- | Cargo.lock | 65 | ||||
-rw-r--r-- | bin/Cargo.toml | 2 | ||||
-rw-r--r-- | bin/src/config.rs | 113 | ||||
-rw-r--r-- | bin/src/dirs.rs | 99 | ||||
-rw-r--r-- | bin/src/err.rs | 6 | ||||
-rw-r--r-- | bin/src/main.rs | 1 |
6 files changed, 185 insertions, 101 deletions
@@ -105,6 +105,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
105 | checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58" | 105 | checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58" |
106 | 106 | ||
107 | [[package]] | 107 | [[package]] |
108 | name = "crossbeam-utils" | ||
109 | version = "0.8.5" | ||
110 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
111 | checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" | ||
112 | dependencies = [ | ||
113 | "cfg-if", | ||
114 | "lazy_static", | ||
115 | ] | ||
116 | |||
117 | [[package]] | ||
108 | name = "fnv" | 118 | name = "fnv" |
109 | version = "1.0.7" | 119 | version = "1.0.7" |
110 | source = "registry+https://github.com/rust-lang/crates.io-index" | 120 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -160,6 +170,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
160 | checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" | 170 | checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" |
161 | 171 | ||
162 | [[package]] | 172 | [[package]] |
173 | name = "ignore" | ||
174 | version = "0.4.18" | ||
175 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
176 | checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" | ||
177 | dependencies = [ | ||
178 | "crossbeam-utils", | ||
179 | "globset", | ||
180 | "lazy_static", | ||
181 | "log", | ||
182 | "memchr", | ||
183 | "regex", | ||
184 | "same-file", | ||
185 | "thread_local", | ||
186 | "walkdir", | ||
187 | "winapi-util", | ||
188 | ] | ||
189 | |||
190 | [[package]] | ||
163 | name = "indexmap" | 191 | name = "indexmap" |
164 | version = "1.7.0" | 192 | version = "1.7.0" |
165 | source = "registry+https://github.com/rust-lang/crates.io-index" | 193 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -243,6 +271,12 @@ dependencies = [ | |||
243 | ] | 271 | ] |
244 | 272 | ||
245 | [[package]] | 273 | [[package]] |
274 | name = "once_cell" | ||
275 | version = "1.8.0" | ||
276 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
277 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" | ||
278 | |||
279 | [[package]] | ||
246 | name = "os_str_bytes" | 280 | name = "os_str_bytes" |
247 | version = "3.1.0" | 281 | version = "3.1.0" |
248 | source = "registry+https://github.com/rust-lang/crates.io-index" | 282 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -344,6 +378,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
344 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" | 378 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" |
345 | 379 | ||
346 | [[package]] | 380 | [[package]] |
381 | name = "same-file" | ||
382 | version = "1.0.6" | ||
383 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
384 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" | ||
385 | dependencies = [ | ||
386 | "winapi-util", | ||
387 | ] | ||
388 | |||
389 | [[package]] | ||
347 | name = "serde" | 390 | name = "serde" |
348 | version = "1.0.130" | 391 | version = "1.0.130" |
349 | source = "registry+https://github.com/rust-lang/crates.io-index" | 392 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -392,7 +435,7 @@ version = "0.3.5" | |||
392 | dependencies = [ | 435 | dependencies = [ |
393 | "ariadne", | 436 | "ariadne", |
394 | "clap", | 437 | "clap", |
395 | "globset", | 438 | "ignore", |
396 | "lib", | 439 | "lib", |
397 | "rnix", | 440 | "rnix", |
398 | "serde", | 441 | "serde", |
@@ -464,6 +507,15 @@ dependencies = [ | |||
464 | ] | 507 | ] |
465 | 508 | ||
466 | [[package]] | 509 | [[package]] |
510 | name = "thread_local" | ||
511 | version = "1.1.3" | ||
512 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
513 | checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" | ||
514 | dependencies = [ | ||
515 | "once_cell", | ||
516 | ] | ||
517 | |||
518 | [[package]] | ||
467 | name = "unicode-segmentation" | 519 | name = "unicode-segmentation" |
468 | version = "1.8.0" | 520 | version = "1.8.0" |
469 | source = "registry+https://github.com/rust-lang/crates.io-index" | 521 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -501,6 +553,17 @@ dependencies = [ | |||
501 | ] | 553 | ] |
502 | 554 | ||
503 | [[package]] | 555 | [[package]] |
556 | name = "walkdir" | ||
557 | version = "2.3.2" | ||
558 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
559 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" | ||
560 | dependencies = [ | ||
561 | "same-file", | ||
562 | "winapi", | ||
563 | "winapi-util", | ||
564 | ] | ||
565 | |||
566 | [[package]] | ||
504 | name = "winapi" | 567 | name = "winapi" |
505 | version = "0.3.9" | 568 | version = "0.3.9" |
506 | source = "registry+https://github.com/rust-lang/crates.io-index" | 569 | source = "registry+https://github.com/rust-lang/crates.io-index" |
diff --git a/bin/Cargo.toml b/bin/Cargo.toml index e78cabd..aa789eb 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml | |||
@@ -12,7 +12,7 @@ description = "Lints and suggestions for the Nix programming language" | |||
12 | ariadne = "0.1.3" | 12 | ariadne = "0.1.3" |
13 | rnix = "0.9.0" | 13 | rnix = "0.9.0" |
14 | clap = "3.0.0-beta.4" | 14 | clap = "3.0.0-beta.4" |
15 | globset = "0.4.8" | 15 | ignore = "0.4.18" |
16 | thiserror = "1.0.30" | 16 | thiserror = "1.0.30" |
17 | similar = "2.1.0" | 17 | similar = "2.1.0" |
18 | vfs = { path = "../vfs" } | 18 | vfs = { path = "../vfs" } |
diff --git a/bin/src/config.rs b/bin/src/config.rs index 46bf60f..e0f5cbd 100644 --- a/bin/src/config.rs +++ b/bin/src/config.rs | |||
@@ -1,16 +1,10 @@ | |||
1 | use std::{ | 1 | use std::{default::Default, fmt, fs, path::PathBuf, str::FromStr}; |
2 | default::Default, | 2 | |
3 | fmt, fs, io, | 3 | use crate::{dirs, err::ConfigErr}; |
4 | path::{Path, PathBuf}, | ||
5 | str::FromStr, | ||
6 | }; | ||
7 | 4 | ||
8 | use clap::Clap; | 5 | use clap::Clap; |
9 | use globset::{Error as GlobError, GlobBuilder, GlobSet, GlobSetBuilder}; | ||
10 | use vfs::ReadOnlyVfs; | 6 | use vfs::ReadOnlyVfs; |
11 | 7 | ||
12 | use crate::err::ConfigErr; | ||
13 | |||
14 | #[derive(Clap, Debug)] | 8 | #[derive(Clap, Debug)] |
15 | #[clap(version, author, about)] | 9 | #[clap(version, author, about)] |
16 | pub struct Opts { | 10 | pub struct Opts { |
@@ -40,6 +34,10 @@ pub struct Check { | |||
40 | #[clap(short, long)] | 34 | #[clap(short, long)] |
41 | ignore: Vec<String>, | 35 | ignore: Vec<String>, |
42 | 36 | ||
37 | /// Don't respect .gitignore files | ||
38 | #[clap(short, long)] | ||
39 | unrestricted: bool, | ||
40 | |||
43 | /// Output format. | 41 | /// Output format. |
44 | /// Supported values: stderr, errfmt, json (on feature flag only) | 42 | /// Supported values: stderr, errfmt, json (on feature flag only) |
45 | #[clap(short = 'o', long, default_value_t, parse(try_from_str))] | 43 | #[clap(short = 'o', long, default_value_t, parse(try_from_str))] |
@@ -48,8 +46,9 @@ pub struct Check { | |||
48 | 46 | ||
49 | impl Check { | 47 | impl Check { |
50 | pub fn vfs(&self) -> Result<ReadOnlyVfs, ConfigErr> { | 48 | pub fn vfs(&self) -> Result<ReadOnlyVfs, ConfigErr> { |
51 | let files = walk_with_ignores(&self.ignore, &self.target)?; | 49 | let ignore = dirs::build_ignore_set(&self.ignore, &self.target, self.unrestricted)?; |
52 | vfs(files) | 50 | let files = dirs::walk_nix_files(ignore, &self.target)?; |
51 | vfs(files.collect::<Vec<_>>()) | ||
53 | } | 52 | } |
54 | } | 53 | } |
55 | 54 | ||
@@ -63,6 +62,10 @@ pub struct Fix { | |||
63 | #[clap(short, long)] | 62 | #[clap(short, long)] |
64 | ignore: Vec<String>, | 63 | ignore: Vec<String>, |
65 | 64 | ||
65 | /// Don't respect .gitignore files | ||
66 | #[clap(short, long)] | ||
67 | unrestricted: bool, | ||
68 | |||
66 | /// Do not fix files in place, display a diff instead | 69 | /// Do not fix files in place, display a diff instead |
67 | #[clap(short, long = "dry-run")] | 70 | #[clap(short, long = "dry-run")] |
68 | pub diff_only: bool, | 71 | pub diff_only: bool, |
@@ -70,8 +73,9 @@ pub struct Fix { | |||
70 | 73 | ||
71 | impl Fix { | 74 | impl Fix { |
72 | pub fn vfs(&self) -> Result<ReadOnlyVfs, ConfigErr> { | 75 | pub fn vfs(&self) -> Result<ReadOnlyVfs, ConfigErr> { |
73 | let files = walk_with_ignores(&self.ignore, &self.target)?; | 76 | let ignore = dirs::build_ignore_set(&self.ignore, &self.target, self.unrestricted)?; |
74 | vfs(files) | 77 | let files = dirs::walk_nix_files(ignore, &self.target)?; |
78 | vfs(files.collect::<Vec<_>>()) | ||
75 | } | 79 | } |
76 | } | 80 | } |
77 | 81 | ||
@@ -97,62 +101,6 @@ pub struct Explain { | |||
97 | pub target: u32, | 101 | pub target: u32, |
98 | } | 102 | } |
99 | 103 | ||
100 | mod dirs { | ||
101 | use std::{ | ||
102 | fs, | ||
103 | io::{self, Error, ErrorKind}, | ||
104 | path::{Path, PathBuf}, | ||
105 | }; | ||
106 | |||
107 | #[derive(Default, Debug)] | ||
108 | pub struct Walker { | ||
109 | dirs: Vec<PathBuf>, | ||
110 | files: Vec<PathBuf>, | ||
111 | } | ||
112 | |||
113 | impl Walker { | ||
114 | pub fn new<P: AsRef<Path>>(target: P) -> io::Result<Self> { | ||
115 | let target = target.as_ref().to_path_buf(); | ||
116 | if !target.exists() { | ||
117 | Err(Error::new( | ||
118 | ErrorKind::NotFound, | ||
119 | format!("file not found: {}", target.display()), | ||
120 | )) | ||
121 | } else if target.is_dir() { | ||
122 | Ok(Self { | ||
123 | dirs: vec![target], | ||
124 | ..Default::default() | ||
125 | }) | ||
126 | } else { | ||
127 | Ok(Self { | ||
128 | files: vec![target], | ||
129 | ..Default::default() | ||
130 | }) | ||
131 | } | ||
132 | } | ||
133 | } | ||
134 | |||
135 | impl Iterator for Walker { | ||
136 | type Item = PathBuf; | ||
137 | fn next(&mut self) -> Option<Self::Item> { | ||
138 | if let Some(dir) = self.dirs.pop() { | ||
139 | if dir.is_dir() { | ||
140 | for entry in fs::read_dir(dir).ok()? { | ||
141 | let entry = entry.ok()?; | ||
142 | let path = entry.path(); | ||
143 | if path.is_dir() { | ||
144 | self.dirs.push(path); | ||
145 | } else if path.is_file() { | ||
146 | self.files.push(path); | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | } | ||
151 | self.files.pop() | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | |||
156 | fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> { | 104 | fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> { |
157 | let parts = src.split(','); | 105 | let parts = src.split(','); |
158 | match parts.collect::<Vec<_>>().as_slice() { | 106 | match parts.collect::<Vec<_>>().as_slice() { |
@@ -184,33 +132,6 @@ fn parse_warning_code(src: &str) -> Result<u32, ConfigErr> { | |||
184 | } | 132 | } |
185 | } | 133 | } |
186 | 134 | ||
187 | fn build_ignore_set(ignores: &[String]) -> Result<GlobSet, GlobError> { | ||
188 | let mut set = GlobSetBuilder::new(); | ||
189 | for pattern in ignores { | ||
190 | let glob = GlobBuilder::new(pattern).build()?; | ||
191 | set.add(glob); | ||
192 | } | ||
193 | set.build() | ||
194 | } | ||
195 | |||
196 | fn walk_nix_files<P: AsRef<Path>>(target: P) -> Result<impl Iterator<Item = PathBuf>, io::Error> { | ||
197 | let walker = dirs::Walker::new(target)?; | ||
198 | Ok(walker.filter(|path: &PathBuf| matches!(path.extension(), Some(e) if e == "nix"))) | ||
199 | } | ||
200 | |||
201 | fn walk_with_ignores<P: AsRef<Path>>( | ||
202 | ignores: &[String], | ||
203 | target: P, | ||
204 | ) -> Result<Vec<PathBuf>, ConfigErr> { | ||
205 | let ignores = build_ignore_set(ignores).map_err(|err| { | ||
206 | ConfigErr::InvalidGlob(err.glob().map(|i| i.to_owned()), err.kind().clone()) | ||
207 | })?; | ||
208 | |||
209 | Ok(walk_nix_files(&target)? | ||
210 | .filter(|path| !ignores.is_match(path)) | ||
211 | .collect()) | ||
212 | } | ||
213 | |||
214 | fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> { | 135 | fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> { |
215 | let mut vfs = ReadOnlyVfs::default(); | 136 | let mut vfs = ReadOnlyVfs::default(); |
216 | for file in files.iter() { | 137 | for file in files.iter() { |
diff --git a/bin/src/dirs.rs b/bin/src/dirs.rs new file mode 100644 index 0000000..d3fe612 --- /dev/null +++ b/bin/src/dirs.rs | |||
@@ -0,0 +1,99 @@ | |||
1 | use std::{ | ||
2 | fs, | ||
3 | io::{self, Error, ErrorKind}, | ||
4 | path::{Path, PathBuf}, | ||
5 | }; | ||
6 | |||
7 | use crate::dirs; | ||
8 | |||
9 | use ignore::{ | ||
10 | gitignore::{Gitignore, GitignoreBuilder}, | ||
11 | Error as IgnoreError, Match, | ||
12 | }; | ||
13 | |||
14 | #[derive(Debug)] | ||
15 | pub struct Walker { | ||
16 | dirs: Vec<PathBuf>, | ||
17 | files: Vec<PathBuf>, | ||
18 | ignore: Gitignore, | ||
19 | } | ||
20 | |||
21 | impl 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 | |||
45 | impl Iterator for Walker { | ||
46 | type Item = PathBuf; | ||
47 | fn next(&mut self) -> Option<Self::Item> { | ||
48 | if let Some(dir) = self.dirs.pop() { | ||
49 | if dir.is_dir() { | ||
50 | if let Match::None | Match::Whitelist(_) = self.ignore.matched(&dir, true) { | ||
51 | for entry in fs::read_dir(&dir).ok()? { | ||
52 | let entry = entry.ok()?; | ||
53 | let path = entry.path(); | ||
54 | if path.is_dir() { | ||
55 | self.dirs.push(path); | ||
56 | } else if path.is_file() { | ||
57 | self.files.push(path); | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | self.files.pop() | ||
64 | } | ||
65 | } | ||
66 | |||
67 | pub fn build_ignore_set<P: AsRef<Path>>( | ||
68 | ignore: &[String], | ||
69 | target: P, | ||
70 | unrestricted: bool, | ||
71 | ) -> Result<Gitignore, IgnoreError> { | ||
72 | let gitignore_path = target.as_ref().join(".gitignore"); | ||
73 | |||
74 | // Looks like GitignoreBuilder::new does not source globs | ||
75 | // within gitignore_path by default, we have to enforce that | ||
76 | // using GitignoreBuilder::add. Probably a bug in the ignore | ||
77 | // crate? | ||
78 | let mut gitignore = GitignoreBuilder::new(&gitignore_path); | ||
79 | |||
80 | // if we are to "restrict" aka "respect" .gitignore, then | ||
81 | // add globs from gitignore path as well | ||
82 | if !unrestricted { | ||
83 | gitignore.add(&gitignore_path); | ||
84 | } | ||
85 | |||
86 | for i in ignore { | ||
87 | gitignore.add_line(None, i.as_str())?; | ||
88 | } | ||
89 | |||
90 | gitignore.build() | ||
91 | } | ||
92 | |||
93 | pub fn walk_nix_files<P: AsRef<Path>>( | ||
94 | ignore: Gitignore, | ||
95 | target: P, | ||
96 | ) -> Result<impl Iterator<Item = PathBuf>, io::Error> { | ||
97 | let walker = dirs::Walker::new(target, ignore)?; | ||
98 | Ok(walker.filter(|path: &PathBuf| matches!(path.extension(), Some(e) if e == "nix"))) | ||
99 | } | ||
diff --git a/bin/src/err.rs b/bin/src/err.rs index 1e52c2b..b776d9f 100644 --- a/bin/src/err.rs +++ b/bin/src/err.rs | |||
@@ -1,13 +1,13 @@ | |||
1 | use std::io; | 1 | use std::io; |
2 | 2 | ||
3 | use globset::ErrorKind; | 3 | // use globset::ErrorKind; |
4 | // use rnix::parser::ParseError; | 4 | // use rnix::parser::ParseError; |
5 | use thiserror::Error; | 5 | use thiserror::Error; |
6 | 6 | ||
7 | #[derive(Error, Debug)] | 7 | #[derive(Error, Debug)] |
8 | pub enum ConfigErr { | 8 | pub enum ConfigErr { |
9 | #[error("error parsing glob `{0:?}`: {1}")] | 9 | #[error("error parsing ignore list `{0}`")] |
10 | InvalidGlob(Option<String>, ErrorKind), | 10 | InvalidGlob(#[from] ignore::Error), |
11 | #[error("path error: {0}")] | 11 | #[error("path error: {0}")] |
12 | InvalidPath(#[from] io::Error), | 12 | InvalidPath(#[from] io::Error), |
13 | #[error("unable to parse `{0}` as line and column")] | 13 | #[error("unable to parse `{0}` as line and column")] |
diff --git a/bin/src/main.rs b/bin/src/main.rs index 4090701..3adf42e 100644 --- a/bin/src/main.rs +++ b/bin/src/main.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | mod config; | 1 | mod config; |
2 | mod dirs; | ||
2 | mod err; | 3 | mod err; |
3 | mod explain; | 4 | mod explain; |
4 | mod fix; | 5 | mod fix; |