added exclude flag for excluding extensions
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Lynne Megido 2021-04-27 20:25:41 +10:00
parent aad70ee13b
commit 7e3efbed5c
Signed by: lynnesbian
GPG Key ID: F0A184B5213D9F90
6 changed files with 105 additions and 25 deletions

View File

@ -2,6 +2,10 @@
Dates are given in YYYY-MM-DD format.
## v0.2
### v0.2.14 (2021-xx-yy)
#### Features
- Added `-x`/`--exclude` flag for excluding file extensions (overrides `-e` or `-E`)
### v0.2.13 (2021-04-26)
#### Features
- Added `-v`/`--verbose` flag for setting verbosity without using `RUST_LOG`

View File

@ -66,8 +66,9 @@ fastrand = "1.4.1"
[profile.release]
lto = "thin"
# perform some simple optimisations when testing
[profile.test]
opt-level = 0
opt-level = 1
# optimise dependencies, even when producing debug and test builds
[profile.dev.package."*"]

View File

@ -1,6 +1,6 @@
#!/bin/bash
fd -e rs -x touch {}
cargo clippy --all-features -- \
cargo clippy --all-features --tests -- \
-W clippy::nursery \
-W clippy::perf \
-W clippy::pedantic \

View File

@ -78,10 +78,17 @@ fn main() {
debug!("Iterating directory: {:?}", args.dirs);
let extensions = args.extensions();
let excludes = args.excluded_extensions();
debug!("Checking files with extensions: {:?}", extensions);
if let Some(extensions) = &extensions {
debug!("Checking files with extensions: {:?}", extensions);
} else if let Some(excludes) = &excludes {
debug!("Skipping files with extensions: {:?}", excludes);
} else {
debug!("Checking files regardless of extensions");
}
let entries = scan_directory(&args.dirs, extensions.as_ref(), &args.get_scan_opts());
let entries = scan_directory(&args.dirs, extensions.as_ref(), excludes.as_ref(), &args.get_scan_opts());
if entries.is_none() {
// no need to log anything for fatal errors - fif will already have printed something obvious like
@ -172,7 +179,7 @@ cfg_if! {
/// Returns `true` if a file matches the given criteria. This means checking whether the file's extension appears in
/// `exts` (if specified), potentially skipping over hidden files, and so on.
fn wanted_file(entry: &DirEntry, exts: Option<&Vec<&str>>, scan_opts: &ScanOpts) -> bool {
fn wanted_file(entry: &DirEntry, exts: Option<&Vec<&str>>, exclude: Option<&Vec<&str>>, scan_opts: &ScanOpts) -> bool {
if entry.depth() == 0 {
// the root directory should always be scanned.
return true;
@ -188,19 +195,21 @@ fn wanted_file(entry: &DirEntry, exts: Option<&Vec<&str>>, scan_opts: &ScanOpts)
return true;
}
let ext = extension_from_path(entry.path());
if let Some(ext) = extension_from_path(entry.path()) {
// file has extension - discard invalid UTF-8 and normalise it to lowercase.
let ext = ext.to_string_lossy().to_lowercase();
let ext = ext.as_str();
if ext.is_none() && !scan_opts.extensionless {
// don't scan files without extensions.
return false;
}
if let Some(exts) = exts {
// only scan if the file has one of the specified extensions.
exts.contains(&ext.unwrap().to_string_lossy().to_lowercase().as_str())
if let Some(exts) = exts {
// only scan if the file has one of the specified extensions.
exts.contains(&ext)
} else {
// no extensions specified - the file should be scanned unless its extension is on the exclude list.
exclude.map_or(true, |exclude| !exclude.contains(&ext))
}
} else {
// no extensions specified - no reason not to scan this file.
true
// no file extension
scan_opts.extensionless
}
}
@ -276,11 +285,11 @@ fn scan_from_walkdir(entries: &[DirEntry]) -> Vec<Result<Findings, ScanError>> {
/// Scans a given directory with [`WalkDir`], filters with [`wanted_file`], checks for errors, and returns a vector of
/// [DirEntry]s.
fn scan_directory(dirs: &Path, exts: Option<&Vec<&str>>, scan_opts: &ScanOpts) -> Option<Vec<DirEntry>> {
fn scan_directory(dirs: &Path, exts: Option<&Vec<&str>>, exclude: Option<&Vec<&str>>, scan_opts: &ScanOpts) -> Option<Vec<DirEntry>> {
let stepper = WalkDir::new(dirs).follow_links(scan_opts.follow_symlinks).into_iter();
let mut probably_fatal_error = false;
let entries: Vec<DirEntry> = stepper
.filter_entry(|e| wanted_file(e, exts, scan_opts)) // filter out unwanted files
.filter_entry(|e| wanted_file(e, exts, exclude, scan_opts)) // filter out unwanted files
.filter_map(|e| {
if let Err(err) = &e {
debug!("uh oh spaghettio!! {:#?}", e);

View File

@ -40,7 +40,7 @@ pub enum OutputFormat {
setting(AppSettings::ColoredHelp)
)]
pub struct Parameters {
/// Only examine files with these extensions (Comma-separated list).
/// Only examine files with these extensions (comma-separated list).
/// This argument conflicts with `-E`.
#[clap(short, long, use_delimiter = true, require_delimiter = true, group = "extensions")]
pub exts: Option<Vec<StringType>>,
@ -50,6 +50,16 @@ pub struct Parameters {
#[clap(short = 'E', long, arg_enum, group = "extensions")]
pub ext_set: Option<ExtensionSet>,
/// Don't scan files with these extensions (comma-separated list).
/// This option takes preference over files specified with -e or -E.
#[clap(
short = 'x',
long,
use_delimiter = true,
require_delimiter = true,
)]
pub exclude: Option<Vec<StringType>>,
/// Don't skip hidden files and directories.
/// Even if this flag is not present, fif will still recurse into a hidden root directory - for example, `fif
/// ~/.hidden` will recurse into `~/.hidden` regardless of whether or not -s was passed as an argument.
@ -94,19 +104,41 @@ pub struct ScanOpts {
}
impl Parameters {
/// Returns an optional vec of the extensions to be scanned - i.e., extensions specified via the `-e` or `-E` flag,
/// minus the extensions excluded with the `-x` flag.
pub fn extensions(&self) -> Option<Vec<&str>> {
let empty_vec = vec![];
let exclude = &self.excluded_extensions().unwrap_or(empty_vec);
// TODO: bleugh
if let Some(exts) = &self.exts {
// extensions supplied like "-e png,jpg,jpeg"
Some(exts.iter().map(|s| s.as_str()).collect())
Some(
exts
.iter()
.map(|ext| ext.as_str())
.filter(|ext| !exclude.contains(ext))
.collect(),
)
} else if let Some(exts) = &self.ext_set {
// extensions supplied like "-E images"
Some(exts.extensions())
Some(
exts
.extensions()
.into_iter()
.filter(|ext| !exclude.contains(ext))
.collect(),
)
} else {
// neither -E nor -e was passed
None
}
}
pub fn excluded_extensions(&self) -> Option<Vec<&str>> {
self.exclude.as_ref().map(|exclude| exclude.iter().map(|ext| ext.as_str()).collect())
}
pub const fn get_scan_opts(&self) -> ScanOpts {
ScanOpts {
hidden: self.scan_hidden,

View File

@ -113,7 +113,7 @@ fn simple_directory() {
follow_symlinks: false,
};
let entries = scan_directory(&dir.path().to_path_buf(), None, &scan_opts).expect("Directory scan failed.");
let entries = scan_directory(&dir.path().to_path_buf(), None, None, &scan_opts).expect("Directory scan failed.");
assert_eq!(entries.len(), files.len());
@ -168,7 +168,6 @@ fn simple_directory() {
/// Ensure that command line argument parsing works correctly - flags are interpreted, booleans are set, and so on.
fn argument_parsing() {
use crate::parameters::{Parameters, ScanOpts};
use clap::Clap;
// pass `-f`, which enables following symlinks, and `-E images`, which scans files with image extensions
@ -177,8 +176,10 @@ fn argument_parsing() {
// check if "jpg" is in the list of extensions to be scanned
assert!(args
.extensions()
.expect("args.extensions() should contain the `images` set!")
.contains(&"jpg"));
.expect("args.extensions() should be Some(_)!")
.contains(&"jpg"),
"args.extensions() should contain the `images` set!"
);
// make sure "scan_hidden" is false
assert!(!args.scan_hidden);
@ -186,6 +187,9 @@ fn argument_parsing() {
// exts should be none
assert!(args.exts.is_none());
// there shouldn't be any excluded extensions
assert!(args.excluded_extensions().is_none());
// get the ScanOpts, and make sure they match expectations
assert_eq!(
args.get_scan_opts(),
@ -198,6 +202,36 @@ fn argument_parsing() {
)
}
#[test]
/// Ensure exclude overrides `-e` and `-E`.
fn exclude_overrides() {
use crate::parameters::{Parameters};
use clap::Clap;
// pass `-E images`, which includes many image extensions, and `-x jpg,png`, which should remove "jpg" and "png" from
// the extensions list
let args: Parameters = Parameters::parse_from(vec!["fif", "-x", "jpg,png", "-E", "images"]);
let extensions = args.extensions();
assert!(extensions.is_some(), "Extensions should contain the `images` set!");
let extensions = extensions.unwrap();
assert!(!extensions.contains(&"jpg"), "\"jpg\" should be excluded!");
assert!(!extensions.contains(&"png"), "\"png\" should be excluded!");
assert!(extensions.contains(&"jpeg"), "\"jpeg\" should be included!");
// pass `-e abc,def,ghi,jkl` and `-x abc,def` -- extensions() should only contain "ghi" and "jkl"
let args: Parameters = Parameters::parse_from(vec!["fif", "-e", "abc,def,ghi,jkl", "-x", "abc,def"]);
let extensions = args.extensions();
assert!(extensions.is_some(), "Extensions should be set!");
let extensions = extensions.unwrap();
assert!(!extensions.contains(&"abc"));
assert!(!extensions.contains(&"def"));
assert!(extensions.contains(&"ghi"));
assert!(extensions.contains(&"jkl"));
}
#[test]
/// Ensure that badly formed command line arguments are rejected.
fn rejects_bad_args() {