From c0487a3b8bb3d9df1e290579bbbd425f7707b5bd Mon Sep 17 00:00:00 2001 From: Akshay Date: Thu, 4 Nov 2021 18:46:03 +0530 Subject: use ignore crate to enforce simpler ignore rules - also respects .gitignore by default - adds new flag `-u` to unrestrict statix --- Cargo.lock | 65 ++++++++++++++++++++++++++++++- bin/Cargo.toml | 2 +- bin/src/config.rs | 113 ++++++++---------------------------------------------- bin/src/dirs.rs | 99 +++++++++++++++++++++++++++++++++++++++++++++++ bin/src/err.rs | 6 +-- bin/src/main.rs | 1 + 6 files changed, 185 insertions(+), 101 deletions(-) create mode 100644 bin/src/dirs.rs diff --git a/Cargo.lock b/Cargo.lock index dd9bdc4..0cde4c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,6 +104,16 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58" +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if", + "lazy_static", +] + [[package]] name = "fnv" version = "1.0.7" @@ -159,6 +169,24 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.7.0" @@ -242,6 +270,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + [[package]] name = "os_str_bytes" version = "3.1.0" @@ -343,6 +377,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.130" @@ -392,7 +435,7 @@ version = "0.3.5" dependencies = [ "ariadne", "clap", - "globset", + "ignore", "lib", "rnix", "serde", @@ -463,6 +506,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + [[package]] name = "unicode-segmentation" version = "1.8.0" @@ -500,6 +552,17 @@ dependencies = [ "indexmap", ] +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "winapi" version = "0.3.9" 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" ariadne = "0.1.3" rnix = "0.9.0" clap = "3.0.0-beta.4" -globset = "0.4.8" +ignore = "0.4.18" thiserror = "1.0.30" similar = "2.1.0" 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 @@ -use std::{ - default::Default, - fmt, fs, io, - path::{Path, PathBuf}, - str::FromStr, -}; +use std::{default::Default, fmt, fs, path::PathBuf, str::FromStr}; + +use crate::{dirs, err::ConfigErr}; use clap::Clap; -use globset::{Error as GlobError, GlobBuilder, GlobSet, GlobSetBuilder}; use vfs::ReadOnlyVfs; -use crate::err::ConfigErr; - #[derive(Clap, Debug)] #[clap(version, author, about)] pub struct Opts { @@ -40,6 +34,10 @@ pub struct Check { #[clap(short, long)] ignore: Vec, + /// Don't respect .gitignore files + #[clap(short, long)] + unrestricted: bool, + /// Output format. /// Supported values: stderr, errfmt, json (on feature flag only) #[clap(short = 'o', long, default_value_t, parse(try_from_str))] @@ -48,8 +46,9 @@ pub struct Check { impl Check { pub fn vfs(&self) -> Result { - let files = walk_with_ignores(&self.ignore, &self.target)?; - vfs(files) + let ignore = dirs::build_ignore_set(&self.ignore, &self.target, self.unrestricted)?; + let files = dirs::walk_nix_files(ignore, &self.target)?; + vfs(files.collect::>()) } } @@ -63,6 +62,10 @@ pub struct Fix { #[clap(short, long)] ignore: Vec, + /// Don't respect .gitignore files + #[clap(short, long)] + unrestricted: bool, + /// Do not fix files in place, display a diff instead #[clap(short, long = "dry-run")] pub diff_only: bool, @@ -70,8 +73,9 @@ pub struct Fix { impl Fix { pub fn vfs(&self) -> Result { - let files = walk_with_ignores(&self.ignore, &self.target)?; - vfs(files) + let ignore = dirs::build_ignore_set(&self.ignore, &self.target, self.unrestricted)?; + let files = dirs::walk_nix_files(ignore, &self.target)?; + vfs(files.collect::>()) } } @@ -97,62 +101,6 @@ pub struct Explain { pub target: u32, } -mod dirs { - use std::{ - fs, - io::{self, Error, ErrorKind}, - path::{Path, PathBuf}, - }; - - #[derive(Default, Debug)] - pub struct Walker { - dirs: Vec, - files: Vec, - } - - impl Walker { - pub fn new>(target: P) -> io::Result { - let target = target.as_ref().to_path_buf(); - if !target.exists() { - Err(Error::new( - ErrorKind::NotFound, - format!("file not found: {}", target.display()), - )) - } else if target.is_dir() { - Ok(Self { - dirs: vec![target], - ..Default::default() - }) - } else { - Ok(Self { - files: vec![target], - ..Default::default() - }) - } - } - } - - impl Iterator for Walker { - type Item = PathBuf; - fn next(&mut self) -> Option { - if let Some(dir) = self.dirs.pop() { - if dir.is_dir() { - for entry in fs::read_dir(dir).ok()? { - let entry = entry.ok()?; - let path = entry.path(); - if path.is_dir() { - self.dirs.push(path); - } else if path.is_file() { - self.files.push(path); - } - } - } - } - self.files.pop() - } - } -} - fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> { let parts = src.split(','); match parts.collect::>().as_slice() { @@ -184,33 +132,6 @@ fn parse_warning_code(src: &str) -> Result { } } -fn build_ignore_set(ignores: &[String]) -> Result { - let mut set = GlobSetBuilder::new(); - for pattern in ignores { - let glob = GlobBuilder::new(pattern).build()?; - set.add(glob); - } - set.build() -} - -fn walk_nix_files>(target: P) -> Result, io::Error> { - let walker = dirs::Walker::new(target)?; - Ok(walker.filter(|path: &PathBuf| matches!(path.extension(), Some(e) if e == "nix"))) -} - -fn walk_with_ignores>( - ignores: &[String], - target: P, -) -> Result, ConfigErr> { - let ignores = build_ignore_set(ignores).map_err(|err| { - ConfigErr::InvalidGlob(err.glob().map(|i| i.to_owned()), err.kind().clone()) - })?; - - Ok(walk_nix_files(&target)? - .filter(|path| !ignores.is_match(path)) - .collect()) -} - fn vfs(files: Vec) -> Result { let mut vfs = ReadOnlyVfs::default(); 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 @@ +use std::{ + fs, + io::{self, Error, ErrorKind}, + path::{Path, PathBuf}, +}; + +use crate::dirs; + +use ignore::{ + gitignore::{Gitignore, GitignoreBuilder}, + Error as IgnoreError, Match, +}; + +#[derive(Debug)] +pub struct Walker { + dirs: Vec, + files: Vec, + ignore: Gitignore, +} + +impl Walker { + pub fn new>(target: P, ignore: Gitignore) -> io::Result { + let target = target.as_ref().to_path_buf(); + if !target.exists() { + Err(Error::new( + ErrorKind::NotFound, + format!("file not found: {}", target.display()), + )) + } else if target.is_dir() { + Ok(Self { + dirs: vec![target], + files: vec![], + ignore, + }) + } else { + Ok(Self { + dirs: vec![], + files: vec![target], + ignore, + }) + } + } +} + +impl Iterator for Walker { + type Item = PathBuf; + fn next(&mut self) -> Option { + if let Some(dir) = self.dirs.pop() { + if dir.is_dir() { + if let Match::None | Match::Whitelist(_) = self.ignore.matched(&dir, true) { + for entry in fs::read_dir(&dir).ok()? { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_dir() { + self.dirs.push(path); + } else if path.is_file() { + self.files.push(path); + } + } + } + } + } + self.files.pop() + } +} + +pub fn build_ignore_set>( + ignore: &[String], + target: P, + unrestricted: bool, +) -> Result { + let gitignore_path = target.as_ref().join(".gitignore"); + + // Looks like GitignoreBuilder::new does not source globs + // within gitignore_path by default, we have to enforce that + // using GitignoreBuilder::add. Probably a bug in the ignore + // crate? + let mut gitignore = GitignoreBuilder::new(&gitignore_path); + + // if we are to "restrict" aka "respect" .gitignore, then + // add globs from gitignore path as well + if !unrestricted { + gitignore.add(&gitignore_path); + } + + for i in ignore { + gitignore.add_line(None, i.as_str())?; + } + + gitignore.build() +} + +pub fn walk_nix_files>( + ignore: Gitignore, + target: P, +) -> Result, io::Error> { + let walker = dirs::Walker::new(target, ignore)?; + Ok(walker.filter(|path: &PathBuf| matches!(path.extension(), Some(e) if e == "nix"))) +} 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 @@ use std::io; -use globset::ErrorKind; +// use globset::ErrorKind; // use rnix::parser::ParseError; use thiserror::Error; #[derive(Error, Debug)] pub enum ConfigErr { - #[error("error parsing glob `{0:?}`: {1}")] - InvalidGlob(Option, ErrorKind), + #[error("error parsing ignore list `{0}`")] + InvalidGlob(#[from] ignore::Error), #[error("path error: {0}")] InvalidPath(#[from] io::Error), #[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 @@ mod config; +mod dirs; mod err; mod explain; mod fix; -- cgit v1.2.3