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 /bin | |
parent | 5b87c6feb3e4a2fcc30ad94125be3dcd4e554754 (diff) |
use ignore crate to enforce simpler ignore rulesfix-ignores
- also respects .gitignore by default
- adds new flag `-u` to unrestrict statix
Diffstat (limited to 'bin')
-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 |
5 files changed, 121 insertions, 100 deletions
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; |