mirror of
https://github.com/uutils/diffutils.git
synced 2026-06-10 15:48:59 -07:00
Make error message consistent with GNU diff's implementation when failing to read input file(s)
This commit is contained in:
+29
-4
@@ -4,6 +4,7 @@
|
||||
// files that was distributed with this source code.
|
||||
|
||||
use crate::params::{parse_params, Format};
|
||||
use regex::Regex;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
@@ -18,6 +19,22 @@ mod params;
|
||||
mod unified_diff;
|
||||
mod utils;
|
||||
|
||||
fn report_failure_to_read_input_file(
|
||||
executable: &OsString,
|
||||
filepath: &OsString,
|
||||
error: &std::io::Error,
|
||||
) {
|
||||
// std::io::Error's display trait outputs "{detail} (os error {code})"
|
||||
// but we want only the {detail} (error string) part
|
||||
let error_code_re = Regex::new(r"\ \(os\ error\ \d+\)$").unwrap();
|
||||
eprintln!(
|
||||
"{}: {}: {}",
|
||||
executable.to_string_lossy(),
|
||||
filepath.to_string_lossy(),
|
||||
error_code_re.replace(error.to_string().as_str(), ""),
|
||||
);
|
||||
}
|
||||
|
||||
// Exit codes are documented at
|
||||
// https://www.gnu.org/software/diffutils/manual/html_node/Invoking-diff.html.
|
||||
// An exit status of 0 means no differences were found,
|
||||
@@ -45,6 +62,7 @@ fn main() -> ExitCode {
|
||||
maybe_report_identical_files();
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
||||
// read files
|
||||
fn read_file_contents(filepath: &OsString) -> io::Result<Vec<u8>> {
|
||||
if filepath == "-" {
|
||||
@@ -54,20 +72,27 @@ fn main() -> ExitCode {
|
||||
fs::read(filepath)
|
||||
}
|
||||
}
|
||||
let mut io_error = false;
|
||||
let from_content = match read_file_contents(¶ms.from) {
|
||||
Ok(from_content) => from_content,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read from-file: {e}");
|
||||
return ExitCode::from(2);
|
||||
report_failure_to_read_input_file(¶ms.executable, ¶ms.from, &e);
|
||||
io_error = true;
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
let to_content = match read_file_contents(¶ms.to) {
|
||||
Ok(to_content) => to_content,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read to-file: {e}");
|
||||
return ExitCode::from(2);
|
||||
report_failure_to_read_input_file(¶ms.executable, ¶ms.to, &e);
|
||||
io_error = true;
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
if io_error {
|
||||
return ExitCode::from(2);
|
||||
}
|
||||
|
||||
// run diff
|
||||
let result: Vec<u8> = match params.format {
|
||||
Format::Normal => normal_diff::diff(&from_content, &to_content, ¶ms),
|
||||
|
||||
+49
-6
@@ -14,6 +14,7 @@ pub enum Format {
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Params {
|
||||
pub executable: OsString,
|
||||
pub from: OsString,
|
||||
pub to: OsString,
|
||||
pub format: Format,
|
||||
@@ -27,6 +28,7 @@ pub struct Params {
|
||||
impl Default for Params {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
executable: OsString::default(),
|
||||
from: OsString::default(),
|
||||
to: OsString::default(),
|
||||
format: Format::default(),
|
||||
@@ -43,10 +45,13 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
|
||||
let mut opts = opts.into_iter().peekable();
|
||||
// parse CLI
|
||||
|
||||
let Some(exe) = opts.next() else {
|
||||
let Some(executable) = opts.next() else {
|
||||
return Err("Usage: <exe> <from> <to>".to_string());
|
||||
};
|
||||
let mut params = Params::default();
|
||||
let mut params = Params {
|
||||
executable,
|
||||
..Default::default()
|
||||
};
|
||||
let mut from = None;
|
||||
let mut to = None;
|
||||
let mut format = None;
|
||||
@@ -63,7 +68,10 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
|
||||
} else if to.is_none() {
|
||||
to = Some(param);
|
||||
} else {
|
||||
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
|
||||
return Err(format!(
|
||||
"Usage: {} <from> <to>",
|
||||
params.executable.to_string_lossy()
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -155,7 +163,10 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
|
||||
} else if to.is_none() {
|
||||
to = Some(param);
|
||||
} else {
|
||||
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
|
||||
return Err(format!(
|
||||
"Usage: {} <from> <to>",
|
||||
params.executable.to_string_lossy()
|
||||
));
|
||||
}
|
||||
}
|
||||
params.from = if let Some(from) = from {
|
||||
@@ -163,14 +174,20 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
|
||||
} else if let Some(param) = opts.next() {
|
||||
param
|
||||
} else {
|
||||
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
|
||||
return Err(format!(
|
||||
"Usage: {} <from> <to>",
|
||||
params.executable.to_string_lossy()
|
||||
));
|
||||
};
|
||||
params.to = if let Some(to) = to {
|
||||
to
|
||||
} else if let Some(param) = opts.next() {
|
||||
param
|
||||
} else {
|
||||
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
|
||||
return Err(format!(
|
||||
"Usage: {} <from> <to>",
|
||||
params.executable.to_string_lossy()
|
||||
));
|
||||
};
|
||||
|
||||
// diff DIRECTORY FILE => diff DIRECTORY/FILE FILE
|
||||
@@ -301,6 +318,7 @@ mod tests {
|
||||
fn basics() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
..Default::default()
|
||||
@@ -309,6 +327,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
..Default::default()
|
||||
@@ -325,6 +344,7 @@ mod tests {
|
||||
for arg in ["-e", "--ed"] {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Ed,
|
||||
@@ -342,6 +362,7 @@ mod tests {
|
||||
params.extend(["foo", "bar"]);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Context,
|
||||
@@ -362,6 +383,7 @@ mod tests {
|
||||
params.extend(["foo", "bar"]);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Context,
|
||||
@@ -399,6 +421,7 @@ mod tests {
|
||||
params.extend(["foo", "bar"]);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Unified,
|
||||
@@ -419,6 +442,7 @@ mod tests {
|
||||
params.extend(["foo", "bar"]);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Unified,
|
||||
@@ -452,6 +476,7 @@ mod tests {
|
||||
fn context_count() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Unified,
|
||||
@@ -466,6 +491,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Unified,
|
||||
@@ -480,6 +506,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Unified,
|
||||
@@ -494,6 +521,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Context,
|
||||
@@ -511,6 +539,7 @@ mod tests {
|
||||
fn report_identical_files() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
..Default::default()
|
||||
@@ -519,6 +548,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
report_identical_files: true,
|
||||
@@ -528,6 +558,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
report_identical_files: true,
|
||||
@@ -549,6 +580,7 @@ mod tests {
|
||||
fn brief() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
..Default::default()
|
||||
@@ -557,6 +589,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
brief: true,
|
||||
@@ -566,6 +599,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
brief: true,
|
||||
@@ -582,6 +616,7 @@ mod tests {
|
||||
fn expand_tabs() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
..Default::default()
|
||||
@@ -591,6 +626,7 @@ mod tests {
|
||||
for option in ["-t", "--expand-tabs"] {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
expand_tabs: true,
|
||||
@@ -608,6 +644,7 @@ mod tests {
|
||||
fn tabsize() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
..Default::default()
|
||||
@@ -616,6 +653,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
tabsize: 0,
|
||||
@@ -629,6 +667,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
tabsize: 42,
|
||||
@@ -686,6 +725,7 @@ mod tests {
|
||||
fn double_dash() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("-g"),
|
||||
to: os("-h"),
|
||||
..Default::default()
|
||||
@@ -697,6 +737,7 @@ mod tests {
|
||||
fn default_to_stdin() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("-"),
|
||||
..Default::default()
|
||||
@@ -705,6 +746,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("-"),
|
||||
to: os("bar"),
|
||||
..Default::default()
|
||||
@@ -713,6 +755,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
executable: os("diff"),
|
||||
from: os("-"),
|
||||
to: os("-"),
|
||||
..Default::default()
|
||||
|
||||
+15
-6
@@ -36,21 +36,30 @@ fn cannot_read_files() -> Result<(), Box<dyn std::error::Error>> {
|
||||
cmd.assert()
|
||||
.code(predicate::eq(2))
|
||||
.failure()
|
||||
.stderr(predicate::str::starts_with("Failed to read from-file"));
|
||||
.stderr(predicate::str::ends_with(format!(
|
||||
": {}: No such file or directory\n",
|
||||
&nopath.as_os_str().to_string_lossy()
|
||||
)));
|
||||
|
||||
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||
cmd.arg(file.path()).arg(&nopath);
|
||||
cmd.assert()
|
||||
.code(predicate::eq(2))
|
||||
.failure()
|
||||
.stderr(predicate::str::starts_with("Failed to read to-file"));
|
||||
.stderr(predicate::str::ends_with(format!(
|
||||
": {}: No such file or directory\n",
|
||||
&nopath.as_os_str().to_string_lossy()
|
||||
)));
|
||||
|
||||
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||
cmd.arg(&nopath).arg(&nopath);
|
||||
cmd.assert()
|
||||
.code(predicate::eq(2))
|
||||
.failure()
|
||||
.stderr(predicate::str::starts_with("Failed to read from-file"));
|
||||
cmd.assert().code(predicate::eq(2)).failure().stderr(
|
||||
predicate::str::contains(format!(
|
||||
": {}: No such file or directory\n",
|
||||
&nopath.as_os_str().to_string_lossy()
|
||||
))
|
||||
.count(2),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user