Compare commits

...

27 commits

Author SHA1 Message Date
484a27b044 oopsies 2024-08-08 12:25:53 +10:00
274f6a44c1
release v0.7.2 🥳 2024-08-07 22:46:39 +10:00
74e14fca78
update copyright 2024-08-07 22:43:55 +10:00
72256ae8d2
MSRV-compatible updates, clippification 2024-08-07 22:41:38 +10:00
a570b07672
release v0.7.1 🥳 2024-01-21 17:04:58 +10:00
10c6512231
REALLY update gitlab CI 2023-03-15 09:59:12 +10:00
c048d21935
update gitlab CI MSRV 2023-03-15 09:17:51 +10:00
4565487149
release v0.7.0 🥳 2023-03-15 06:53:05 +10:00
5017854dd3
clippy, format 2023-03-15 06:51:29 +10:00
4469104ac1
update changelog 2023-03-15 06:46:09 +10:00
7e03dd7cf7
fix value parsing, bump MSRV 2023-03-15 06:42:44 +10:00
61196df626
update clap to 4.1 2023-03-15 06:23:59 +10:00
9e592863f1
update dependencies 2023-03-15 06:12:38 +10:00
62a630249c
update dependencies 2023-01-02 07:36:11 +10:00
57fbbc23be visual studio code 2022-09-19 01:59:48 +10:00
476209b82e
release v0.6.0 🥳 2022-09-04 13:52:37 +10:00
acd33980e9
clippy cleanup 2022-09-04 13:48:54 +10:00
420bd0483c
update all dependencies, remove deprecated code 2022-09-04 13:41:41 +10:00
866a8d41d5
implement Eq where possible 2022-09-04 12:55:30 +10:00
74098ed281
update and fix dependencies 2022-09-04 12:47:17 +10:00
c15d92eaa6
update deps 2022-05-20 07:18:57 +10:00
b19a508020
fix changelog 2022-05-05 09:45:44 +10:00
cabed845ce
release v0.5.2 🥳 2022-05-02 18:22:09 +10:00
20587fd663
clippy! 2022-05-02 18:13:06 +10:00
944360f9ab
documentation 2022-05-02 18:11:28 +10:00
c9f68d8373
add reminder to use fif --fix 2022-05-02 18:07:14 +10:00
1b0505aad1
don't rename certain zip/exe subtypes. fixes #1 2022-05-02 18:06:51 +10:00
20 changed files with 792 additions and 513 deletions

1
.gitignore vendored
View file

@ -21,3 +21,4 @@ cargo-timing*.html
*.sync-conflict* *.sync-conflict*
.idea/sonarlint .idea/sonarlint
.directory .directory
*.tmp

View file

@ -105,7 +105,7 @@ build-base-stable:
build-base-msrv: build-base-msrv:
extends: build-base-stable extends: build-base-stable
image: "rust:1.54.0" image: "rust:1.64.0"
cache: cache:
key: msrv key: msrv
paths: paths:
@ -131,7 +131,7 @@ build-stable:
build-msrv: build-msrv:
extends: build-stable extends: build-stable
needs: ["build-base-msrv"] needs: ["build-base-msrv"]
image: "rust:1.54.0" image: "rust:1.64.0"
cache: cache:
key: msrv key: msrv
paths: paths:
@ -156,7 +156,7 @@ test-stable:
test-msrv: test-msrv:
extends: test-stable extends: test-stable
image: "rust:1.54.0" image: "rust:1.64.0"
needs: ["build-msrv"] needs: ["build-msrv"]
cache: cache:
key: msrv key: msrv

64
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,64 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in library 'fif'",
"cargo": {
"args": [
"test",
"--no-run",
"--lib",
"--package=fif"
],
"filter": {
"name": "fif",
"kind": "lib"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'fif'",
"cargo": {
"args": [
"build",
"--bin=fif",
"--package=fif"
],
"filter": {
"name": "fif",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'fif'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=fif",
"--package=fif"
],
"filter": {
"name": "fif",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

View file

@ -4,6 +4,39 @@ Dates are given in YYYY-MM-DD format - for example, the 15th of October 2021 is
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## v0.7.2 - 2024-08-07
### Changed
- Updated and pinned dependencies, using the latest MSRV-compatible versions available.
- Implement clippy suggestions
## v0.7.1 - 2024-01-24
### Changed
- Updated and pinned dependencies, using the latest MSRV-compatible versions available.
## v0.7.0 - 2023-03-15
### Changed
- The Minimum Supported Rust Version (MSRV) is now **1.64.0**.
- Update [`clap`] to v4.0
- Update [`infer`] to v0.13.0, bringing support for formats such as [CPIO](https://en.wikipedia.org/wiki/Cpio) and
[OpenRaster](https://www.openraster.org/index.html)
## v0.6.0 - 2022-09-04
### Changed
- The Minimum Supported Rust Version (MSRV) is now **1.57.0**.
- Update [`clap`] to v3.2.0
- Update [`smartstring`] to v1.0 - this should (slightly) improve performance on 32-bit big endian architectures
such as PowerPC
## v0.5.2 - 2022-05-02
### Added
- Output now contains a reminder to use `fif --fix`
### Changed
- Some extensions are considered to be always valid - these are:
- "filepart", "part", "crdownload": Partially downloaded files, renaming could break download
- "bak", "backup": Backup copies are a common idiom (e.g. "game.exe.bak") and should be respected
### Fixed
- Support for many file types that are subcategories of others (e.g., fif will no longer rename apk files to zip) (#1)
## v0.5.1 - 2022-04-12 ## v0.5.1 - 2022-04-12
### Added ### Added
- When using the [`infer`] backend, fif is now able to detect [Mach-O](https://en.wikipedia.org/wiki/Mach-O) binaries - When using the [`infer`] backend, fif is now able to detect [Mach-O](https://en.wikipedia.org/wiki/Mach-O) binaries
@ -16,7 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## v0.5.0 - 2022-01-01 ## v0.5.0 - 2022-01-01
### Changed ### Changed
- The Minimum Supported Rust Version (MSRV) is now **1.54.0**. - The Minimum Supported Rust Version (MSRV) is now **1.57.0**.
- Updated [`new_mime_guess`] to 4.0.0 - Updated [`new_mime_guess`] to 4.0.0
- `--version` output now handles missing Git commit hashes, and specifies the target operating system - `--version` output now handles missing Git commit hashes, and specifies the target operating system
### Fixed ### Fixed

875
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,11 @@
[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.5.1" version = "0.7.2"
authors = ["Lynnesbian <lynne@bune.city>"] authors = ["Lynnesbian <lynne@bune.city>"]
edition = "2018" edition = "2021"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
rust-version = "1.54.0" # clap 3 requires >=1.54.0 rust-version = "1.64.0" # clap 4.1 requires >=1.64.0
repository = "https://gitlab.com/Lynnesbian/fif" repository = "https://gitlab.com/Lynnesbian/fif"
readme = "README.md" readme = "README.md"
keywords = ["mime", "mimetype", "utilities", "tools"] keywords = ["mime", "mimetype", "utilities", "tools"]
@ -23,57 +23,55 @@ xdg-mime-backend = ["xdg-mime"]
json = ["serde", "serde_json"] json = ["serde", "serde_json"]
[dependencies] [dependencies]
walkdir = "~2.3.2" walkdir = "2.4.0"
log = "0.4.14" log = "0.4.14"
mime = "0.3.16" mime = "0.3.16"
mime_guess = { package = "new_mime_guess", features = ["phf-map"], version = "4.0.0" } mime_guess = { package = "new_mime_guess", version = "4.0.0" }
snailquote = "0.3.0" snailquote = "0.3.0"
once_cell = "1.8.0" once_cell = "1.8.0"
rayon = { version = "1.5.0", optional = true } rayon = { version = "1.5.0", optional = true }
exitcode = "1.1.2" exitcode = "1.1.2"
cfg-if = "1.0.0" cfg-if = "1.0.0"
itertools = "0.10.0" itertools = "0.13.0"
serde = { version = "1.0", features = ["derive"], optional = true } serde = { version = "1.0", features = ["derive"], optional = true }
serde_json = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true }
num_cpus = { version = "1.13.0", optional = true } num_cpus = { version = "1.13.0", optional = true }
maplit = "1.0.2"
parking_lot = "0.12.0" parking_lot = "0.12.0"
smartstring = "1"
maplit = "1.0.2"
# various clap dependencies with MSRV-incompatible versions:
anstyle-query = "=1.0.0"
anstyle-parse = "=0.2.1"
anstyle = "=1.0.2"
clap_lex = "=0.5.0"
colorchoice = "=1.0.0"
[target.'cfg(not(unix))'.dependencies] [target.'cfg(not(unix))'.dependencies]
xdg-mime = { version = "0.3.3", optional = true } xdg-mime = { version = "0.4.0", optional = true }
infer = "0.7.0" infer = "=0.13.0" # 0.14.0 uses `let else`, which requires rust 1.65
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
xdg-mime = "0.3.3" xdg-mime = "0.4.0"
infer = { version = "0.7.0", optional = true } infer = { version = "=0.13.0", optional = true }
[target.'cfg(not(all(target_endian = "big", target_pointer_width = "32")))'.dependencies]
# the seemingly weird target constraint here is due to this:
# https://github.com/bodil/smartstring/blob/v0.2.9/src/config.rs#L91-L93
# essentially, smartstring is intentionally blocked from compiling on 32-bit big endian archs, so our dependency on it
# needs to be too. otherwise, fif won't work on platforms like powerpc, even though this dependency is the only
# blocker -- fif runs just fine on powerpc (or on my powerbook G4, anyway) without smartstring.
# additionally, versions before 0.2.4 didn't impl Display, so we need at least that version for displaying Strings.
# version 1.0 of smartstring adds 32-bit BE compatibility (by rewriting the implementation), but requires rust 1.57.0,
# so we can't use it.
smartstring = ">= 0.2.4, <=0.2.9"
[dependencies.clap] [dependencies.clap]
version = "3.1.0" version = "~4.3" #4.4 has some deps that can't be satisfied on our MSRV
default-features = false default-features = false
features = ["wrap_help", "color", "derive", "std", "unicode"] features = ["wrap_help", "color", "derive", "std", "unicode"]
[dependencies.env_logger] [dependencies.env_logger]
version = "0.9.0" version = "0.10.0" # 0.11 depends on is_terminal_polyfill 1.70.1, which requires rust 1.70
default-features = false default-features = false
features = ["termcolor", "atty"] features = ["color"]
[dev-dependencies] [dev-dependencies]
tempfile = "3.2.0" tempfile = "3.2.0"
rand = "0.8.3" rand = "0.8.3"
assert_cmd = "2.0.2" assert_cmd = "=2.0.5" # higher versions than this have dependencies that require later rust versions
regex = { version = "1.5.4", default-features = false, features = ["std"] } predicates-tree = "=1.0.9"
predicates-core = "=1.0.6"
regex = { version = "=1.9.5", default-features = false, features = ["std"] } # 1.9.6 requires rust 1.65
[profile.release] [profile.release]
lto = "thin" lto = "thin"
@ -86,4 +84,4 @@ opt-level = 3
opt-level = 3 opt-level = 3
[package.metadata] [package.metadata]
msrv = "1.54.0" msrv = "1.64.0"

View file

@ -7,7 +7,7 @@
[![Version](https://img.shields.io/crates/v/fif.svg?logo=rust&style=flat-square) [![Version](https://img.shields.io/crates/v/fif.svg?logo=rust&style=flat-square)
](https://crates.io/crates/fif) ](https://crates.io/crates/fif)
[![Minimum Supported Rust Version](https://img.shields.io/badge/msrv-1.54.0-orange?logo=rust&style=flat-square) [![Minimum Supported Rust Version](https://img.shields.io/badge/msrv-1.57.0-orange?logo=rust&style=flat-square)
](https://gitlab.com/Lynnesbian/fif/-/blob/master/README.md#version-policy) ](https://gitlab.com/Lynnesbian/fif/-/blob/master/README.md#version-policy)
[![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)
@ -232,6 +232,10 @@ same line (["OTBS"](https://en.wikipedia.org/wiki/Indentation_style#Variant:_1TB
For more detailed information on the formatting rules used by this project, see the configured options in For more detailed information on the formatting rules used by this project, see the configured options in
[`rustfmt.toml`](https://gitlab.com/Lynnesbian/fif/-/blob/master/rustfmt.toml). [`rustfmt.toml`](https://gitlab.com/Lynnesbian/fif/-/blob/master/rustfmt.toml).
## Additional credits
Some of the code for correctly handling files with multiple valid extensions (particularly in the case of the
Portable Executable format) comes from [Czkawka](https://github.com/qarmin/czkawka)
## License ## License
Copyright (C) 2021 Lynnesbian Copyright (C) 2021 Lynnesbian

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian // SPDX-FileCopyrightText: 2021-2024 Lynnesbian
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
use std::process::Command; use std::process::Command;
@ -14,9 +14,9 @@ fn main() -> Result<(), String> {
// a more robust way of doing this would be to use vergen (https://github.com/rustyhorde/vergen), but it pulls in a // a more robust way of doing this would be to use vergen (https://github.com/rustyhorde/vergen), but it pulls in a
// whole bunch of extra dependencies (including chrono and git2), and also blocks compilation on the current MSRV. // whole bunch of extra dependencies (including chrono and git2), and also blocks compilation on the current MSRV.
// this method is less clever and robust, but it works! // this method is less clever and robust, but it works!
let git = Command::new("git").args(&["rev-parse", "--short", "HEAD"]).output(); let git = Command::new("git").args(["rev-parse", "--short", "HEAD"]).output();
let hash = match git { let hash = match git {
Ok(output) => String::from_utf8_lossy(&*output.stdout).into(), Ok(output) => String::from_utf8_lossy(&output.stdout).into(),
Err(_) => { Err(_) => {
// git not being present (or failing) shouldn't block compilation // git not being present (or failing) shouldn't block compilation
println!("cargo:warning=Failed to retrieve git commit hash"); println!("cargo:warning=Failed to retrieve git commit hash");

View file

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# SPDX-FileCopyrightText: 2021-2022 Lynnesbian # SPDX-FileCopyrightText: 2021-2024 Lynnesbian
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
set -e set -e
@ -40,6 +40,7 @@ for backend in "${_backends[@]}"; do
-A clippy::must_use_candidate \ -A clippy::must_use_candidate \
-A clippy::missing_panics_doc \ -A clippy::missing_panics_doc \
-A clippy::missing_errors_doc \ -A clippy::missing_errors_doc \
-A clippy::doc_markdown \
"$_extra" "$_extra"
done done
@ -52,3 +53,4 @@ done
# must_use_candidate: useless # must_use_candidate: useless
# missing_panics_doc: the docs are just for me, fif isn't really intended to be used as a library, so this is unneeded # missing_panics_doc: the docs are just for me, fif isn't really intended to be used as a library, so this is unneeded
# missing_errors_doc: ditto # missing_errors_doc: ditto
# doc_markdown: way too many false positives

View file

@ -1,3 +1,3 @@
# avoid-breaking-exported-api = false # only available on nightly for now # avoid-breaking-exported-api = false # only available on nightly for now
cognitive-complexity-threshold = 15 cognitive-complexity-threshold = 15
msrv = "1.54.0" msrv = "1.64.0"

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian // SPDX-FileCopyrightText: 2021-2024 Lynnesbian
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
//! File handling - scanning, detecting MIME types, and so on. //! File handling - scanning, detecting MIME types, and so on.
@ -127,16 +127,38 @@ pub fn scan_file(entry: &DirEntry, canonical_paths: bool) -> Result<Findings, Sc
Ok(Some(result)) => result, Ok(Some(result)) => result,
}; };
// set of known extensions for the given MIME type // determine whether or not the file's current extension is valid
let known_exts = mime_extension_lookup(result.essence_str().into()); let valid = if let Some(entry_ext) = path.extension() {
// file extension for this particular file // discard invalid UTF-8 and convert to lowercase. all extensions in both backend's databases are lowercase
let entry_ext = path.extension(); // ascii, so this assumption is fine.
let entry_ext = entry_ext.to_string_lossy().to_lowercase();
let valid = match known_exts { // if the file has any of these extensions, it is probably either:
// there is a known set of extensions for this MIME type, and the file has an extension // - a copy of another file renamed for backup purposes (e.g. a word processor might save by renaming "my.doc" to
Some(e) if entry_ext.is_some() => e.contains(&entry_ext.unwrap().to_string_lossy().to_lowercase().into()), // "my.doc.bak", then creating "my.doc", leaving the backup for safekeeping), which shouldn't be renamed so as
// either this file has no extension, or there is no known set of extensions for this MIME type :( // not to break the backup program
Some(_) | None => false, // - a partially downloaded file, which shouldn't be renamed to avoid corrupting it and blocking the downloader
// from resuming
if ["bak", "backup", "filepart", "part", "crdownload"]
.iter()
.any(|ext| ext == &entry_ext)
{
true
} else {
// otherwise, check to see whether there's a known extension for this file type
// retrieve set of known extensions for the given MIME type
let known_exts = mime_extension_lookup(result.essence_str().into());
match known_exts {
// there is a known set of extensions for this MIME type - is entry_ext in the given set?
Some(e) => e.contains(&entry_ext.into()),
// there is no known set of extensions for this MIME type :(
None => false,
}
}
} else {
// this file has no extension
false
}; };
let path = if canonical_paths { let path = if canonical_paths {
@ -262,9 +284,8 @@ pub fn mime_type<T: MimeDb>(db: &T, path: &Path) -> io::Result<Option<Mime>> {
file.seek(SeekFrom::Start(0))?; file.seek(SeekFrom::Start(0))?;
read = file.read(&mut buffer); read = file.read(&mut buffer);
match read { match read {
Ok(_) => break,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(_) => break, Err(_) | Ok(_) => break,
} }
} }
@ -296,7 +317,7 @@ pub fn mime_type<T: MimeDb>(db: &T, path: &Path) -> io::Result<Option<Mime>> {
// attempt to read up to BUF_SIZE bytes of the file. // attempt to read up to BUF_SIZE bytes of the file.
let mut buffer = [0; BUF_SIZE]; let mut buffer = [0; BUF_SIZE];
file.seek(SeekFrom::Start(0))?; file.seek(SeekFrom::Start(0))?;
file.read(&mut buffer)?; _ = file.read(&mut buffer)?;
Ok(db.get_type(&buffer)) Ok(db.get_type(&buffer))
} }
@ -350,16 +371,47 @@ pub fn mime_extension_lookup(essence: String) -> Option<Vec<String>> {
// classic office files considered harmful // classic office files considered harmful
vec![String::from("doc"), String::from("xls"), String::from("ppt")] vec![String::from("doc"), String::from("xls"), String::from("ppt")]
} else if essence == "application/zip" { } else if essence == "application/zip" {
// neither xdg-mime nor infer seem to be able to detect office XML files properly... // both backends seem to be unable to consistently detect OOXML files, so they should be considered valid
// extensions for zip files to prevent them being erroneously renamed.
// additionally, there are various popular formats that are just renamed zip files, such as android's apk
// format, that also shouldn't be renamed.
[ [
vec![String::from("zip"), String::from("docx"), String::from("xlsx"), String::from("pptx")], vec![
String::from("zip"),
String::from("docx"),
String::from("xlsx"),
String::from("pptx"),
String::from("apk"),
String::from("ipa"),
String::from("docbook"),
String::from("kdenlive"),
String::from("vcpkg"),
String::from("nupkg"),
String::from("whl"),
String::from("xpi"),
],
possible_exts, possible_exts,
] ]
.concat() .concat()
} else if essence == "application/x-ms-dos-executable" { } else if essence == "application/x-ms-dos-executable" {
// .dll, .exe, and .scr files are given the same MIME type... but you definitely don't want to rename one to the // .dll, .exe, .scr, etc. files are given the same MIME type, and aren't really distinguishable from each other
// other! // ... but you definitely don't want to rename one to the other!
[vec![String::from("dll"), String::from("exe"), String::from("scr")], possible_exts].concat() [
vec![
String::from("exe"),
String::from("dll"),
String::from("scr"),
String::from("com"),
String::from("dll16"),
String::from("drv"),
String::from("drv16"),
String::from("cpl"),
String::from("msstyles"),
String::from("sys"),
],
possible_exts,
]
.concat()
} else { } else {
possible_exts possible_exts
}) })

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian // SPDX-FileCopyrightText: 2021-2024 Lynnesbian
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
//! The [`Findings`] and [`ScanError`] structs, used for conveying whether a given file was able to be scanned, whether //! The [`Findings`] and [`ScanError`] structs, used for conveying whether a given file was able to be scanned, whether

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian // SPDX-FileCopyrightText: 2021-2024 Lynnesbian
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
//! Logic for handling the various output formats that fif can output to. //! Logic for handling the various output formats that fif can output to.
@ -31,7 +31,6 @@ use crate::String;
/// // ...just use: /// // ...just use:
/// smart_write(&mut f, writables!["hello", Newline]); /// smart_write(&mut f, writables!["hello", Newline]);
/// ``` /// ```
#[macro_export] #[macro_export]
macro_rules! writables { macro_rules! writables {
[$($args:tt),+] => { [$($args:tt),+] => {
@ -55,7 +54,7 @@ macro_rules! writablesln {
}; };
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Eq)]
pub enum Writable<'a> { pub enum Writable<'a> {
String(&'a str), String(&'a str),
Path(&'a Path), Path(&'a Path),
@ -64,15 +63,15 @@ pub enum Writable<'a> {
// the lifetime of a lifetime // the lifetime of a lifetime
impl<'a> From<&'a str> for Writable<'a> { impl<'a> From<&'a str> for Writable<'a> {
fn from(s: &'a str) -> Writable<'a> { Writable::String(s) } fn from(s: &'a str) -> Self { Writable::String(s) }
} }
impl<'a> From<&'a Path> for Writable<'a> { impl<'a> From<&'a Path> for Writable<'a> {
fn from(p: &'a Path) -> Writable<'a> { Writable::Path(p) } fn from(p: &'a Path) -> Self { Writable::Path(p) }
} }
impl<'a> From<&'a OsStr> for Writable<'a> { impl<'a> From<&'a OsStr> for Writable<'a> {
fn from(p: &'a OsStr) -> Writable<'a> { Writable::Path(p.as_ref()) } fn from(p: &'a OsStr) -> Self { Writable::Path(p.as_ref()) }
} }
fn generated_by() -> String { format!("Generated by fif {}", CLAP_LONG_VERSION.as_str()).into() } fn generated_by() -> String { format!("Generated by fif {}", CLAP_LONG_VERSION.as_str()).into() }
@ -90,7 +89,7 @@ pub fn smart_write<W: Write>(f: &mut W, writeables: &[Writable]) -> io::Result<(
} }
} }
} }
Writable::String(s) => write!(f, "{}", s)?, Writable::String(s) => write!(f, "{s}")?,
Writable::Path(path) => { Writable::Path(path) => {
if let Some(path_str) = path.to_str() { if let Some(path_str) = path.to_str() {
let escaped = escape(path_str); let escaped = escape(path_str);
@ -98,9 +97,9 @@ pub fn smart_write<W: Write>(f: &mut W, writeables: &[Writable]) -> io::Result<(
// the escaped string is the same as the input - this will occur for inputs like "file.txt" which don't // the escaped string is the same as the input - this will occur for inputs like "file.txt" which don't
// need to be escaped. however, it's Best Practice™ to escape such strings anyway, so we prefix/suffix the // need to be escaped. however, it's Best Practice™ to escape such strings anyway, so we prefix/suffix the
// escaped string with single quotes. // escaped string with single quotes.
write!(f, "'{}'", escaped)?; write!(f, "'{escaped}'")?;
} else { } else {
write!(f, "{}", escaped)?; write!(f, "{escaped}")?;
} }
} else { } else {
write!(f, "'")?; write!(f, "'")?;
@ -111,7 +110,7 @@ pub fn smart_write<W: Write>(f: &mut W, writeables: &[Writable]) -> io::Result<(
// f.write_all(&*path.as_os_str().encode_wide().collect::<Vec<u16>>())?; // f.write_all(&*path.as_os_str().encode_wide().collect::<Vec<u16>>())?;
write!(f, "{}", path.as_os_str().to_string_lossy())?; write!(f, "{}", path.as_os_str().to_string_lossy())?;
} else { } else {
f.write_all(&*path.as_os_str().as_bytes())?; f.write_all(path.as_os_str().as_bytes())?;
} }
} }
write!(f, "'")?; write!(f, "'")?;
@ -197,6 +196,7 @@ impl FormatSteps for Shell {
if let Ok(working_directory) = std::env::current_dir() { if let Ok(working_directory) = std::env::current_dir() {
smart_write(f, writablesln!["# Run from ", (working_directory.as_path())])?; smart_write(f, writablesln!["# Run from ", (working_directory.as_path())])?;
} }
write!(f, "# Happy with these changes? Run `fif --fix` from the same directory!")?;
smart_write(f, writablesln![Newline, "set -e", Newline]) smart_write(f, writablesln![Newline, "set -e", Newline])
} }
@ -253,6 +253,7 @@ impl FormatSteps for PowerShell {
if let Ok(working_directory) = std::env::current_dir() { if let Ok(working_directory) = std::env::current_dir() {
smart_write(f, writablesln!["<# Run from ", (working_directory.as_path()), " #>"])?; smart_write(f, writablesln!["<# Run from ", (working_directory.as_path()), " #>"])?;
} }
write!(f, "<# Happy with these changes? Run `fif --fix` from the same directory! #>")?;
smart_write(f, writables![Newline]) smart_write(f, writables![Newline])
} }
@ -287,7 +288,11 @@ impl FormatSteps for Text {
} }
fn header<W: Write>(&self, f: &mut W) -> io::Result<()> { fn header<W: Write>(&self, f: &mut W) -> io::Result<()> {
smart_write(f, writablesln![(generated_by().as_str()), Newline]) smart_write(f, writablesln![(generated_by().as_str()), Newline])?;
if let Ok(working_directory) = std::env::current_dir() {
smart_write(f, writablesln!["Run from ", (working_directory.as_path())])?;
}
write!(f, "Happy with these changes? Run `fif --fix` from the same directory!")
} }
fn footer<W: Write>(&self, f: &mut W) -> io::Result<()> { fn footer<W: Write>(&self, f: &mut W) -> io::Result<()> {

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian // SPDX-FileCopyrightText: 2021-2024 Lynnesbian
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
//! This library consists of all of the things fif needs to run. It only exists as a library to separate code, and to //! This library consists of all of the things fif needs to run. It only exists as a library to separate code, and to
@ -48,9 +48,9 @@ cfg_if! {
cfg_if! { cfg_if! {
if #[cfg(any(all(unix, feature = "infer-backend"), all(not(unix), not(feature = "xdg-mime-backend"))))] { if #[cfg(any(all(unix, feature = "infer-backend"), all(not(unix), not(feature = "xdg-mime-backend"))))] {
/// A [`Lazy`] holding an instance of [`mime_db::MimeDb`]. Initialised at program startup. /// A [`Lazy`] holding an instance of [`mime_db::MimeDb`]. Initialised at program startup.
pub static MIMEDB: Lazy<mime_db::InferDb> = Lazy::new(crate::mime_db::InferDb::init); pub static MIMEDB: Lazy<mime_db::InferDb> = Lazy::new(mime_db::InferDb::init);
} else { } else {
/// A [`Lazy`] holding an instance of [`mime_db::MimeDb`]. Initialised at program startup. /// A [`Lazy`] holding an instance of [`mime_db::MimeDb`]. Initialised at program startup.
pub static MIMEDB: Lazy<mime_db::XdgDb> = Lazy::new(crate::mime_db::XdgDb::init); pub static MIMEDB: Lazy<mime_db::XdgDb> = Lazy::new(mime_db::XdgDb::init);
} }
} }

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian // SPDX-FileCopyrightText: 2021-2024 Lynnesbian
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// fif - a command-line tool for detecting and optionally correcting files with incorrect extensions. // fif - a command-line tool for detecting and optionally correcting files with incorrect extensions.
@ -132,7 +132,7 @@ fn main() {
if args.fix { if args.fix {
fn ask(message: &str) -> bool { fn ask(message: &str) -> bool {
let mut buf = String::with_capacity(1); let mut buf = String::with_capacity(1);
print!("{} [y/N] ", message); print!("{message} [y/N] ");
// flush stdout to ensure message is displayed // flush stdout to ensure message is displayed
stdout().flush().expect("Failed to flush stdout"); stdout().flush().expect("Failed to flush stdout");
@ -163,10 +163,10 @@ fn main() {
// handles: --prompt never --overwrite // handles: --prompt never --overwrite
// user specified --prompt never in conjunction with --overwrite, so always rename // user specified --prompt never in conjunction with --overwrite, so always rename
true true
} else if prompt == Prompt::Error || ask(&*format!("Rename {:#?} to {:#?}?", &f.file, &rename_to)) { } else if prompt == Prompt::Error || ask(&format!("Rename {:#?} to {:#?}?", &f.file, &rename_to)) {
// handles: --prompt error --overwrite, --prompt always --overwrite [y] // handles: --prompt error --overwrite, --prompt always --overwrite [y]
// if the target exists, prompt before renaming; otherwise, just rename // if the target exists, prompt before renaming; otherwise, just rename
!rename_to.exists() || ask(&*format!("Destination {:#?} already exists, overwrite?", rename_to)) !rename_to.exists() || ask(&format!("Destination {rename_to:#?} already exists, overwrite?"))
} else { } else {
// handles: --prompt always --overwrite [n] // handles: --prompt always --overwrite [n]
// user was prompted and replied "no" // user was prompted and replied "no"
@ -182,7 +182,7 @@ fn main() {
loop { loop {
// until file is renamed successfully // until file is renamed successfully
match std::fs::rename(&f.file, &rename_to) { match std::fs::rename(&f.file, &rename_to) {
Ok(_) => { Ok(()) => {
info!("Renamed {:#?} -> {:#?}", f.file, rename_to); info!("Renamed {:#?} -> {:#?}", f.file, rename_to);
renamed += 1; renamed += 1;
break; break;
@ -191,7 +191,7 @@ fn main() {
warn!("Couldn't rename {:#?} to {:#?}: {:#?}", f.file, rename_to, e); warn!("Couldn't rename {:#?} to {:#?}: {:#?}", f.file, rename_to, e);
// if the user passed --prompt never, continue to the next file // if the user passed --prompt never, continue to the next file
// otherwise, prompt user to retry move, retrying until the rename succeeds or they respond "N" // otherwise, prompt user to retry move, retrying until the rename succeeds or they respond "N"
if prompt == Prompt::Never || !ask(&*format!("Error while renaming file: {:#?}. Try again?", e)) { if prompt == Prompt::Never || !ask(&format!("Error while renaming file: {e:#?}. Try again?")) {
failed += 1; failed += 1;
break; break;
} }

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian // SPDX-FileCopyrightText: 2021-2024 Lynnesbian
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
//! Backend-neutral Mime database abstraction. //! Backend-neutral Mime database abstraction.
@ -32,7 +32,7 @@ cfg_if! {
} }
fn open_document_check(buf: &[u8], kind: &str) -> bool { fn open_document_check(buf: &[u8], kind: &str) -> bool {
let mime = format!("application/vnd.oasis.opendocument.{}", kind); let mime = format!("application/vnd.oasis.opendocument.{kind}");
let mime = mime.as_bytes(); let mime = mime.as_bytes();
buf.len() > 38 + mime.len() && buf.starts_with(b"PK\x03\x04") && buf[38..mime.len() + 38] == mime[..] buf.len() > 38 + mime.len() && buf.starts_with(b"PK\x03\x04") && buf[38..mime.len() + 38] == mime[..]

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian // SPDX-FileCopyrightText: 2021-2024 Lynnesbian
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
//! Command line argument parsing logic and associated functions. //! Command line argument parsing logic and associated functions.
@ -7,7 +7,7 @@ use std::collections::BTreeSet;
use std::path::PathBuf; use std::path::PathBuf;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use clap::{ArgEnum, Parser}; use clap::{ArgAction, Parser, ValueEnum};
use crate::utils::{CLAP_LONG_VERSION, CLAP_VERSION}; use crate::utils::{CLAP_LONG_VERSION, CLAP_VERSION};
use crate::String as StringType; use crate::String as StringType;
@ -22,7 +22,7 @@ cfg_if! {
} }
} }
#[derive(ArgEnum, PartialEq, Debug, Copy, Clone)] #[derive(ValueEnum, Eq, PartialEq, Debug, Copy, Clone)]
/// The format to use when running fif without the `--fix` flag. Specified at runtime with the `-o`/`--output-format` /// The format to use when running fif without the `--fix` flag. Specified at runtime with the `-o`/`--output-format`
/// flag. /// flag.
pub enum OutputFormat { pub enum OutputFormat {
@ -39,7 +39,7 @@ pub enum OutputFormat {
Json, Json,
} }
#[derive(ArgEnum, PartialEq, Debug, Copy, Clone)] #[derive(ValueEnum, Eq, PartialEq, Debug, Copy, Clone)]
/// Specifies under what conditions the user should be prompted when running fif in `--fix` mode. Defaults to `Error`. /// Specifies under what conditions the user should be prompted when running fif in `--fix` mode. Defaults to `Error`.
/// Specified at runtime with the `-p`/`--prompt` flag. /// Specified at runtime with the `-p`/`--prompt` flag.
pub enum Prompt { pub enum Prompt {
@ -58,13 +58,12 @@ pub enum Prompt {
long_version = CLAP_LONG_VERSION.as_str(), long_version = CLAP_LONG_VERSION.as_str(),
author = option_env!("CARGO_PKG_AUTHORS").unwrap_or("Lynnesbian"), author = option_env!("CARGO_PKG_AUTHORS").unwrap_or("Lynnesbian"),
about = option_env!("CARGO_PKG_DESCRIPTION").unwrap_or("File Info Fixer"), about = option_env!("CARGO_PKG_DESCRIPTION").unwrap_or("File Info Fixer"),
before_help = "Copyright © 2021-2022 Lynnesbian under the GPL3 (or later) License.", before_help = "Copyright © 2021-2024 Lynnesbian under the GPL3 (or later) License.",
after_long_help = "Copyright © 2021-2022 Lynnesbian\n\ after_long_help = "Copyright © 2021-2024 Lynnesbian\n\
This program is free software: you can redistribute it and/or modify \ This program is free software: you can redistribute it and/or modify \
it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 \ it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 \
of the License, or (at your option) any later version.", of the License, or (at your option) any later version.",
max_term_width = 120, max_term_width = 120
setting(clap::AppSettings::DeriveDisplayOrder)
)] )]
/// [`Clap`]-derived struct used to parse command line arguments. /// [`Clap`]-derived struct used to parse command line arguments.
pub struct Parameters { pub struct Parameters {
@ -73,7 +72,7 @@ pub struct Parameters {
pub fix: bool, pub fix: bool,
/// Requires --fix. Should fif prompt you `Never`, only on `Error`s and overwrites, or `Always`? /// Requires --fix. Should fif prompt you `Never`, only on `Error`s and overwrites, or `Always`?
#[clap(short = 'p', long, arg_enum, requires = "fix", help_heading = "RENAMING")] #[clap(short = 'p', long, value_enum, requires = "fix", help_heading = "RENAMING")]
pub prompt: Option<Prompt>, pub prompt: Option<Prompt>,
/// Requires --fix. Allow overwriting files. Warning: When used in combination with `--prompt never`, fif will /// Requires --fix. Allow overwriting files. Warning: When used in combination with `--prompt never`, fif will
@ -86,8 +85,8 @@ pub struct Parameters {
/// Only examine files with these extensions. /// Only examine files with these extensions.
/// Multiple extensions can be specified by either using the flag multiple times (`-e jpg -e png -e gif`), or by /// Multiple extensions can be specified by either using the flag multiple times (`-e jpg -e png -e gif`), or by
/// separating them with commas (`-e jpg,png,gif`). /// separating them with commas (`-e jpg,png,gif`).
#[clap(short, long, use_value_delimiter = true, require_value_delimiter = true, value_name = "ext", takes_value = true, #[clap(short, long, use_value_delimiter = true, value_delimiter = ',', value_name = "ext", num_args(1),
validator = validate_exts, help_heading = "FILTERING")] value_parser = validate_exts, help_heading = "FILTERING")]
pub exts: Option<Vec<StringType>>, pub exts: Option<Vec<StringType>>,
/// Use these preset lists of extensions as the search filter (comma-separated list). /// Use these preset lists of extensions as the search filter (comma-separated list).
@ -96,9 +95,9 @@ pub struct Parameters {
#[clap( #[clap(
short = 'E', short = 'E',
long, long,
arg_enum, value_enum,
use_value_delimiter = true, use_value_delimiter = true,
require_value_delimiter = true, value_delimiter = ',',
value_name = "set", value_name = "set",
help_heading = "FILTERING" help_heading = "FILTERING"
)] )]
@ -106,7 +105,7 @@ pub struct Parameters {
/// Don't scan files with these extensions. /// Don't scan files with these extensions.
/// This option takes precedence over extensions specified with `-e` or `-E`. /// This option takes precedence over extensions specified with `-e` or `-E`.
#[clap(short = 'x', long, use_value_delimiter = true, require_value_delimiter = true, value_name = "ext", validator = #[clap(short = 'x', long, use_value_delimiter = true, value_delimiter = ',', value_name = "ext", value_parser =
validate_exts, help_heading = "FILTERING")] validate_exts, help_heading = "FILTERING")]
pub exclude: Option<Vec<StringType>>, pub exclude: Option<Vec<StringType>>,
@ -115,9 +114,9 @@ pub struct Parameters {
#[clap( #[clap(
short = 'X', short = 'X',
long, long,
arg_enum, value_enum,
use_value_delimiter = true, use_value_delimiter = true,
require_value_delimiter = true, value_delimiter = ',',
value_name = "set", value_name = "set",
help_heading = "FILTERING" help_heading = "FILTERING"
)] )]
@ -146,17 +145,17 @@ pub struct Parameters {
/// Output format to use. /// Output format to use.
/// By default, fif will output a PowerShell script on Windows, and a Bourne Shell script on other platforms. /// By default, fif will output a PowerShell script on Windows, and a Bourne Shell script on other platforms.
#[clap(short, long, default_value = DEFAULT_FORMAT, arg_enum, value_name = "format", help_heading = "OUTPUT")] #[clap(short, long, default_value = DEFAULT_FORMAT, value_enum, value_name = "format", help_heading = "OUTPUT")]
pub output_format: OutputFormat, pub output_format: OutputFormat,
/// Output verbosity. Each additional `-v` increases verbosity. /// Output verbosity. Each additional `-v` increases verbosity.
/// Can be overridden by FIF_LOG or RUST_LOG. /// Can be overridden by `FIF_LOG` or `RUST_LOG`.
#[clap(short, long, parse(from_occurrences), group = "verbosity", help_heading = "OUTPUT")] #[clap(short, long, action = ArgAction::Count, group = "verbosity", help_heading = "OUTPUT")]
pub verbose: u8, pub verbose: u8,
/// Output quietness. Each additional `-q` decreases verbosity. /// Output quietness. Each additional `-q` decreases verbosity.
/// Can be overridden by FIF_LOG or RUST_LOG. /// Can be overridden by `FIF_LOG` or `RUST_LOG`.
#[clap(short, long, parse(from_occurrences), group = "verbosity", help_heading = "OUTPUT")] #[clap(short, long, action = ArgAction::Count, group = "verbosity", help_heading = "OUTPUT")]
pub quiet: u8, pub quiet: u8,
/// Use canonical (absolute) paths in output. /// Use canonical (absolute) paths in output.
@ -167,7 +166,7 @@ pub struct Parameters {
pub canonical_paths: bool, pub canonical_paths: bool,
/// The directory to process. /// The directory to process.
#[clap(name = "DIR", default_value = ".", parse(from_os_str))] #[clap(name = "DIR", default_value = ".", value_parser)]
pub dir: PathBuf, pub dir: PathBuf,
#[cfg(feature = "multi-threaded")] #[cfg(feature = "multi-threaded")]
@ -181,7 +180,7 @@ pub struct Parameters {
/// Validation function for argument parsing that ensures passed-in extensions are lowercase, and that the user /// Validation function for argument parsing that ensures passed-in extensions are lowercase, and that the user
/// didn't supply an empty list. /// didn't supply an empty list.
fn validate_exts(exts: &str) -> Result<(), String> { fn validate_exts(exts: &str) -> Result<StringType, String> {
// TODO: i would much rather accept uppercase exts and convert them to lowercase than just rejecting lowercase exts... // TODO: i would much rather accept uppercase exts and convert them to lowercase than just rejecting lowercase exts...
if exts.is_empty() { if exts.is_empty() {
@ -191,11 +190,11 @@ fn validate_exts(exts: &str) -> Result<(), String> {
if exts.to_lowercase() != exts { if exts.to_lowercase() != exts {
return Err(String::from("Supplied extensions must be lowercase")); return Err(String::from("Supplied extensions must be lowercase"));
} }
Ok(()) Ok(exts.into())
} }
/// Further options relating to scanning. /// Further options relating to scanning.
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(Eq, PartialEq, Debug, Copy, Clone)]
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
pub struct ScanOpts { pub struct ScanOpts {
/// Whether hidden files and directories should be scanned. /// Whether hidden files and directories should be scanned.
@ -298,7 +297,7 @@ impl Parameters {
} }
/// Sets of extensions for use with [Parameter](crate::parameters::Parameters)'s `-E` flag. /// Sets of extensions for use with [Parameter](crate::parameters::Parameters)'s `-E` flag.
#[derive(ArgEnum, PartialEq, Debug, Copy, Clone)] #[derive(ValueEnum, Eq, PartialEq, Debug, Copy, Clone)]
pub enum ExtensionSet { pub enum ExtensionSet {
/// Extensions used for image file formats, such as `png`, `jpeg`, `webp`, etc. /// Extensions used for image file formats, such as `png`, `jpeg`, `webp`, etc.
Images, Images,

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian // SPDX-FileCopyrightText: 2021-2024 Lynnesbian
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
@ -102,11 +102,11 @@ fn simple_directory() {
set_current_dir(dir.path()).expect("Failed to change directory."); set_current_dir(dir.path()).expect("Failed to change directory.");
for (name, bytes) in &files { for (name, bytes) in &files {
let mut file = File::create(dir.path().join(name)).expect(&*format!("Failed to create file: {}", name)); let mut file = File::create(dir.path().join(name)).unwrap_or_else(|_| panic!("Failed to create file: {name}"));
file file
.write_all(bytes) .write_all(bytes)
.expect(&*format!("Failed to write to file: {}", name)); .unwrap_or_else(|_| panic!("Failed to write to file: {name}"));
drop(file); drop(file);
} }
@ -225,7 +225,7 @@ fn argument_parsing() {
#[test] #[test]
/// Ensure that `fif -e jpg dir` is interpreted as "scan for jpg files in dir" and not "scan for jpg and dir files" /// Ensure that `fif -e jpg dir` is interpreted as "scan for jpg files in dir" and not "scan for jpg and dir files"
fn positional_args() { fn positional_args() {
for flag in &["-x", "-e", "-X", "-E"] { for flag in ["-x", "-e", "-X", "-E"] {
assert_eq!( assert_eq!(
Parameters::parse_from(vec!["fif", flag, "images", "directory"]).dir, Parameters::parse_from(vec!["fif", flag, "images", "directory"]).dir,
PathBuf::from("directory") PathBuf::from("directory")
@ -279,12 +279,12 @@ fn exclude_set_overrides_include_set() {
.iter() .iter()
.chain(ExtensionSet::Video.extensions().iter()) .chain(ExtensionSet::Video.extensions().iter())
{ {
assert!(extensions.contains(&ext), "Extensions should contain {}!", ext); assert!(extensions.contains(&ext), "Extensions should contain {ext}!");
} }
// ensure all of images' extensions are excluded // ensure all of images' extensions are excluded
for ext in ExtensionSet::Images.extensions() { for ext in ExtensionSet::Images.extensions() {
assert!(!extensions.contains(&ext), "Extensions should not contain {}!", ext); assert!(!extensions.contains(&ext), "Extensions should not contain {ext}!");
} }
} }
@ -315,7 +315,7 @@ fn rejects_bad_args() {
for test in &tests { for test in &tests {
// first, try testing the flags against the Parameters struct... // first, try testing the flags against the Parameters struct...
assert!(Parameters::try_parse_from(test).is_err(), "Failed to reject {:?}", test); assert!(Parameters::try_parse_from(test).is_err(), "Failed to reject {test:?}");
// ...then, make sure it actually works against the binary // ...then, make sure it actually works against the binary
let mut cmd = Command::cargo_bin("fif").unwrap(); let mut cmd = Command::cargo_bin("fif").unwrap();
cmd.args(test).assert().failure(); cmd.args(test).assert().failure();
@ -357,9 +357,8 @@ fn check_version_output() {
let output = cmd.arg("-V").ok().unwrap().stdout; let output = cmd.arg("-V").ok().unwrap().stdout;
let output = String::from_utf8(output).unwrap(); let output = String::from_utf8(output).unwrap();
assert!( assert!(
Regex::new(r#"fif v([0-9]\.){2}[0-9]"#).unwrap().is_match(output.trim()), Regex::new(r"fif v([0-9]\.){2}[0-9]").unwrap().is_match(output.trim()),
"\"{}\" does not match the expected `-v` format!", "\"{output}\" does not match the expected `-v` format!"
output
); );
// test `--version` matches the format of "fif x.y.z (OS, example backend, commit #1234abc)" // test `--version` matches the format of "fif x.y.z (OS, example backend, commit #1234abc)"
@ -367,7 +366,7 @@ fn check_version_output() {
let output = cmd.arg("--version").ok().unwrap().stdout; let output = cmd.arg("--version").ok().unwrap().stdout;
let output = String::from_utf8(output).unwrap(); let output = String::from_utf8(output).unwrap();
assert!( assert!(
Regex::new(r#"fif v([0-9]\.){2}[0-9] \(.+, .+ backend, (unknown commit|commit #[[:xdigit:]]{7})\)"#) Regex::new(r"fif v([0-9]\.){2}[0-9] \(.+, .+ backend, (unknown commit|commit #[[:xdigit:]]{7})\)")
.unwrap() .unwrap()
.is_match(output.trim()), .is_match(output.trim()),
"\"{}\" does not match the expected `--version` format!", "\"{}\" does not match the expected `--version` format!",
@ -392,7 +391,7 @@ fn identify_random_bytes() {
} }
for (mime, count) in &results { for (mime, count) in &results {
println!("{}:\t{} counts", mime, count); println!("{mime}:\t{count} counts");
} }
println!("No type found:\t{} counts", 1000 - results.values().sum::<i32>()); println!("No type found:\t{} counts", 1000 - results.values().sum::<i32>());
} }
@ -429,9 +428,7 @@ fn outputs_move_commands() {
// the output should contain a command like "mv -i misnamed_file.png misnamed_file.jpg" // the output should contain a command like "mv -i misnamed_file.png misnamed_file.jpg"
assert!( assert!(
contents.contains("misnamed_file.jpg") && contents.contains("misnamed_file.png"), contents.contains("misnamed_file.jpg") && contents.contains("misnamed_file.png"),
"{} output doesn't contain move command!\n===\n{}", "{format} output doesn't contain move command!\n===\n{contents}"
format,
contents
); );
} }
} }
@ -465,8 +462,7 @@ fn test_json() {
// the output should contain the file's MIME type // the output should contain the file's MIME type
assert!( assert!(
contents.contains(IMAGE_JPEG.essence_str()), contents.contains(IMAGE_JPEG.essence_str()),
"JSON output doesn't contain move command!\n===\n{}", "JSON output doesn't contain move command!\n===\n{contents}"
contents
); );
} }
@ -483,8 +479,8 @@ fn media_contains_audio_video_images() {
.for_each(|ext| assert!(media_exts.contains(&ext))); .for_each(|ext| assert!(media_exts.contains(&ext)));
assert_eq!( assert_eq!(
Parameters::parse_from(&["fif", "-E", "media"]).extensions(), Parameters::parse_from(["fif", "-E", "media"]).extensions(),
Parameters::parse_from(&["fif", "-E", "audio,video,images"]).extensions() Parameters::parse_from(["fif", "-E", "audio,video,images"]).extensions()
); );
} }
@ -510,7 +506,7 @@ fn writables_is_correct() {
fn verbosity() { fn verbosity() {
use log::LevelFilter; use log::LevelFilter;
assert!( assert!(
Parameters::try_parse_from(&["fif", "-q", "-v"]).is_err(), Parameters::try_parse_from(["fif", "-q", "-v"]).is_err(),
"Failed to reject usage of both -q and -v!" "Failed to reject usage of both -q and -v!"
); );
@ -527,7 +523,7 @@ fn verbosity() {
]; ];
for (flags, level) in expected_results { for (flags, level) in expected_results {
assert_eq!(Parameters::parse_from(&["fif", flags]).get_verbosity(), level); assert_eq!(Parameters::parse_from(["fif", flags]).get_verbosity(), level);
} }
} }
@ -564,21 +560,3 @@ fn sort_findings() {
assert_eq!(findings.next().unwrap().file, Path::new("ccc")); assert_eq!(findings.next().unwrap().file, Path::new("ccc"));
assert_eq!(findings.next(), None); assert_eq!(findings.next(), None);
} }
#[test]
#[cfg(not(all(target_endian = "big", target_pointer_width = "32")))]
/// Ensures that [`SmartString`]s don't deviate from std's Strings
// remove this when (if) updating to smartstring v1.0!
fn validate_string_type() {
use std::string::String as StdString;
use fif::String as SmartString;
assert_eq!(SmartString::new(), StdString::new());
assert_eq!(SmartString::from("smol"), StdString::from("smol"));
assert_eq!(
SmartString::from("A long and therefore heap-allocated string"),
StdString::from("A long and therefore heap-allocated string")
);
smartstring::validate();
}

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian // SPDX-FileCopyrightText: 2021-2024 Lynnesbian
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
//! Various minor utilities. //! Various minor utilities.

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# SPDX-FileCopyrightText: 2021-2022 Lynnesbian # SPDX-FileCopyrightText: 2021-2024 Lynnesbian
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import re import re