Compare commits
No commits in common. "master" and "v0.5.1" have entirely different histories.
20 changed files with 508 additions and 787 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -21,4 +21,3 @@ cargo-timing*.html
|
||||||
*.sync-conflict*
|
*.sync-conflict*
|
||||||
.idea/sonarlint
|
.idea/sonarlint
|
||||||
.directory
|
.directory
|
||||||
*.tmp
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ build-base-stable:
|
||||||
|
|
||||||
build-base-msrv:
|
build-base-msrv:
|
||||||
extends: build-base-stable
|
extends: build-base-stable
|
||||||
image: "rust:1.64.0"
|
image: "rust:1.54.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.64.0"
|
image: "rust:1.54.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.64.0"
|
image: "rust:1.54.0"
|
||||||
needs: ["build-msrv"]
|
needs: ["build-msrv"]
|
||||||
cache:
|
cache:
|
||||||
key: msrv
|
key: msrv
|
||||||
|
|
64
.vscode/launch.json
vendored
64
.vscode/launch.json
vendored
|
@ -1,64 +0,0 @@
|
||||||
{
|
|
||||||
// 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}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
35
CHANGELOG.md
35
CHANGELOG.md
|
@ -4,39 +4,6 @@ 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
|
||||||
|
@ -49,7 +16,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.57.0**.
|
- The Minimum Supported Rust Version (MSRV) is now **1.54.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
|
||||||
|
|
865
Cargo.lock
generated
865
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
56
Cargo.toml
56
Cargo.toml
|
@ -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.7.2"
|
version = "0.5.1"
|
||||||
authors = ["Lynnesbian <lynne@bune.city>"]
|
authors = ["Lynnesbian <lynne@bune.city>"]
|
||||||
edition = "2021"
|
edition = "2018"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
rust-version = "1.64.0" # clap 4.1 requires >=1.64.0
|
rust-version = "1.54.0" # clap 3 requires >=1.54.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,55 +23,57 @@ xdg-mime-backend = ["xdg-mime"]
|
||||||
json = ["serde", "serde_json"]
|
json = ["serde", "serde_json"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
walkdir = "2.4.0"
|
walkdir = "~2.3.2"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
mime_guess = { package = "new_mime_guess", version = "4.0.0" }
|
mime_guess = { package = "new_mime_guess", features = ["phf-map"], 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.13.0"
|
itertools = "0.10.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 }
|
||||||
parking_lot = "0.12.0"
|
|
||||||
smartstring = "1"
|
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
|
parking_lot = "0.12.0"
|
||||||
# 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.4.0", optional = true }
|
xdg-mime = { version = "0.3.3", optional = true }
|
||||||
infer = "=0.13.0" # 0.14.0 uses `let else`, which requires rust 1.65
|
infer = "0.7.0"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
xdg-mime = "0.4.0"
|
xdg-mime = "0.3.3"
|
||||||
infer = { version = "=0.13.0", optional = true }
|
infer = { version = "0.7.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 = "~4.3" #4.4 has some deps that can't be satisfied on our MSRV
|
version = "3.1.0"
|
||||||
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.10.0" # 0.11 depends on is_terminal_polyfill 1.70.1, which requires rust 1.70
|
version = "0.9.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["color"]
|
features = ["termcolor", "atty"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
assert_cmd = "=2.0.5" # higher versions than this have dependencies that require later rust versions
|
assert_cmd = "2.0.2"
|
||||||
predicates-tree = "=1.0.9"
|
regex = { version = "1.5.4", default-features = false, features = ["std"] }
|
||||||
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"
|
||||||
|
@ -84,4 +86,4 @@ opt-level = 3
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
msrv = "1.64.0"
|
msrv = "1.54.0"
|
||||||
|
|
|
@ -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.57.0-orange?logo=rust&style=flat-square)
|
[![Minimum Supported Rust Version](https://img.shields.io/badge/msrv-1.54.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,10 +232,6 @@ 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
|
||||||
|
|
||||||
|
|
6
build.rs
6
build.rs
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
// SPDX-FileCopyrightText: 2021-2022 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");
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
# SPDX-FileCopyrightText: 2021-2022 Lynnesbian
|
||||||
# SPDX-License-Identifier: CC0-1.0
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
@ -40,7 +40,6 @@ 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
|
||||||
|
|
||||||
|
@ -53,4 +52,3 @@ 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
|
|
||||||
|
|
|
@ -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.64.0"
|
msrv = "1.54.0"
|
||||||
|
|
88
src/files.rs
88
src/files.rs
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
// SPDX-FileCopyrightText: 2021-2022 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,38 +127,16 @@ pub fn scan_file(entry: &DirEntry, canonical_paths: bool) -> Result<Findings, Sc
|
||||||
Ok(Some(result)) => result,
|
Ok(Some(result)) => result,
|
||||||
};
|
};
|
||||||
|
|
||||||
// determine whether or not the file's current extension is valid
|
// set of known extensions for the given MIME type
|
||||||
let valid = if let Some(entry_ext) = path.extension() {
|
let known_exts = mime_extension_lookup(result.essence_str().into());
|
||||||
// discard invalid UTF-8 and convert to lowercase. all extensions in both backend's databases are lowercase
|
// file extension for this particular file
|
||||||
// ascii, so this assumption is fine.
|
let entry_ext = path.extension();
|
||||||
let entry_ext = entry_ext.to_string_lossy().to_lowercase();
|
|
||||||
|
|
||||||
// if the file has any of these extensions, it is probably either:
|
let valid = match known_exts {
|
||||||
// - a copy of another file renamed for backup purposes (e.g. a word processor might save by renaming "my.doc" to
|
// there is a known set of extensions for this MIME type, and the file has an extension
|
||||||
// "my.doc.bak", then creating "my.doc", leaving the backup for safekeeping), which shouldn't be renamed so as
|
Some(e) if entry_ext.is_some() => e.contains(&entry_ext.unwrap().to_string_lossy().to_lowercase().into()),
|
||||||
// not to break the backup program
|
// either this file has no extension, or there is no known set of extensions for this MIME type :(
|
||||||
// - a partially downloaded file, which shouldn't be renamed to avoid corrupting it and blocking the downloader
|
Some(_) | None => false,
|
||||||
// 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 {
|
||||||
|
@ -284,8 +262,9 @@ 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(_) | Ok(_) => break,
|
Err(_) => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +296,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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,47 +350,16 @@ 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" {
|
||||||
// both backends seem to be unable to consistently detect OOXML files, so they should be considered valid
|
// neither xdg-mime nor infer seem to be able to detect office XML files properly...
|
||||||
// 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![
|
vec![String::from("zip"), String::from("docx"), String::from("xlsx"), String::from("pptx")],
|
||||||
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, .scr, etc. files are given the same MIME type, and aren't really distinguishable from each other
|
// .dll, .exe, and .scr files are given the same MIME type... but you definitely don't want to rename one to the
|
||||||
// ... but you definitely don't want to rename one to the other!
|
// 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
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
// SPDX-FileCopyrightText: 2021-2022 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
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
// SPDX-FileCopyrightText: 2021-2022 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,6 +31,7 @@ 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),+] => {
|
||||||
|
@ -54,7 +55,7 @@ macro_rules! writablesln {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Writable<'a> {
|
pub enum Writable<'a> {
|
||||||
String(&'a str),
|
String(&'a str),
|
||||||
Path(&'a Path),
|
Path(&'a Path),
|
||||||
|
@ -63,15 +64,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) -> Self { Writable::String(s) }
|
fn from(s: &'a str) -> Writable<'a> { Writable::String(s) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a Path> for Writable<'a> {
|
impl<'a> From<&'a Path> for Writable<'a> {
|
||||||
fn from(p: &'a Path) -> Self { Writable::Path(p) }
|
fn from(p: &'a Path) -> Writable<'a> { Writable::Path(p) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a OsStr> for Writable<'a> {
|
impl<'a> From<&'a OsStr> for Writable<'a> {
|
||||||
fn from(p: &'a OsStr) -> Self { Writable::Path(p.as_ref()) }
|
fn from(p: &'a OsStr) -> Writable<'a> { 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() }
|
||||||
|
@ -89,7 +90,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);
|
||||||
|
@ -97,9 +98,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, "'")?;
|
||||||
|
@ -110,7 +111,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, "'")?;
|
||||||
|
@ -196,7 +197,6 @@ 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,7 +253,6 @@ 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])
|
||||||
}
|
}
|
||||||
|
@ -288,11 +287,7 @@ 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<()> {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
// SPDX-FileCopyrightText: 2021-2022 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(mime_db::InferDb::init);
|
pub static MIMEDB: Lazy<mime_db::InferDb> = Lazy::new(crate::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(mime_db::XdgDb::init);
|
pub static MIMEDB: Lazy<mime_db::XdgDb> = Lazy::new(crate::mime_db::XdgDb::init);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
// SPDX-FileCopyrightText: 2021-2022 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!("{message} [y/N] ");
|
print!("{} [y/N] ", message);
|
||||||
|
|
||||||
// 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 {rename_to:#?} already exists, overwrite?"))
|
!rename_to.exists() || ask(&*format!("Destination {:#?} already exists, overwrite?", rename_to))
|
||||||
} 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: {e:#?}. Try again?")) {
|
if prompt == Prompt::Never || !ask(&*format!("Error while renaming file: {:#?}. Try again?", e)) {
|
||||||
failed += 1;
|
failed += 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
// SPDX-FileCopyrightText: 2021-2022 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[..]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
// SPDX-FileCopyrightText: 2021-2022 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::{ArgAction, Parser, ValueEnum};
|
use clap::{ArgEnum, Parser};
|
||||||
|
|
||||||
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(ValueEnum, Eq, PartialEq, Debug, Copy, Clone)]
|
#[derive(ArgEnum, 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(ValueEnum, Eq, PartialEq, Debug, Copy, Clone)]
|
#[derive(ArgEnum, 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,12 +58,13 @@ 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-2024 Lynnesbian under the GPL3 (or later) License.",
|
before_help = "Copyright © 2021-2022 Lynnesbian under the GPL3 (or later) License.",
|
||||||
after_long_help = "Copyright © 2021-2024 Lynnesbian\n\
|
after_long_help = "Copyright © 2021-2022 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 {
|
||||||
|
@ -72,7 +73,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, value_enum, requires = "fix", help_heading = "RENAMING")]
|
#[clap(short = 'p', long, arg_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
|
||||||
|
@ -85,8 +86,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, value_delimiter = ',', value_name = "ext", num_args(1),
|
#[clap(short, long, use_value_delimiter = true, require_value_delimiter = true, value_name = "ext", takes_value = true,
|
||||||
value_parser = validate_exts, help_heading = "FILTERING")]
|
validator = 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).
|
||||||
|
@ -95,9 +96,9 @@ pub struct Parameters {
|
||||||
#[clap(
|
#[clap(
|
||||||
short = 'E',
|
short = 'E',
|
||||||
long,
|
long,
|
||||||
value_enum,
|
arg_enum,
|
||||||
use_value_delimiter = true,
|
use_value_delimiter = true,
|
||||||
value_delimiter = ',',
|
require_value_delimiter = true,
|
||||||
value_name = "set",
|
value_name = "set",
|
||||||
help_heading = "FILTERING"
|
help_heading = "FILTERING"
|
||||||
)]
|
)]
|
||||||
|
@ -105,7 +106,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, value_delimiter = ',', value_name = "ext", value_parser =
|
#[clap(short = 'x', long, use_value_delimiter = true, require_value_delimiter = true, value_name = "ext", validator =
|
||||||
validate_exts, help_heading = "FILTERING")]
|
validate_exts, help_heading = "FILTERING")]
|
||||||
pub exclude: Option<Vec<StringType>>,
|
pub exclude: Option<Vec<StringType>>,
|
||||||
|
|
||||||
|
@ -114,9 +115,9 @@ pub struct Parameters {
|
||||||
#[clap(
|
#[clap(
|
||||||
short = 'X',
|
short = 'X',
|
||||||
long,
|
long,
|
||||||
value_enum,
|
arg_enum,
|
||||||
use_value_delimiter = true,
|
use_value_delimiter = true,
|
||||||
value_delimiter = ',',
|
require_value_delimiter = true,
|
||||||
value_name = "set",
|
value_name = "set",
|
||||||
help_heading = "FILTERING"
|
help_heading = "FILTERING"
|
||||||
)]
|
)]
|
||||||
|
@ -145,17 +146,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, value_enum, value_name = "format", help_heading = "OUTPUT")]
|
#[clap(short, long, default_value = DEFAULT_FORMAT, arg_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, action = ArgAction::Count, group = "verbosity", help_heading = "OUTPUT")]
|
#[clap(short, long, parse(from_occurrences), 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, action = ArgAction::Count, group = "verbosity", help_heading = "OUTPUT")]
|
#[clap(short, long, parse(from_occurrences), group = "verbosity", help_heading = "OUTPUT")]
|
||||||
pub quiet: u8,
|
pub quiet: u8,
|
||||||
|
|
||||||
/// Use canonical (absolute) paths in output.
|
/// Use canonical (absolute) paths in output.
|
||||||
|
@ -166,7 +167,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 = ".", value_parser)]
|
#[clap(name = "DIR", default_value = ".", parse(from_os_str))]
|
||||||
pub dir: PathBuf,
|
pub dir: PathBuf,
|
||||||
|
|
||||||
#[cfg(feature = "multi-threaded")]
|
#[cfg(feature = "multi-threaded")]
|
||||||
|
@ -180,7 +181,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<StringType, String> {
|
fn validate_exts(exts: &str) -> Result<(), 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() {
|
||||||
|
@ -190,11 +191,11 @@ fn validate_exts(exts: &str) -> Result<StringType, 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(exts.into())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Further options relating to scanning.
|
/// Further options relating to scanning.
|
||||||
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
|
#[derive(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.
|
||||||
|
@ -297,7 +298,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(ValueEnum, Eq, PartialEq, Debug, Copy, Clone)]
|
#[derive(ArgEnum, 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,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
// SPDX-FileCopyrightText: 2021-2022 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)).unwrap_or_else(|_| panic!("Failed to create file: {name}"));
|
let mut file = File::create(dir.path().join(name)).expect(&*format!("Failed to create file: {}", name));
|
||||||
|
|
||||||
file
|
file
|
||||||
.write_all(bytes)
|
.write_all(bytes)
|
||||||
.unwrap_or_else(|_| panic!("Failed to write to file: {name}"));
|
.expect(&*format!("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,8 +357,9 @@ 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()),
|
||||||
"\"{output}\" does not match the expected `-v` format!"
|
"\"{}\" 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)"
|
||||||
|
@ -366,7 +367,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!",
|
||||||
|
@ -391,7 +392,7 @@ fn identify_random_bytes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (mime, count) in &results {
|
for (mime, count) in &results {
|
||||||
println!("{mime}:\t{count} counts");
|
println!("{}:\t{} counts", mime, count);
|
||||||
}
|
}
|
||||||
println!("No type found:\t{} counts", 1000 - results.values().sum::<i32>());
|
println!("No type found:\t{} counts", 1000 - results.values().sum::<i32>());
|
||||||
}
|
}
|
||||||
|
@ -428,7 +429,9 @@ 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"),
|
||||||
"{format} output doesn't contain move command!\n===\n{contents}"
|
"{} output doesn't contain move command!\n===\n{}",
|
||||||
|
format,
|
||||||
|
contents
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -462,7 +465,8 @@ 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{contents}"
|
"JSON output doesn't contain move command!\n===\n{}",
|
||||||
|
contents
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,8 +483,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()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,7 +510,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!"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -523,7 +527,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -560,3 +564,21 @@ 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();
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
//! Various minor utilities.
|
//! Various minor utilities.
|
||||||
|
|
2
test.py
2
test.py
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2021-2024 Lynnesbian
|
# SPDX-FileCopyrightText: 2021-2022 Lynnesbian
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
Loading…
Reference in a new issue