From 9ac0957e2836446c2fda5d8f8ff7f869fd5860bd Mon Sep 17 00:00:00 2001 From: Akshay Date: Thu, 4 Nov 2021 18:47:28 +0530 Subject: Squashed commit of the following: commit c0487a3b8bb3d9df1e290579bbbd425f7707b5bd Author: Akshay Date: Thu Nov 4 18:46:03 2021 +0530 use ignore crate to enforce simpler ignore rules - also respects .gitignore by default - adds new flag `-u` to unrestrict statix --- bin/Cargo.toml | 2 +- bin/src/config.rs | 113 ++++++++---------------------------------------------- bin/src/dirs.rs | 99 +++++++++++++++++++++++++++++++++++++++++++++++ bin/src/err.rs | 6 +-- bin/src/main.rs | 1 + 5 files changed, 121 insertions(+), 100 deletions(-) create mode 100644 bin/src/dirs.rs (limited to 'bin') 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