writables!, windows newlines, sorted changelog, always quote paths
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Lynne Megido 2021-04-26 20:19:58 +10:00
parent 6a7577d874
commit d6974b4959
Signed by: lynnesbian
GPG Key ID: F0A184B5213D9F90
5 changed files with 143 additions and 70 deletions

View File

@ -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
View File

@ -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",

View File

@ -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"

View File

@ -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])
}
}

View File

@ -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]
)
}