Compare commits

..

No commits in common. "7e3efbed5c93dbcb58d20ae606712627680a2109" and "af9968947c75bc0a50f1744416314bce2b5295c5" have entirely different histories.

7 changed files with 28 additions and 108 deletions

View file

@ -2,10 +2,6 @@
Dates are given in YYYY-MM-DD format. Dates are given in YYYY-MM-DD format.
## v0.2 ## 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) ### v0.2.13 (2021-04-26)
#### Features #### Features
- Added `-v`/`--verbose` flag for setting verbosity without using `RUST_LOG` - Added `-v`/`--verbose` flag for setting verbosity without using `RUST_LOG`

4
Cargo.lock generated
View file

@ -313,9 +313,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.94" version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]] [[package]]
name = "log" name = "log"

View file

@ -10,7 +10,7 @@ repository = "https://gitlab.com/Lynnesbian/fif"
readme = "README.md" readme = "README.md"
keywords = ["mime", "mimetype", "utilities", "tools"] keywords = ["mime", "mimetype", "utilities", "tools"]
categories = ["command-line-utilities"] categories = ["command-line-utilities"]
exclude = [".idea/", "*.toml", "!Cargo.toml", "*.sh", "*.py", "*.yml", "*.md", ".mailmap", "pkg/"] exclude = [".idea/", "Cross.toml", "*.sh", "*.py", ".drone.yml", "pkg/"]
#resolver = "2" #resolver = "2"
#license-file = "LICENSE" #license-file = "LICENSE"
@ -66,9 +66,8 @@ fastrand = "1.4.1"
[profile.release] [profile.release]
lto = "thin" lto = "thin"
# perform some simple optimisations when testing
[profile.test] [profile.test]
opt-level = 1 opt-level = 0
# optimise dependencies, even when producing debug and test builds # optimise dependencies, even when producing debug and test builds
[profile.dev.package."*"] [profile.dev.package."*"]

View file

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

View file

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

View file

@ -40,7 +40,7 @@ pub enum OutputFormat {
setting(AppSettings::ColoredHelp) setting(AppSettings::ColoredHelp)
)] )]
pub struct Parameters { 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`. /// This argument conflicts with `-E`.
#[clap(short, long, use_delimiter = true, require_delimiter = true, group = "extensions")] #[clap(short, long, use_delimiter = true, require_delimiter = true, group = "extensions")]
pub exts: Option<Vec<StringType>>, pub exts: Option<Vec<StringType>>,
@ -50,16 +50,6 @@ pub struct Parameters {
#[clap(short = 'E', long, arg_enum, group = "extensions")] #[clap(short = 'E', long, arg_enum, group = "extensions")]
pub ext_set: Option<ExtensionSet>, 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. /// 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 /// 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. /// ~/.hidden` will recurse into `~/.hidden` regardless of whether or not -s was passed as an argument.
@ -104,41 +94,19 @@ pub struct ScanOpts {
} }
impl Parameters { 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>> { 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 { if let Some(exts) = &self.exts {
// extensions supplied like "-e png,jpg,jpeg" // extensions supplied like "-e png,jpg,jpeg"
Some( Some(exts.iter().map(|s| s.as_str()).collect())
exts
.iter()
.map(|ext| ext.as_str())
.filter(|ext| !exclude.contains(ext))
.collect(),
)
} else if let Some(exts) = &self.ext_set { } else if let Some(exts) = &self.ext_set {
// extensions supplied like "-E images" // extensions supplied like "-E images"
Some( Some(exts.extensions())
exts
.extensions()
.into_iter()
.filter(|ext| !exclude.contains(ext))
.collect(),
)
} else { } else {
// neither -E nor -e was passed // neither -E nor -e was passed
None 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 { pub const fn get_scan_opts(&self) -> ScanOpts {
ScanOpts { ScanOpts {
hidden: self.scan_hidden, hidden: self.scan_hidden,

View file

@ -113,7 +113,7 @@ fn simple_directory() {
follow_symlinks: false, follow_symlinks: false,
}; };
let entries = scan_directory(&dir.path().to_path_buf(), None, None, &scan_opts).expect("Directory scan failed."); let entries = scan_directory(&dir.path().to_path_buf(), None, &scan_opts).expect("Directory scan failed.");
assert_eq!(entries.len(), files.len()); assert_eq!(entries.len(), files.len());
@ -168,6 +168,7 @@ fn simple_directory() {
/// Ensure that command line argument parsing works correctly - flags are interpreted, booleans are set, and so on. /// Ensure that command line argument parsing works correctly - flags are interpreted, booleans are set, and so on.
fn argument_parsing() { fn argument_parsing() {
use crate::parameters::{Parameters, ScanOpts}; use crate::parameters::{Parameters, ScanOpts};
use clap::Clap; use clap::Clap;
// pass `-f`, which enables following symlinks, and `-E images`, which scans files with image extensions // pass `-f`, which enables following symlinks, and `-E images`, which scans files with image extensions
@ -176,10 +177,8 @@ fn argument_parsing() {
// check if "jpg" is in the list of extensions to be scanned // check if "jpg" is in the list of extensions to be scanned
assert!(args assert!(args
.extensions() .extensions()
.expect("args.extensions() should be Some(_)!") .expect("args.extensions() should contain the `images` set!")
.contains(&"jpg"), .contains(&"jpg"));
"args.extensions() should contain the `images` set!"
);
// make sure "scan_hidden" is false // make sure "scan_hidden" is false
assert!(!args.scan_hidden); assert!(!args.scan_hidden);
@ -187,9 +186,6 @@ fn argument_parsing() {
// exts should be none // exts should be none
assert!(args.exts.is_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 // get the ScanOpts, and make sure they match expectations
assert_eq!( assert_eq!(
args.get_scan_opts(), args.get_scan_opts(),
@ -202,36 +198,6 @@ 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] #[test]
/// Ensure that badly formed command line arguments are rejected. /// Ensure that badly formed command line arguments are rejected.
fn rejects_bad_args() { fn rejects_bad_args() {