From b424be466079ddccfee1950e9f225d1e85bbe6e8 Mon Sep 17 00:00:00 2001 From: Loi Chyan Date: Wed, 2 Oct 2024 20:19:35 +0800 Subject: [PATCH 1/2] refactor: use `Utf8PathBuf` instead of `PathBuf` --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/cli.rs | 6 +++--- src/main.rs | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb13d70..98fd433 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,6 +181,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" + [[package]] name = "cc" version = "1.1.0" @@ -574,6 +580,7 @@ version = "0.4.1" dependencies = [ "assert_cmd", "bytesize", + "camino", "clap", "codespan-reporting", "content_inspector", diff --git a/Cargo.toml b/Cargo.toml index 3b05bc0..36d14af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ rust-version = "1.70" [dependencies] bytesize = "1.3" +camino = "1.1" clap = { version = ">=4.0, <4.5", features = ["derive"] } codespan-reporting = "0.11.1" content_inspector = "0.2.4" diff --git a/src/cli.rs b/src/cli.rs index ab5dc01..151bb6a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,11 +1,11 @@ //! Command line arguments parser. use std::io::BufReader; -use std::path::PathBuf; use std::str::FromStr; use std::{fmt, fs, io}; use bytesize::ByteSize; +use camino::Utf8PathBuf; use clap::{Parser, Subcommand, ValueEnum}; use shadow_rs::formatcp; use thisctx::IntoError; @@ -170,7 +170,7 @@ impl FromStr for UserInput { #[derive(Clone, Debug)] pub enum IoPath { Stdio, - Path(PathBuf), + Path(Utf8PathBuf), } impl FromStr for IoPath { @@ -189,7 +189,7 @@ impl fmt::Display for IoPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Stdio => f.write_str("STDIO"), - Self::Path(path) => path.display().fmt(f), + Self::Path(path) => path.fmt(f), } } } diff --git a/src/main.rs b/src/main.rs index e991913..a4ac93e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,7 +58,7 @@ fn walk<'a>( }) .transpose() }) - .map(|e| e.map(|path| Source(IoPath::Path(path), None))), + .map(|e| e.map(|path| Source(IoPath::Path(path.try_into().unwrap()), None))), ) } } From 0680464d80d410bc5f0a4ed65278607156d23161 Mon Sep 17 00:00:00 2001 From: Loi Chyan Date: Wed, 2 Oct 2024 19:37:30 +0800 Subject: [PATCH 2/2] fix(cli): deprecate `input:output` syntax --- src/cli.rs | 30 +++++++++++++++++++----------- src/error.rs | 2 ++ src/main.rs | 37 +++++++++++++++++++++++++++---------- tests/cli.rs | 9 ++++++--- 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 151bb6a..baab905 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -126,12 +126,17 @@ pub enum Command { /// Set the file size limit (0 to disable it). #[arg(long, value_name= V_SIZE, default_value = DEFAULT_SIZE)] size_limit: ByteSize, - /// Path tuple(s) of files to read from and write to. + /// Save fixed files to different paths. /// - /// Each tuple is an input path followed by an optional output path, - /// e.g. `nerdfix fix /input/as/ouput /read/from:/write/to`. + /// Each path should be paired with its corresponding source path. Use + /// empty strings to save output directly to the source path. For + /// example, `nerdfix fix -o output1 -o "" input1 input2` will save + /// `input1` to `output1` and save `input2` to its original path. + #[arg(short, long, value_name = V_PATH)] + output: Vec, + /// Path(s) of files to check. #[arg(value_name = V_SOURCE)] - source: Vec, + source: Vec, }, /// Fuzzy search for an icon. Search {}, @@ -249,16 +254,19 @@ impl fmt::Display for OutputFormat { } #[derive(Clone, Debug)] -pub struct Source(pub IoPath, pub Option); +pub struct Outpath(pub Option); -impl FromStr for Source { - type Err = &'static str; +impl FromStr for Outpath { + type Err = ::Err; fn from_str(s: &str) -> Result { - Ok(if let Some((input, output)) = s.split_once(':') { - Source(input.parse()?, Some(output.parse()?)) + if s.is_empty() { + Ok(Self(None)) } else { - Source(s.parse()?, None) - }) + s.parse().map(Some).map(Self) + } } } + +#[derive(Debug)] +pub struct Source(pub IoPath, pub Option); diff --git a/src/error.rs b/src/error.rs index 721f8ee..d828c8d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -52,6 +52,8 @@ pub enum Error { InvalidCodepoint, #[error("Operation was interrupted by the user")] Interrupted, + #[error("Number of output paths mismatch with source paths")] + OutputMismatched, #[error(transparent)] Any(Box), } diff --git a/src/main.rs b/src/main.rs index a4ac93e..d2c8343 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,29 +12,31 @@ mod runtime; shadow_rs::shadow!(shadow); use clap::Parser; -use cli::{Command, IoPath, Source}; -use prompt::YesOrNo; -use runtime::{CheckerContext, Runtime}; -use thisctx::WithContext; +use thisctx::{IntoError, WithContext}; use tracing::{error, info, warn, Level}; use tracing_subscriber::prelude::*; -use util::{LogStatus, ResultExt as _}; use walkdir::WalkDir; +use self::cli::{Command, IoPath, Source}; +use self::prompt::YesOrNo; +use self::runtime::{CheckerContext, Runtime}; +use self::util::{LogStatus, ResultExt as _}; + static ICONS: &str = include_str!("./icons.json"); static SUBSTITUTIONS: &str = include_str!("./substitutions.json"); fn walk<'a>( - paths: impl 'a + IntoIterator, + paths: impl 'a + IntoIterator)>, recursive: bool, ) -> impl 'a + Iterator> { if !recursive { - Box::new(paths.into_iter().map(Ok)) as Box> + Box::new(paths.into_iter().map(|(i, o)| Source(i, o)).map(Ok)) + as Box> } else { Box::new( paths .into_iter() - .flat_map(|Source(input, output)| { + .flat_map(|(input, output)| { if let Some(output) = output { warn!("Output path is ignored when `--recursive`: {}", output); } @@ -119,7 +121,7 @@ fn main_impl() -> error::Result<()> { size_limit: size_limit.as_u64(), ..Default::default() }; - for source in walk(source.into_iter().map(|p| Source(p, None)), recursive) { + for source in walk(source.into_iter().map(|p| (p, None)), recursive) { tri!({ let source = source?; rt.check(&mut context, &source.0, None) @@ -135,11 +137,23 @@ fn main_impl() -> error::Result<()> { recursive, include_binary, size_limit, + output, source, } => { if yes { warn!("`--yes` is deprecated, use `--write` instead"); } + if !output.is_empty() && output.len() != source.len() { + return Err(error::OutputMismatched.build()); + } + if cfg!(unix) { + // Colon in `C:\Path` should not be considered as separators. + for p in source.iter() { + if matches!(p, IoPath::Path(p) if p.as_str().contains(':')) { + warn!("`input:output` syntax is deprecated, use `--output` instead"); + } + } + } let rt = rt.build(); let mut context = CheckerContext { write, @@ -149,7 +163,10 @@ fn main_impl() -> error::Result<()> { ..Default::default() }; let mut buffer = String::new(); - for source in walk(source, recursive) { + for source in walk( + source.into_iter().zip(output.into_iter().map(|p| p.0)), + recursive, + ) { tri!({ let source = source?; let Source(input, output) = &source; diff --git a/tests/cli.rs b/tests/cli.rs index b9b332a..2fb682c 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -131,7 +131,8 @@ fn fix() { "fix", "--select-first", "--write", - "tests/test-data.txt:-" + "-o-", + "tests/test-data.txt" ); } @@ -144,7 +145,8 @@ fn fix_with_exact_subs() { "--write", "-i=src/icons.json", "-i=tests/test-substitutions.json", - "tests/test-data.txt:-" + "-o-", + "tests/test-data.txt" ); } @@ -157,6 +159,7 @@ fn fix_with_prefix_subs() { "--write", "-i=src/icons.json", "--sub=prefix:mdi-/md-", - "tests/test-data.txt:-" + "-o-", + "tests/test-data.txt" ); }