Skip to content

Commit 72c7802

Browse files
committed
Take utility name as first parameter on diffutils
This is in preparation for adding the other diffutils commands, cmp, diff3, sdiff. We use a similar strategy to uutils/coreutils, with the single binary acting as one of the supported tools if called through a symlink with the appropriate name. When using the multi-tool binary directly, the utility needds to be the first parameter.
1 parent 9103365 commit 72c7802

File tree

5 files changed

+296
-127
lines changed

5 files changed

+296
-127
lines changed

src/diff.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// This file is part of the uutils diffutils package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE-*
4+
// files that was distributed with this source code.
5+
6+
use crate::params::{parse_params, Format};
7+
use crate::utils::report_failure_to_read_input_file;
8+
use crate::{context_diff, ed_diff, normal_diff, unified_diff};
9+
use std::env::ArgsOs;
10+
use std::ffi::OsString;
11+
use std::fs;
12+
use std::io::{self, Read, Write};
13+
use std::iter::Peekable;
14+
use std::process::{exit, ExitCode};
15+
16+
// Exit codes are documented at
17+
// https://www.gnu.org/software/diffutils/manual/html_node/Invoking-diff.html.
18+
// An exit status of 0 means no differences were found,
19+
// 1 means some differences were found,
20+
// and 2 means trouble.
21+
pub(crate) fn main(opts: Peekable<ArgsOs>) -> ExitCode {
22+
let params = parse_params(opts).unwrap_or_else(|error| {
23+
eprintln!("{error}");
24+
exit(2);
25+
});
26+
// if from and to are the same file, no need to perform any comparison
27+
let maybe_report_identical_files = || {
28+
if params.report_identical_files {
29+
println!(
30+
"Files {} and {} are identical",
31+
params.from.to_string_lossy(),
32+
params.to.to_string_lossy(),
33+
);
34+
}
35+
};
36+
if params.from == "-" && params.to == "-"
37+
|| same_file::is_same_file(&params.from, &params.to).unwrap_or(false)
38+
{
39+
maybe_report_identical_files();
40+
return ExitCode::SUCCESS;
41+
}
42+
43+
// read files
44+
fn read_file_contents(filepath: &OsString) -> io::Result<Vec<u8>> {
45+
if filepath == "-" {
46+
let mut content = Vec::new();
47+
io::stdin().read_to_end(&mut content).and(Ok(content))
48+
} else {
49+
fs::read(filepath)
50+
}
51+
}
52+
let mut io_error = false;
53+
let from_content = match read_file_contents(&params.from) {
54+
Ok(from_content) => from_content,
55+
Err(e) => {
56+
report_failure_to_read_input_file(&params.executable, &params.from, &e);
57+
io_error = true;
58+
vec![]
59+
}
60+
};
61+
let to_content = match read_file_contents(&params.to) {
62+
Ok(to_content) => to_content,
63+
Err(e) => {
64+
report_failure_to_read_input_file(&params.executable, &params.to, &e);
65+
io_error = true;
66+
vec![]
67+
}
68+
};
69+
if io_error {
70+
return ExitCode::from(2);
71+
}
72+
73+
// run diff
74+
let result: Vec<u8> = match params.format {
75+
Format::Normal => normal_diff::diff(&from_content, &to_content, &params),
76+
Format::Unified => unified_diff::diff(&from_content, &to_content, &params),
77+
Format::Context => context_diff::diff(&from_content, &to_content, &params),
78+
Format::Ed => ed_diff::diff(&from_content, &to_content, &params).unwrap_or_else(|error| {
79+
eprintln!("{error}");
80+
exit(2);
81+
}),
82+
};
83+
if params.brief && !result.is_empty() {
84+
println!(
85+
"Files {} and {} differ",
86+
params.from.to_string_lossy(),
87+
params.to.to_string_lossy()
88+
);
89+
} else {
90+
io::stdout().write_all(&result).unwrap();
91+
}
92+
if result.is_empty() {
93+
maybe_report_identical_files();
94+
ExitCode::SUCCESS
95+
} else {
96+
ExitCode::from(1)
97+
}
98+
}

src/main.rs

Lines changed: 56 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -3,119 +3,77 @@
33
// For the full copyright and license information, please view the LICENSE-*
44
// files that was distributed with this source code.
55

6-
use crate::params::{parse_params, Format};
7-
use regex::Regex;
8-
use std::env;
9-
use std::ffi::OsString;
10-
use std::fs;
11-
use std::io::{self, Read, Write};
12-
use std::process::{exit, ExitCode};
6+
use std::{
7+
env::ArgsOs,
8+
ffi::OsString,
9+
iter::Peekable,
10+
path::{Path, PathBuf},
11+
process::ExitCode,
12+
};
1313

1414
mod context_diff;
15+
mod diff;
1516
mod ed_diff;
1617
mod macros;
1718
mod normal_diff;
1819
mod params;
1920
mod unified_diff;
2021
mod utils;
2122

