writables!, windows newlines, sorted changelog, always quote paths
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
6a7577d874
commit
d6974b4959
5 changed files with 143 additions and 70 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -3,15 +3,22 @@ Dates are given in YYYY-MM-DD format.
|
|||
|
||||
## v0.2
|
||||
### v0.2.13 (2021-???)
|
||||
#### Features
|
||||
- Added system extension set (`.dll`, `.so`, `.exe`...)
|
||||
- Output is now sorted: Files that couldn't be read, then files with no known mimetype, then files with no known
|
||||
extensions, then files with the wrong extension
|
||||
#### Bugfixes
|
||||
- Fixed some bad formatting in PowerShell output
|
||||
- Always quote file paths in output, even when not necessary - This makes output more portable and less likely to break
|
||||
in future, or if [`snailquote`] misses something
|
||||
#### Other
|
||||
- Added Apple iWork document formats to documents extension set
|
||||
- Cleaned up and properly documented tests
|
||||
- Renamed `Script` (in `formats.rs`) to `Shell`, in line with renaming in `parameters.rs`
|
||||
- Added .rpa (Ren'Py archive) support to infer backend
|
||||
- Added system extension set
|
||||
- [`xdg-mime`] no longer uses git version
|
||||
- Output is sorted: Files that couldn't be read, then files with no known mimetype, then files with no known extensions,
|
||||
then files with the wrong extension
|
||||
- Fixed some bad formatting in PowerShell output
|
||||
- Output `\r\n` on Windows
|
||||
- Use a macro to generate `Writable` arrays, making the code a little bit cleaner and nicer to write
|
||||
|
||||
### v0.2.12 (2021-04-14)
|
||||
#### Features
|
||||
|
@ -88,7 +95,7 @@ Dates are given in YYYY-MM-DD format.
|
|||
- Automatically disable [`xdg-mime`] backend on Windows
|
||||
- Exit codes
|
||||
- Improved error handling
|
||||
- Retrieve extension sets from [`mime_guess`] rather than hardcoding them in
|
||||
- Retrieve extension sets from [`mime_guess`] rather than hardcoding them
|
||||
#### Bugfixes
|
||||
- Improved SVG detection
|
||||
#### Other
|
||||
|
@ -135,4 +142,5 @@ Initial commit!
|
|||
[`structopt`]: https://crates.io/crates/structopt
|
||||
[`clap`]: https://crates.io/crates/clap
|
||||
[`infer`]: https://crates.io/crates/infer
|
||||
[`mime_guess`]: https://crates.io/crates/mime_guess
|
||||
[`mime_guess`]: https://crates.io/crates/mime_guess
|
||||
[`snailquote`]: https://crates.io/crates/snailquote
|
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -184,9 +184,9 @@ checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193"
|
|||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.4.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3"
|
||||
checksum = "77b705829d1e87f762c2df6da140b26af5839e1033aa84aa5f56bb688e4e1bdb"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
@ -300,9 +300,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.5"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374"
|
||||
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags",
|
||||
|
@ -578,9 +578,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.69"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
|
||||
checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -61,7 +61,7 @@ default-features = false
|
|||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.2.0"
|
||||
fastrand = "1.4.0"
|
||||
fastrand = "1.4.1"
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
|
|
161
src/formats.rs
161
src/formats.rs
|
@ -6,19 +6,49 @@ use std::io::{self, Write};
|
|||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use snailquote::escape;
|
||||
|
||||
use crate::scan_error::ScanError;
|
||||
use crate::{Findings, BACKEND};
|
||||
use itertools::Itertools;
|
||||
|
||||
/// A macro for creating an array of `Writable`s without needing to pepper your code with `into()`s.
|
||||
/// # Usage
|
||||
/// ```
|
||||
/// let f = std::io::stdout();
|
||||
/// // Instead of...
|
||||
/// smart_write(f, &["hello".into(), Writable::Newline]);
|
||||
/// // ...just use:
|
||||
/// smart_write(f, writables!["hello", Newline]);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! writables {
|
||||
[$($args:tt),+] => {
|
||||
&[$(writables!(@do $args),)*]
|
||||
};
|
||||
|
||||
(@do Newline) => {
|
||||
$crate::formats::Writable::Newline
|
||||
};
|
||||
|
||||
(@do Space) => {
|
||||
$crate::formats::Writable::Space
|
||||
};
|
||||
|
||||
(@do $arg:expr) => {
|
||||
$arg.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The current version of fif, as defined in Cargo.toml.
|
||||
const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[doc(hidden)]
|
||||
type Entries<'a> = [Result<Findings<'a>, ScanError<'a>>];
|
||||
|
||||
enum Writable<'a> {
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Writable<'a> {
|
||||
String(&'a str),
|
||||
Path(&'a Path),
|
||||
Space,
|
||||
|
@ -44,25 +74,48 @@ impl<'a> From<&'a OsStr> for Writable<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn generated_by() -> String {
|
||||
format!("Generated by fif {} ({} backend)", VERSION.unwrap_or("???"), BACKEND)
|
||||
}
|
||||
|
||||
fn smart_write<W: Write>(f: &mut W, writeables: &[Writable]) -> io::Result<()> {
|
||||
// ehhhh
|
||||
for writeable in writeables {
|
||||
match writeable {
|
||||
Writable::Space => write!(f, " ")?,
|
||||
Writable::Newline => writeln!(f,)?,
|
||||
Writable::Newline => {
|
||||
cfg_if! {
|
||||
if #[cfg(windows)] {
|
||||
write!(f, "\r\n")?
|
||||
} else {
|
||||
writeln!(f,)?
|
||||
}
|
||||
}
|
||||
}
|
||||
Writable::String(s) => write!(f, "{}", s)?,
|
||||
Writable::Path(path) => {
|
||||
if let Some(string) = path.to_str() {
|
||||
write!(f, "{}", escape(string))?
|
||||
let escaped = escape(string);
|
||||
if escaped == string {
|
||||
// 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
|
||||
// escaped string with single quotes.
|
||||
write!(f, "'{}'", escaped)?
|
||||
} else {
|
||||
write!(f, "{}", escaped)?
|
||||
}
|
||||
} else {
|
||||
write!(f, "'")?;
|
||||
#[cfg(unix)]
|
||||
f.write_all(&*path.as_os_str().as_bytes())?;
|
||||
// TODO: implement bonked strings for windows
|
||||
// something like:
|
||||
// f.write_all(&*path.as_os_str().encode_wide().collect::<Vec<u16>>())?;
|
||||
#[cfg(windows)]
|
||||
write!(f, "{}", path.as_os_str().to_string_lossy())?;
|
||||
cfg_if! {
|
||||
if #[cfg(windows)] {
|
||||
// TODO: implement bonked strings for windows
|
||||
// something like:
|
||||
// f.write_all(&*path.as_os_str().encode_wide().collect::<Vec<u16>>())?;
|
||||
write!(f, "{}", path.as_os_str().to_string_lossy())?;
|
||||
} else {
|
||||
f.write_all(&*path.as_os_str().as_bytes())?;
|
||||
}
|
||||
}
|
||||
write!(f, "'")?
|
||||
}
|
||||
}
|
||||
|
@ -130,52 +183,38 @@ impl Format for Shell {
|
|||
}
|
||||
|
||||
fn rename<W: Write>(&self, f: &mut W, from: &Path, to: &Path) -> io::Result<()> {
|
||||
smart_write(
|
||||
f,
|
||||
&[
|
||||
"mv -v -i -- ".into(),
|
||||
from.into(),
|
||||
Writable::Space,
|
||||
to.into(),
|
||||
Writable::Newline,
|
||||
],
|
||||
)
|
||||
smart_write(f, writables!("mv -v -i -- ", from, Space, to, Newline))
|
||||
}
|
||||
|
||||
fn no_known_extension<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
||||
smart_write(
|
||||
f,
|
||||
&["echo No known extension for ".into(), path.into(), Writable::Newline],
|
||||
)
|
||||
smart_write(f, writables!["echo No known extension for ", path, Newline])
|
||||
}
|
||||
|
||||
fn unreadable<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
||||
smart_write(f, &["# Failed to read ".into(), path.into(), Writable::Newline])
|
||||
smart_write(f, writables!["# Failed to read", path, Newline])
|
||||
}
|
||||
|
||||
fn unknown_type<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
||||
smart_write(f, writables!["# Failed to detect mime type for ", path, Newline])
|
||||
}
|
||||
|
||||
fn header<W: Write>(&self, _: &Entries, f: &mut W) -> io::Result<()> {
|
||||
smart_write(
|
||||
f,
|
||||
&[
|
||||
"# Failed to detect mime type for ".into(),
|
||||
path.into(),
|
||||
Writable::Newline,
|
||||
writables![
|
||||
"#!/usr/bin/env sh",
|
||||
Newline,
|
||||
"# ",
|
||||
(generated_by().as_str()),
|
||||
Newline,
|
||||
"set -e",
|
||||
Newline
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn header<W: Write>(&self, _: &Entries, f: &mut W) -> io::Result<()> {
|
||||
writeln!(
|
||||
f,
|
||||
"#!/usr/bin/env sh\n# Generated by fif {} ({} backend)",
|
||||
VERSION.unwrap_or("???"),
|
||||
BACKEND
|
||||
)?;
|
||||
writeln!(f, "\nset -e\n")
|
||||
}
|
||||
|
||||
fn footer<W: Write>(&self, _: &Entries, f: &mut W) -> io::Result<()> {
|
||||
writeln!(f, "\necho 'Done.'")
|
||||
smart_write(f, writables![Newline, "echo 'Done.'", Newline])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,12 +234,12 @@ impl Format for PowerShell {
|
|||
// there doesn't seem to be a way to rename the file, prompting only if the target already exists.
|
||||
smart_write(
|
||||
f,
|
||||
&[
|
||||
"Rename-Item -Path ".into(),
|
||||
from.into(),
|
||||
" -NewName ".into(),
|
||||
to.file_name().unwrap().into(),
|
||||
Writable::Newline,
|
||||
writables![
|
||||
"Rename-Item -Path ",
|
||||
from,
|
||||
" -NewName ",
|
||||
(to.file_name().unwrap()),
|
||||
Newline
|
||||
],
|
||||
)
|
||||
}
|
||||
|
@ -208,10 +247,13 @@ impl Format for PowerShell {
|
|||
fn no_known_extension<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
||||
smart_write(
|
||||
f,
|
||||
&[
|
||||
"Write-Output @'\nNo known extension for ".into(),
|
||||
path.into(),
|
||||
"\n'@".into(),
|
||||
writables![
|
||||
"Write-Output @'",
|
||||
Newline,
|
||||
"No known extension for ",
|
||||
path,
|
||||
Newline,
|
||||
"'@"
|
||||
],
|
||||
)
|
||||
}
|
||||
|
@ -219,27 +261,32 @@ impl Format for PowerShell {
|
|||
fn unreadable<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
||||
smart_write(
|
||||
f,
|
||||
&["Write-Output @'\nFailed to read ".into(), path.into(), "\n'@".into()],
|
||||
writables!["Write-Output @'", Newline, "Failed to read ", path, Newline, "'@"],
|
||||
)
|
||||
}
|
||||
|
||||
fn unknown_type<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
||||
smart_write(
|
||||
f,
|
||||
&["<# Failed to detect mime type for ".into(), path.into(), "#>".into(), Writable::Newline],
|
||||
writables!["<# Failed to detect mime type for ", path, " #>", Newline],
|
||||
)
|
||||
}
|
||||
|
||||
fn header<W: Write>(&self, _: &Entries, f: &mut W) -> io::Result<()> {
|
||||
writeln!(
|
||||
smart_write(
|
||||
f,
|
||||
"#!/usr/bin/env pwsh\n# Generated by fif {} ({} backend)",
|
||||
VERSION.unwrap_or("???"),
|
||||
BACKEND
|
||||
writables![
|
||||
"#!/usr/bin/env pwsh",
|
||||
Newline,
|
||||
"<# ",
|
||||
(generated_by().as_str()),
|
||||
" #>",
|
||||
Newline
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn footer<W: Write>(&self, _: &Entries, f: &mut W) -> io::Result<()> {
|
||||
writeln!(f, "\nWrite-Output 'Done!'")
|
||||
smart_write(f, writables![Newline, "Write-Output 'Done!'", Newline])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -276,6 +276,7 @@ fn outputs_move_commands() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
/// Ensure that the Media extension set contains all (is a superset) of Audio, Video, and Images.
|
||||
fn media_contains_audio_video_images() {
|
||||
use crate::extension_set::ExtensionSet::{Audio, Images, Media, Videos};
|
||||
let media_exts = Media.extensions();
|
||||
|
@ -286,3 +287,20 @@ fn media_contains_audio_video_images() {
|
|||
.into_iter()
|
||||
.for_each(|ext| assert!(media_exts.contains(&ext)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Ensure that the `writables!` macro produces the output it should.
|
||||
fn writables_is_correct() {
|
||||
use crate::formats::Writable;
|
||||
use crate::writables;
|
||||
|
||||
assert_eq!(
|
||||
&[
|
||||
"henlo".into(),
|
||||
Path::new("henlo").into(),
|
||||
Writable::Newline,
|
||||
Writable::Space
|
||||
],
|
||||
writables!["henlo", (Path::new("henlo")), Newline, Space]
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue