Skip to content

Commit

Permalink
Merge pull request #27
Browse files Browse the repository at this point in the history
Fixes #26. As noted in #26, the colon in `input:output` can conflict with paths on Windows, such as `C:\Path`. This PR deprecates that syntax and introduces a new `--output` argument. The `--output` argument specifies a different path for saving the fixed files and must be paired with source paths. For example, `nerdfix fix -o output1 -o "" input1 input2` saves `input1` to `output1` and writes `input2` in place.
  • Loading branch information
loichyan authored Oct 2, 2024
2 parents d060f1a + 0680464 commit e7b0b83
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 28 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
36 changes: 22 additions & 14 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Outpath>,
/// Path(s) of files to check.
#[arg(value_name = V_SOURCE)]
source: Vec<Source>,
source: Vec<IoPath>,
},
/// Fuzzy search for an icon.
Search {},
Expand Down Expand Up @@ -170,7 +175,7 @@ impl FromStr for UserInput {
#[derive(Clone, Debug)]
pub enum IoPath {
Stdio,
Path(PathBuf),
Path(Utf8PathBuf),
}

impl FromStr for IoPath {
Expand All @@ -189,7 +194,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),
}
}
}
Expand Down Expand Up @@ -249,16 +254,19 @@ impl fmt::Display for OutputFormat {
}

#[derive(Clone, Debug)]
pub struct Source(pub IoPath, pub Option<IoPath>);
pub struct Outpath(pub Option<IoPath>);

impl FromStr for Source {
type Err = &'static str;
impl FromStr for Outpath {
type Err = <IoPath as FromStr>::Err;

fn from_str(s: &str) -> Result<Self, Self::Err> {
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<IoPath>);
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Send + Sync + std::error::Error>),
}
Expand Down
39 changes: 28 additions & 11 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = Source>,
paths: impl 'a + IntoIterator<Item = (IoPath, Option<IoPath>)>,
recursive: bool,
) -> impl 'a + Iterator<Item = error::Result<Source>> {
if !recursive {
Box::new(paths.into_iter().map(Ok)) as Box<dyn Iterator<Item = _>>
Box::new(paths.into_iter().map(|(i, o)| Source(i, o)).map(Ok))
as Box<dyn Iterator<Item = _>>
} 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);
}
Expand All @@ -58,7 +60,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))),
)
}
}
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand All @@ -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;
Expand Down
9 changes: 6 additions & 3 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ fn fix() {
"fix",
"--select-first",
"--write",
"tests/test-data.txt:-"
"-o-",
"tests/test-data.txt"
);
}

Expand All @@ -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"
);
}

Expand All @@ -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"
);
}

0 comments on commit e7b0b83

Please sign in to comment.