22-
fn report_failure_to_read_input_file(
23-
executable: &OsString,
24-
filepath: &OsString,
25-
error: &std::io::Error,
26-
) {
27-
// std::io::Error's display trait outputs "{detail} (os error {code})"
28-
// but we want only the {detail} (error string) part
29-
let error_code_re = Regex::new(r"\ \(os\ error\ \d+\)$").unwrap();
30-
eprintln!(
31-
"{}: {}: {}",
32-
executable.to_string_lossy(),
33-
filepath.to_string_lossy(),
34-
error_code_re.replace(error.to_string().as_str(), ""),
35-
);
23+
/// # Panics
24+
/// Panics if the binary path cannot be determined
25+
fn binary_path(args: &mut Peekable<ArgsOs>) -> PathBuf {
26+
match args.peek() {
27+
Some(ref s) if !s.is_empty() => PathBuf::from(s),
28+
_ => std::env::current_exe().unwrap(),
29+
}
30+
}
31+
32+
fn name(binary_path: &Path) -> Option<&str> {
33+
binary_path.file_stem()?.to_str()
34+
}
35+
36+
const VERSION: &str = env!("CARGO_PKG_VERSION");
37+
38+
fn usage(name: &str) {
39+
println!("{name} {VERSION} (multi-call binary)\n");
40+
println!("Usage: {name} [function [arguments...]]\n");
41+
println!("Currently defined functions:\n");
42+
println!(" diff\n");
43+
}
44+
45+
fn second_arg_error(name: &str) -> ! {
46+
println!("Expected utility name as second argument, got nothing.");
47+
usage(name);
48+
std::process::exit(0);
3649
}
3750

38-
// Exit codes are documented at
39-
// https://www.gnu.org/software/diffutils/manual/html_node/Invoking-diff.html.
40-
// An exit status of 0 means no differences were found,
41-
// 1 means some differences were found,
42-
// and 2 means trouble.
4351
fn main() -> ExitCode {
44-
let opts = env::args_os();
45-
let params = parse_params(opts).unwrap_or_else(|error| {
46-
eprintln!("{error}");
47-
exit(2);
52+
let mut args = std::env::args_os().peekable();
53+
54+
let exe_path = binary_path(&mut args);
55+
let exe_name = name(&exe_path).unwrap_or_else(|| {
56+
usage("<unknown binary>");
57+
std::process::exit(1);
4858
});
49-
// if from and to are the same file, no need to perform any comparison
50-
let maybe_report_identical_files = || {
51-
if params.report_identical_files {
52-
println!(
53-
"Files {} and {} are identical",
54-
params.from.to_string_lossy(),
55-
params.to.to_string_lossy(),
56-
);
57-
}
58-
};
59-
if params.from == "-" && params.to == "-"
60-
|| same_file::is_same_file(&params.from, &params.to).unwrap_or(false)
61-
{
62-
maybe_report_identical_files();
63-
return ExitCode::SUCCESS;
64-
}
6559

66-
// read files
67-
fn read_file_contents(filepath: &OsString) -> io::Result<Vec<u8>> {
68-
if filepath == "-" {
69-
let mut content = Vec::new();
70-
io::stdin().read_to_end(&mut content).and(Ok(content))
71-
} else {
72-
fs::read(filepath)
73-
}
74-
}
75-
let mut io_error = false;
76-
let from_content = match read_file_contents(&params.from) {
77-
Ok(from_content) => from_content,
78-
Err(e) => {
79-
report_failure_to_read_input_file(&params.executable, &params.from, &e);
80-
io_error = true;
81-
vec![]
82-
}
83-
};
84-
let to_content = match read_file_contents(&params.to) {
85-
Ok(to_content) => to_content,
86-
Err(e) => {
87-
report_failure_to_read_input_file(&params.executable, &params.to, &e);
88-
io_error = true;
89-
vec![]
90-
}
91-
};
92-
if io_error {
93-
return ExitCode::from(2);
94-
}
60+
let util_name = if exe_name == "diffutils" {
61+
// Discard the item we peeked.
62+
let _ = args.next();
9563

96-
// run diff
97-
let result: Vec<u8> = match params.format {
98-
Format::Normal => normal_diff::diff(&from_content, &to_content, &params),
99-
Format::Unified => unified_diff::diff(&from_content, &to_content, &params),
100-
Format::Context => context_diff::diff(&from_content, &to_content, &params),
101-
Format::Ed => ed_diff::diff(&from_content, &to_content, &params).unwrap_or_else(|error| {
102-
eprintln!("{error}");
103-
exit(2);
104-
}),
105-
};
106-
if params.brief && !result.is_empty() {
107-
println!(
108-
"Files {} and {} differ",
109-
params.from.to_string_lossy(),
110-
params.to.to_string_lossy()
111-
);
112-
} else {
113-
io::stdout().write_all(&result).unwrap();
114-
}
115-
if result.is_empty() {
116-
maybe_report_identical_files();
117-
ExitCode::SUCCESS
64+
args.peek()
65+
.cloned()
66+
.unwrap_or_else(|| second_arg_error(exe_name))
11867
} else {
119-
ExitCode::from(1)
68+
OsString::from(exe_name)
69+
};
70+
71+
match util_name.to_str() {
72+
Some("diff") => diff::main(args),
73+
Some(name) => {
74+
usage(&format!("{}: utility not supported", name));
75+
ExitCode::from(1)
76+
}
77+
None => second_arg_error(exe_name),
12078
}
12179
}

0 commit comments

Comments
 (0)