Compare commits

..

8 commits

Author SHA1 Message Date
af9968947c
release v0.2.13 0u0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-04-26 22:36:02 +10:00
b864b4b388
test.py: exit on non-zero exit code 2021-04-26 22:34:32 +10:00
40bdac274f
minor cleanup 2021-04-26 22:32:43 +10:00
889b2e2316
move ExtensionSet stuff into parameters.rs 2021-04-26 22:17:57 +10:00
2ceafb7acf
added -v/--verbose 2021-04-26 22:15:14 +10:00
7a315e9cf6
run clippy with all features/targets enabled 2021-04-26 21:01:49 +10:00
475998fd22
use Cargo.lock w/ CI 2021-04-26 21:01:35 +10:00
0753c7c948
update readme badge links 2021-04-26 20:52:14 +10:00
12 changed files with 118 additions and 84 deletions

View file

@ -7,7 +7,7 @@ steps:
- name: linux-gnu - name: linux-gnu
image: rust:latest image: rust:latest
commands: commands:
- cargo test -j3 - cargo test --locked -j3
#- name: linux-musl #- name: linux-musl
# image: rust:alpine # image: rust:alpine
# commands: # commands:

View file

@ -19,12 +19,12 @@ stages:
build: build:
stage: build stage: build
script: script:
- cargo build --verbose - cargo build --verbose --locked
cargo-test: cargo-test:
stage: test stage: test
script: script:
cargo test --verbose cargo test --verbose --locked
clippy: clippy:
stage: test stage: test

View file

@ -2,8 +2,9 @@
Dates are given in YYYY-MM-DD format. Dates are given in YYYY-MM-DD format.
## v0.2 ## v0.2
### v0.2.13 (2021-???) ### v0.2.13 (2021-04-26)
#### Features #### Features
- Added `-v`/`--verbose` flag for setting verbosity without using `RUST_LOG`
- Added system extension set (`.dll`, `.so`, `.exe`...) - Added system extension set (`.dll`, `.so`, `.exe`...)
- Output is now sorted: Files that couldn't be read, then files with no known mimetype, then files with no known - Output is now sorted: Files that couldn't be read, then files with no known mimetype, then files with no known
extensions, then files with the wrong extension extensions, then files with the wrong extension

2
Cargo.lock generated
View file

