// This file is part of the uutils diffutils package. // // For the full copyright and license information, please view the LICENSE-* // files that was distributed with this source code. use crate::params::{parse_params, Format}; use crate::utils::report_failure_to_read_input_file; use crate::{context_diff, ed_diff, normal_diff, side_diff, unified_diff}; use std::env::ArgsOs; use std::ffi::OsString; use std::fs; use std::io::{self, stdout, Read, Write}; use std::iter::Peekable; use std::process::{exit, ExitCode}; // 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, // 1 means some differences were found, // and 2 means trouble. pub fn main(opts: Peekable) -> ExitCode { let params = parse_params(opts).unwrap_or_else(|error| { eprintln!("{error}"); exit(2); }); if params.recursive { return recursive(¶ms).unwrap_or_else(|error| { eprintln!("diff: {error}"); ExitCode::from(2) }); } compare(¶ms) } /// Compare directory entries in lexical order, with GNU diff's recursive output. fn recursive(params: &crate::params::Params) -> io::Result { let from = std::path::Path::new(¶ms.from); let to = std::path::Path::new(¶ms.to); if !from.is_dir() || !to.is_dir() { if fs::read(from)? != fs::read(to)? { println!("diff -r {} {}", from.display(), to.display()); } return Ok(compare(params)); } let mut names = std::collections::BTreeSet::new(); for dir in [from, to] { for entry in fs::read_dir(dir)? { names.insert(entry?.file_name()); } } let mut status = ExitCode::SUCCESS; for name in names { let left = from.join(&name); let right = to.join(&name); let result = if !left.exists() || !right.exists() { let parent = if left.exists() { from } else { to }; println!("Only in {}: {}", parent.display(), name.to_string_lossy()); ExitCode::from(1) } else if left.is_dir() != right.is_dir() { println!("File {} is a {} while file {} is a {}", left.display(), if left.is_dir() { "directory" } else { "regular file" }, right.display(), if right.is_dir() { "directory" } else { "regular file" }); ExitCode::from(1) } else { let mut child = params.clone(); child.from = left.into_os_string(); child.to = right.into_os_string(); recursive(&child)? }; if result == ExitCode::from(2) { status = result; } else if result == ExitCode::from(1) && status == ExitCode::SUCCESS { status = result; } } Ok(status) } fn compare(params: &crate::params::Params) -> ExitCode { // if from and to are the same file, no need to perform any comparison let maybe_report_identical_files = || { if params.report_identical_files { println!( "Files {} and {} are identical", params.from.to_string_lossy(), params.to.to_string_lossy(), ); } }; if params.from == "-" && params.to == "-" || same_file::is_same_file(¶ms.from, ¶ms.to).unwrap_or(false) { maybe_report_identical_files(); return ExitCode::SUCCESS; } // read files fn read_file_contents(filepath: &OsString) -> io::Result> { if filepath == "-" { let mut content = Vec::new(); io::stdin().read_to_end(&mut content).and(Ok(content)) } else { fs::read(filepath) } } let mut io_error = false; let from_content = match read_file_contents(¶ms.from) { Ok(from_content) => from_content, Err(e) => { 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) => { 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 = match params.format { Format::Normal => normal_diff::diff(&from_content, &to_content, ¶ms), Format::Unified => unified_diff::diff(&from_content, &to_content, ¶ms), Format::Context => context_diff::diff(&from_content, &to_content, ¶ms), Format::Ed => ed_diff::diff(&from_content, &to_content, ¶ms).unwrap_or_else(|error| { eprintln!("{error}"); exit(2); }), Format::SideBySide => { let mut output = stdout().lock(); side_diff::diff(&from_content, &to_content, &mut output, ¶ms) } }; if params.brief && !result.is_empty() { println!( "Files {} and {} differ", params.from.to_string_lossy(), params.to.to_string_lossy() ); } else { io::stdout().write_all(&result).unwrap(); } if result.is_empty() { maybe_report_identical_files(); ExitCode::SUCCESS } else { ExitCode::from(1) } }