aboutsummaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorAkshay <[email protected]>2021-11-04 13:16:03 +0000
committerAkshay <[email protected]>2021-11-04 13:16:03 +0000
commitc0487a3b8bb3d9df1e290579bbbd425f7707b5bd (patch)
tree3be2fa52ec7e48d094f45e3c8f85b1819bac6e66 /bin
parent5b87c6feb3e4a2fcc30ad94125be3dcd4e554754 (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.toml2
-rw-r--r--bin/src/config.rs113
-rw-r--r--bin/src/dirs.rs99
-rw-r--r--bin/src/err.rs6
-rw-r--r--bin/src/main.rs1
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"
12ariadne = "0.1.3" 12ariadne = "0.1.3"
13rnix = "0.9.0" 13rnix = "0.9.0"
14clap = "3.0.0-beta.4" 14clap = "3.0.0-beta.4"
15globset = "0.4.8" 15ignore = "0.4.18"
16thiserror = "1.0.30" 16thiserror = "1.0.30"
17similar = "2.1.0" 17similar = "2.1.0"
18vfs = { path = "../vfs" } 18vfs = { 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 @@
1use std::{ 1use std::{default::Default, fmt, fs, path::PathBuf, str::FromStr};
2 default::Default, 2
3 fmt, fs, io, 3use crate::{dirs, err::ConfigErr};
4 path::{Path, PathBuf},
5 str::FromStr,
6};
7 4
8use clap::Clap; 5use clap::Clap;
9use globset::{Error as GlobError, GlobBuilder, GlobSet, GlobSetBuilder};
10use vfs::ReadOnlyVfs; 6use vfs::ReadOnlyVfs;
11 7
12use crate::err::ConfigErr;
13
14#[derive(Clap, Debug)] 8#[derive(Clap, Debug)]
15#[clap(version, author, about)] 9#[clap(version, author, about)]
16pub struct Opts { 10pub 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
49impl Check { 47impl 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
71impl Fix { 74impl 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
100mod 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
156fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> { 104fn 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
187fn 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
196fn 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
201fn 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
214fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> { 135fn 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 @@
1use std::{
2 fs,
3 io::{self, Error, ErrorKind},
4 path::{Path, PathBuf},
5};
6
7use crate::dirs;
8
9use ignore::{
10 gitignore::{Gitignore, GitignoreBuilder},
11 Error as IgnoreError, Match,
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 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
67pub 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
93pub 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 @@
1use std::io; 1use std::io;
2 2
3use globset::ErrorKind; 3// use globset::ErrorKind;
4// use rnix::parser::ParseError; 4// use rnix::parser::ParseError;
5use thiserror::Error; 5use thiserror::Error;
6 6
7#[derive(Error, Debug)] 7#[derive(Error, Debug)]
8pub enum ConfigErr { 8pub 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 @@
1mod config; 1mod config;
2mod dirs;
2mod err; 3mod err;
3mod explain; 4mod explain;
4mod fix; 5mod fix;