@ -193,7 +193,7 @@ dependencies = [
[[package]] [[package]]
name = "fif" name = "fif"
version = "0.2.12" version = "0.2.13"
dependencies = [ dependencies = [
"cached", "cached",
"cfg-if", "cfg-if",

View file

@ -1,7 +1,7 @@
[package] [package]
name = "fif" name = "fif"
description = "A command-line tool for detecting and optionally correcting files with incorrect extensions." description = "A command-line tool for detecting and optionally correcting files with incorrect extensions."
version = "0.2.12" version = "0.2.13"
authors = ["Lynnesbian <lynne@bune.city>"] authors = ["Lynnesbian <lynne@bune.city>"]
edition = "2018" edition = "2018"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"

View file

@ -6,8 +6,10 @@ fif
](https://crates.io/crates/fif) ](https://crates.io/crates/fif)
[![License](https://img.shields.io/crates/l/fif.svg?style=flat-square) [![License](https://img.shields.io/crates/l/fif.svg?style=flat-square)
](https://gitlab.com/Lynnesbian/fif/-/blob/master/LICENSE) ](https://gitlab.com/Lynnesbian/fif/-/blob/master/LICENSE)
[![Build status](https://img.shields.io/drone/build/lynnesbian/fif?logo=drone&server=https%3A%2F%2Fdrone.bune.city&style=flat-square) [![Drone build status](https://img.shields.io/drone/build/lynnesbian/fif?logo=drone&server=https%3A%2F%2Fdrone.bune.city&style=flat-square)
](https://gitlab.com/Lynnesbian/fif/-/blob/master/README.md) ](https://drone.bune.city/lynnesbian/fif)
[![GitLab build status](https://img.shields.io/gitlab/pipeline/Lynnesbian/fif/master?logo=gitlab&style=flat-square)
](https://gitlab.com/Lynnesbian/fif/-/pipelines/latest)
[![Unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg?style=flat-square) [![Unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg?style=flat-square)
](https://github.com/rust-secure-code/safety-dance/) ](https://github.com/rust-secure-code/safety-dance/)
@ -60,16 +62,6 @@ cargo install fif --no-default-features
## Usage ## Usage
See `fif --help` for more. See `fif --help` for more.
### Logging
By default, fif will log any warnings and/or errors encountered during execution. The verbosity of the logging can be
modified by the `RUST_LOG` to one of: `trace`, `debug`, `info`, `warn`, `error`.
For example:
```bash
RUST_LOG=debug fif ~/Downloads
```
### The basics ### The basics
The simplest way to use fif looks like this: The simplest way to use fif looks like this:
@ -106,3 +98,32 @@ You can also manually specify an output format to use:
```bash ```bash
fif -O powershell ~/Documents > output.ps1 fif -O powershell ~/Documents > output.ps1
``` ```
### Logging
By default, fif will log any warnings and/or errors encountered during execution. This can be changed with the `-v`
flag:
```bash
# also log info
fif -v ~/Downloads
# ...and debug
fif -vv ~/Downloads
# ...and trace
fif -vvv ~/Downloads
```
The verbosity of the logging can be
modified by the `RUST_LOG` to one of: `trace`, `debug`, `info`, `warn`, `error`.
For example:
```bash
RUST_LOG=debug fif ~/Downloads
```
The five logging levels are used as follows:
| Level | Description | Example |
|-|-|-|
| error | Errors that cause fif to stop running | fif was unable to open the provided directory |
| warn | Warnings that don't cause fif to stop running | fif was unable to determine the mime type of a given file |
| info | Information pertaining to fif's status | The provided directory was scanned without issue, and no files are in need of renaming |
| debug | Debug information - usually not important to end users | The list of extensions fif will consider |
| trace | Trace info - usually not important to end users | "Found 15 items to check", "Scan successful", etc. |

View file

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
fd -e rs -x touch {} fd -e rs -x touch {}
cargo clippy --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

@ -1,52 +0,0 @@
//! Sets of extensions for use with [Parameter](crate::parameters::Parameters)'s `-E` flag.
use clap::Clap;
#[derive(Clap, PartialEq, Debug)]
pub enum ExtensionSet {
/// Extensions used for image file formats, such as `png`, `jpeg`, `webp`, etc.
Images,
/// Extensions used for audio file formats, such as `mp3`, `ogg`, `flac`, etc.
Audio,
/// Extensions used for video file formats, such as `mkv`, `mp4`, `mov`, etc.
Videos,
/// Extensions used for media file formats. This acts as a combination of the [Images](ExtensionSet::Images),
/// [Audio](ExtensionSet::Audio) and [Videos](ExtensionSet::Videos) variants.
Media,
/// Extensions used for document file formats, such as `pdf`, `odt`, `docx`, etc.
Documents,
/// Extensions used for text file formats, such as `txt`, `toml`, `html`, etc.
Text,
/// Extensions used for archive file formats, such as `zip`, `zst`, `gz`, etc.
Archives,
/// Extensions used for system file formats, such as `mbr`, `crash`, `dll`, etc.
System,
}
impl ExtensionSet {
/// The list of known extensions for this `ExtensionSet`.
pub fn extensions(&self) -> Vec<&str> {
match self {
Self::Images => mime_guess::get_mime_extensions_str("image/*").unwrap().to_vec(),
Self::Audio => mime_guess::get_mime_extensions_str("audio/*").unwrap().to_vec(),
Self::Videos => mime_guess::get_mime_extensions_str("video/*").unwrap().to_vec(),
Self::Media => [
Self::Images.extensions(),
Self::Audio.extensions(),
Self::Videos.extensions(),
]
.concat(),
Self::Documents => vec![
"pdf", "doc", "docx", "ppt", "pptx", "xls", "xlsx", "csv", "tsv", "odt", "ods", "odp", "oda", "rtf", "ps",
"pages", "key", "numbers",
],
Self::Text => mime_guess::get_mime_extensions_str("text/*").unwrap().to_vec(),
// many compressed file types follow the name scheme "application/x.+compressed.*" - maybe this can be used
// somehow to extract extensions for compressed files from mime_guess?
Self::Archives => vec!["zip", "tar", "gz", "zst", "xz", "rar", "7z", "bz", "bz2", "tgz", "rpa"],
Self::System => vec![
"com", "dll", "exe", "sys", "reg", "nt", "cpl", "msi", "efi", "bio", "rcv", "mbr", "sbf", "grub", "ko",
"dylib", "pdb", "hdmp", "crash",
],
}
}
}

View file

@ -27,8 +27,6 @@ use clap::Clap;
use env_logger::Env; use env_logger::Env;
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
#[cfg(feature = "multi-threaded")]
use rayon::prelude::*;
use walkdir::{DirEntry, WalkDir}; use walkdir::{DirEntry, WalkDir};
use crate::findings::Findings; use crate::findings::Findings;
@ -37,7 +35,6 @@ use crate::mime_db::MimeDb;
use crate::parameters::{OutputFormat, ScanOpts}; use crate::parameters::{OutputFormat, ScanOpts};
use crate::scan_error::ScanError; use crate::scan_error::ScanError;
mod extension_set;
mod findings; mod findings;
mod formats; mod formats;
mod inspectors; mod inspectors;
@ -67,7 +64,7 @@ cfg_if! {
fn main() { fn main() {
let args: parameters::Parameters = parameters::Parameters::parse(); let args: parameters::Parameters = parameters::Parameters::parse();
let mut builder = env_logger::Builder::from_env(Env::new().filter_or("RUST_LOG", "INFO")); let mut builder = env_logger::Builder::from_env(Env::new().filter_or("RUST_LOG", args.default_verbosity()));
builder builder
// .format(|buf, r| writeln!(buf, "{} - {}", r.level(), r.args())) // .format(|buf, r| writeln!(buf, "{} - {}", r.level(), r.args()))
@ -115,13 +112,13 @@ fn main() {
match result { match result {
Ok(r) => { Ok(r) => {
debug!( debug!(
"{:?} is {}, should have file extension {}", "{:?} is of type {}, should have extension \"{}\"",
r.file, r.file,
r.mime, r.mime,
r.recommended_extension().unwrap_or_else(|| "???".into()) r.recommended_extension().unwrap_or_else(|| "???".into())
) )
} }
Err(f) => warn!("Error 0uo - {}", f), Err(f) => warn!("{}", f),
} }
} }
@ -223,14 +220,12 @@ fn scan_file(entry: &DirEntry) -> Result<Findings, ScanError> {
if result.is_err() { if result.is_err() {
// an error occurred while trying to read the file // an error occurred while trying to read the file
// error!("{}: {}", entry.path().to_string_lossy(), error);
return Err(ScanError::File(entry.path())); return Err(ScanError::File(entry.path()));
} }
let result = result.unwrap(); let result = result.unwrap();
if result.is_none() { if result.is_none() {
// the file was read successfully, but we were unable to determine its mimetype // the file was read successfully, but we were unable to determine its mimetype
// warn!("Couldn't determine mimetype for {}", entry.path().to_string_lossy());
return Err(ScanError::Mime(entry.path())); return Err(ScanError::Mime(entry.path()));
} }
@ -259,6 +254,8 @@ fn scan_file(entry: &DirEntry) -> Result<Findings, ScanError> {
fn scan_from_walkdir(entries: &[DirEntry]) -> Vec<Result<Findings, ScanError>> { fn scan_from_walkdir(entries: &[DirEntry]) -> Vec<Result<Findings, ScanError>> {
cfg_if! { cfg_if! {
if #[cfg(feature = "multi-threaded")] { if #[cfg(feature = "multi-threaded")] {
use rayon::prelude::*;
// rather than using a standard par_iter, split the entries into chunks of 32 first. // rather than using a standard par_iter, split the entries into chunks of 32 first.
// this allows each spawned thread to handle 32 files before before closing, rather than creating a new thread for // this allows each spawned thread to handle 32 files before before closing, rather than creating a new thread for
// each file. this leads to a pretty substantial speedup that i'm pretty substantially happy about 0u0 // each file. this leads to a pretty substantial speedup that i'm pretty substantially happy about 0u0

View file

@ -1,6 +1,5 @@
//! [Clap] struct used to parse command line arguments. //! [Clap] struct used to parse command line arguments.
use crate::extension_set::ExtensionSet;
use crate::string_type::String as StringType; use crate::string_type::String as StringType;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use clap::{AppSettings, Clap}; use clap::{AppSettings, Clap};
@ -72,6 +71,11 @@ pub struct Parameters {
#[clap(short, long)] #[clap(short, long)]
pub follow_symlinks: bool, pub follow_symlinks: bool,
/// Output verbosity. Defaults to only logging warnings and errors.
/// Can be overridden by RUST_LOG.
#[clap(short, long, parse(from_occurrences))]
pub verbose: u8,
/// The directory to process. /// The directory to process.
// TODO: right now this can only take a single directory - should this be improved? // TODO: right now this can only take a single directory - should this be improved?
#[clap(name = "DIR", default_value = ".", parse(from_os_str))] #[clap(name = "DIR", default_value = ".", parse(from_os_str))]
@ -110,4 +114,67 @@ impl Parameters {
follow_symlinks: self.follow_symlinks, follow_symlinks: self.follow_symlinks,
} }
} }
pub fn default_verbosity(&self) -> &'static str {
#![allow(clippy::missing_const_for_fn)]
// match was not permitted inside const functions until 1.46
match self.verbose {
0 => "warn",
1 => "info",
2 => "debug",
_ => "trace",
}
}
}
/// Sets of extensions for use with [Parameter](crate::parameters::Parameters)'s `-E` flag.
#[derive(Clap, PartialEq, Debug)]
pub enum ExtensionSet {
/// Extensions used for image file formats, such as `png`, `jpeg`, `webp`, etc.
Images,
/// Extensions used for audio file formats, such as `mp3`, `ogg`, `flac`, etc.
Audio,
/// Extensions used for video file formats, such as `mkv`, `mp4`, `mov`, etc.
Videos,
/// Extensions used for media file formats. This acts as a combination of the [Images](ExtensionSet::Images),
/// [Audio](ExtensionSet::Audio) and [Videos](ExtensionSet::Videos) variants.
Media,
/// Extensions used for document file formats, such as `pdf`, `odt`, `docx`, etc.
Documents,
/// Extensions used for text file formats, such as `txt`, `toml`, `html`, etc.
Text,
/// Extensions used for archive file formats, such as `zip`, `zst`, `gz`, etc.
Archives,
/// Extensions used for system file formats, such as `mbr`, `crash`, `dll`, etc.
System,
}
impl ExtensionSet {
/// The list of known extensions for this `ExtensionSet`.
pub fn extensions(&self) -> Vec<&str> {
match self {
Self::Images => mime_guess::get_mime_extensions_str("image/*").unwrap().to_vec(),
Self::Audio => mime_guess::get_mime_extensions_str("audio/*").unwrap().to_vec(),
Self::Videos => mime_guess::get_mime_extensions_str("video/*").unwrap().to_vec(),
Self::Media => [
Self::Images.extensions(),
Self::Audio.extensions(),
Self::Videos.extensions(),
]
.concat(),
Self::Documents => vec![
"pdf", "doc", "docx", "ppt", "pptx", "xls", "xlsx", "csv", "tsv", "odt", "ods", "odp", "oda", "rtf", "ps",
"pages", "key", "numbers",
],
Self::Text => mime_guess::get_mime_extensions_str("text/*").unwrap().to_vec(),
// many compressed file types follow the name scheme "application/x.+compressed.*" - maybe this can be used
// somehow to extract extensions for compressed files from mime_guess?
Self::Archives => vec!["zip", "tar", "gz", "zst", "xz", "rar", "7z", "bz", "bz2", "tgz", "rpa"],
Self::System => vec![
"com", "dll", "exe", "sys", "reg", "nt", "cpl", "msi", "efi", "bio", "rcv", "mbr", "sbf", "grub", "ko",
"dylib", "pdb", "hdmp", "crash",
],
}
}
} }

View file

@ -278,7 +278,7 @@ fn outputs_move_commands() {
#[test] #[test]
/// Ensure that the Media extension set contains all (is a superset) of Audio, Video, and Images. /// Ensure that the Media extension set contains all (is a superset) of Audio, Video, and Images.
fn media_contains_audio_video_images() { fn media_contains_audio_video_images() {
use crate::extension_set::ExtensionSet::{Audio, Images, Media, Videos}; use crate::parameters::ExtensionSet::{Audio, Images, Media, Videos};
let media_exts = Media.extensions(); let media_exts = Media.extensions();
// assert every extension in the audio/video/image sets is contained in the media set // assert every extension in the audio/video/image sets is contained in the media set

View file

@ -11,7 +11,7 @@ def test_archs():
for arch in archs: for arch in archs:
print(f"Testing {arch} ({upto} of {target})") print(f"Testing {arch} ({upto} of {target})")
subprocess.run(f"cross test --features=infer-backend --target {arch}-unknown-linux-gnu".split(" ")) subprocess.run(f"cross test --features=infer-backend --target {arch}-unknown-linux-gnu".split(" ")).check_returncode()
upto += 1 upto += 1
def test_versions(): def test_versions():
@ -33,11 +33,11 @@ def test_versions():
for version in versions: for version in versions:
for backend in backends: for backend in backends:
print(f"[{version}, {backend}] Tests ({upto} of {target})") print(f"[{version}, {backend}] Tests ({upto} of {target})")
subprocess.run(f"cargo +{version} test --features={backend}-backend".split(" ")) subprocess.run(f"cargo +{version} test --features={backend}-backend".split(" ")).check_returncode()
upto += 1 upto += 1
print(f"[{version}, {backend}] Scanning imgs ({upto} of {target})") print(f"[{version}, {backend}] Scanning imgs ({upto} of {target})")
subprocess.run(f"cargo +{version} run --release --features={backend}-backend -- imgs".split(" ")) subprocess.run(f"cargo +{version} run --release --features={backend}-backend -- imgs".split(" ")).check_returncode()
upto += 1 upto += 1
def main(): def main():