Compare commits
No commits in common. "7e3efbed5c93dbcb58d20ae606712627680a2109" and "af9968947c75bc0a50f1744416314bce2b5295c5" have entirely different histories.
7e3efbed5c
...
af9968947c
7 changed files with 28 additions and 108 deletions
|
@ -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
4
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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."*"]
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
35
src/main.rs
35
src/main.rs
|
@ -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();
|
if ext.is_none() && !scan_opts.extensionless {
|
||||||
let ext = ext.as_str();
|
// don't scan files without extensions.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(exts) = exts {
|
if let Some(exts) = exts {
|
||||||
// only scan if the file has one of the specified extensions.
|
// only scan if the file has one of the specified extensions.
|
||||||
exts.contains(&ext)
|
exts.contains(&ext.unwrap().to_string_lossy().to_lowercase().as_str())
|
||||||
} else {
|
} else {
|
||||||
// no extensions specified - the file should be scanned unless its extension is on the exclude list.
|
// no extensions specified - no reason not to scan this file.
|
||||||
exclude.map_or(true, |exclude| !exclude.contains(&ext))
|
true
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// no file extension
|
|
||||||
scan_opts.extensionless
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in a new issue