aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock65
-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
6 files changed, 185 insertions, 101 deletions
diff --git a/Cargo.lock b/Cargo.lock
index dd9bdc4..0cde4c2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -105,6 +105,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
105checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58" 105checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58"
106 106
107[[package]] 107[[package]]
108name = "crossbeam-utils"
109version = "0.8.5"
110source = "registry+https://github.com/rust-lang/crates.io-index"
111checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
112dependencies = [
113 "cfg-if",
114 "lazy_static",
115]
116
117[[package]]
108name = "fnv" 118name = "fnv"
109version = "1.0.7" 119version = "1.0.7"
110source = "registry+https://github.com/rust-lang/crates.io-index" 120source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -160,6 +170,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
160checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" 170checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
161 171
162[[package]] 172[[package]]
173name = "ignore"
174version = "0.4.18"
175source = "registry+https://github.com/rust-lang/crates.io-index"
176checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
177dependencies = [
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]]
163name = "indexmap" 191name = "indexmap"
164version = "1.7.0" 192version = "1.7.0"
165source = "registry+https://github.com/rust-lang/crates.io-index" 193source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -243,6 +271,12 @@ dependencies = [
243] 271]
244 272
245[[package]] 273[[package]]
274name = "once_cell"
275version = "1.8.0"
276source = "registry+https://github.com/rust-lang/crates.io-index"
277checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
278
279[[package]]
246name = "os_str_bytes" 280name = "os_str_bytes"
247version = "3.1.0" 281version = "3.1.0"
248source = "registry+https://github.com/rust-lang/crates.io-index" 282source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -344,6 +378,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
344checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 378checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
345 379
346[[package]] 380[[package]]
381name = "same-file"
382version = "1.0.6"
383source = "registry+https://github.com/rust-lang/crates.io-index"
384checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
385dependencies = [
386 "winapi-util",
387]
388
389[[package]]
347name = "serde" 390name = "serde"
348version = "1.0.130" 391version = "1.0.130"
349source = "registry+https://github.com/rust-lang/crates.io-index" 392source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -392,7 +435,7 @@ version = "0.3.5"
392dependencies = [ 435dependencies = [
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]]
510name = "thread_local"
511version = "1.1.3"
512source = "registry+https://github.com/rust-lang/crates.io-index"
513checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
514dependencies = [
515 "once_cell",
516]
517
518[[package]]
467name = "unicode-segmentation" 519name = "unicode-segmentation"
468version = "1.8.0" 520version = "1.8.0"
469source = "registry+https://github.com/rust-lang/crates.io-index" 521source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -501,6 +553,17 @@ dependencies = [
501] 553]
502 554
503[[package]] 555[[package]]
556name = "walkdir"
557version = "2.3.2"
558source = "registry+https://github.com/rust-lang/crates.io-index"
559checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
560dependencies = [
561 "same-file",
562 "winapi",
563 "winapi-util",
564]
565
566[[package]]
504name = "winapi" 567name = "winapi"
505version = "0.3.9" 568version = "0.3.9"
506source = "registry+https://github.com/rust-lang/crates.io-index" 569source = "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"
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;