From c638e8ab4b34ba64ddf6ec779a008d2ca73a1027 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Sun, 25 Sep 2022 23:42:01 -0700 Subject: [PATCH] Factor out a file argument type --- src/files.rs | 73 +++++++++++++++++++++++--------------------- src/main.rs | 83 ++++++++++++++++++++++++++------------------------ src/options.rs | 61 +++++++++++++++++++++++++++++-------- 3 files changed, 131 insertions(+), 86 deletions(-) diff --git a/src/files.rs b/src/files.rs index 74f703e36..8c1712293 100644 --- a/src/files.rs +++ b/src/files.rs @@ -10,13 +10,15 @@ use std::{ use rustc_hash::FxHashSet; use walkdir::WalkDir; +use crate::options::FileArgument; + pub fn read_files_or_die( - lhs_path: &Path, - rhs_path: &Path, + lhs_path: &FileArgument, + rhs_path: &FileArgument, missing_as_empty: bool, ) -> (Vec, Vec) { - let lhs_res = read_cli_path(lhs_path); - let rhs_res = read_cli_path(rhs_path); + let lhs_res = read_file_arg(lhs_path); + let rhs_res = read_file_arg(rhs_path); match (lhs_res, rhs_res) { // Both files exist, the happy case. @@ -44,47 +46,48 @@ pub fn read_files_or_die( /// Read a path provided in a CLI argument, handling /dev/null and - /// correctly. -fn read_cli_path(path: &Path) -> std::io::Result> { - // Treat /dev/null as an empty file, even on platforms like - // Windows where this path doesn't exist. Git uses /dev/null - // regardless of the platform. - if path == Path::new("/dev/null") { - return Ok(vec![]); - } - - if path == Path::new("-") { - let stdin = std::io::stdin(); - let mut handle = stdin.lock(); - - let mut bytes = vec![]; - handle.read_to_end(&mut bytes)?; - - return Ok(bytes); +fn read_file_arg(file_arg: &FileArgument) -> std::io::Result> { + match file_arg { + FileArgument::NamedPath(path) => fs::read(path), + FileArgument::Stdin => { + let stdin = std::io::stdin(); + let mut handle = stdin.lock(); + + let mut bytes = vec![]; + handle.read_to_end(&mut bytes)?; + Ok(bytes) + } + FileArgument::DevNull => { + // Treat /dev/null as an empty file, even on platforms like + // Windows where this path doesn't exist. Git uses /dev/null + // regardless of the platform. + Ok(vec![]) + } } - - fs::read(path) } /// Write a human-friendly description of `e` to stderr. -fn eprint_read_error(path: &Path, e: &std::io::Error) { +fn eprint_read_error(file_arg: &FileArgument, e: &std::io::Error) { match e.kind() { std::io::ErrorKind::NotFound => { - eprintln!("No such file: {}", path.display()); + eprintln!("No such file: {}", file_arg.display()); } std::io::ErrorKind::PermissionDenied => { - eprintln!("Permission denied when reading file: {}", path.display()); + eprintln!( + "Permission denied when reading file: {}", + file_arg.display() + ); } - _ => { - if path.is_dir() { + _ => match file_arg { + FileArgument::NamedPath(path) if path.is_dir() => { eprintln!("Expected a file, got a directory: {}", path.display()); - } else { - eprintln!( - "Could not read file: {} (error {:?})", - path.display(), - e.kind() - ); } - } + _ => eprintln!( + "Could not read file: {} (error {:?})", + file_arg.display(), + e.kind() + ), + }, }; } @@ -92,7 +95,7 @@ pub fn read_or_die(path: &Path) -> Vec { match fs::read(path) { Ok(src) => src, Err(e) => { - eprint_read_error(path, &e); + eprint_read_error(&FileArgument::NamedPath(path.to_path_buf()), &e); std::process::exit(1); } } diff --git a/src/main.rs b/src/main.rs index a7ffcd93c..415f48917 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,9 +52,10 @@ use parse::guess_language::{guess, language_name}; static GLOBAL: MiMalloc = MiMalloc; use diff::sliders::fix_all_sliders; -use options::{DisplayMode, DisplayOptions, Mode, DEFAULT_TAB_WIDTH}; +use options::{DisplayMode, DisplayOptions, FileArgument, Mode, DEFAULT_TAB_WIDTH}; use owo_colors::OwoColorize; use rayon::prelude::*; +use std::path::PathBuf; use std::{env, path::Path}; use summary::{DiffResult, FileContent}; use syntax::init_next_prev; @@ -160,45 +161,49 @@ fn main() { lhs_display_path, rhs_display_path, } => { - let lhs_path = Path::new(&lhs_path); - let rhs_path = Path::new(&rhs_path); - if lhs_path == rhs_path { + let is_dir = match &lhs_path { + FileArgument::NamedPath(path) => path.is_dir(), + _ => false, + }; + eprintln!( "warning: You've specified the same {} twice.\n", - if lhs_path.is_dir() { - "directory" - } else { - "file" - } + if is_dir { "directory" } else { "file" } ); } - if lhs_path.is_dir() && rhs_path.is_dir() { - diff_directories( - lhs_path, - rhs_path, - &display_options, - graph_limit, - byte_limit, - language_override, - ) - .for_each(|diff_result| { + match (&lhs_path, &rhs_path) { + ( + options::FileArgument::NamedPath(lhs_path), + options::FileArgument::NamedPath(rhs_path), + ) if lhs_path.is_dir() && rhs_path.is_dir() => { + diff_directories( + &lhs_path, + &rhs_path, + &display_options, + graph_limit, + byte_limit, + language_override, + ) + .for_each(|diff_result| { + print_diff_result(&display_options, &diff_result); + }); + } + _ => { + let diff_result = diff_file( + &lhs_display_path, + &rhs_display_path, + &lhs_path, + &rhs_path, + &display_options, + missing_as_empty, + graph_limit, + byte_limit, + language_override, + ); print_diff_result(&display_options, &diff_result); - }); - } else { - let diff_result = diff_file( - &lhs_display_path, - &rhs_display_path, - lhs_path, - rhs_path, - &display_options, - missing_as_empty, - graph_limit, - byte_limit, - language_override, - ); - print_diff_result(&display_options, &diff_result); + } } } }; @@ -218,8 +223,8 @@ fn replace_tabs(src: &str, tab_width: usize) -> String { fn diff_file( lhs_display_path: &str, rhs_display_path: &str, - lhs_path: &Path, - rhs_path: &Path, + lhs_path: &FileArgument, + rhs_path: &FileArgument, display_options: &DisplayOptions, missing_as_empty: bool, graph_limit: usize, @@ -398,8 +403,8 @@ fn diff_file_content( /// When more than one file is modified, the hg extdiff extension passes directory /// paths with the all the modified files. fn diff_directories<'a>( - lhs_dir: &'a Path, - rhs_dir: &'a Path, + lhs_dir: &'a PathBuf, + rhs_dir: &'a PathBuf, display_options: &DisplayOptions, graph_limit: usize, byte_limit: usize, @@ -421,8 +426,8 @@ fn diff_directories<'a>( diff_file( &rel_path.to_string_lossy(), &rel_path.to_string_lossy(), - &lhs_path, - &rhs_path, + &FileArgument::NamedPath(lhs_path), + &FileArgument::NamedPath(rhs_path), &display_options, true, graph_limit, diff --git a/src/options.rs b/src/options.rs index ea14d1ac1..3a683de41 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,6 +1,6 @@ //! CLI option parsing. -use std::{borrow::Borrow, env, ffi::OsString}; +use std::{borrow::Borrow, env, ffi::OsStr, path::PathBuf}; use atty::Stream; use clap::{crate_authors, crate_description, crate_version, Arg, Command}; @@ -178,6 +178,41 @@ pub enum DisplayMode { SideBySideShowBoth, } +#[derive(Eq, PartialEq, Debug)] +pub enum FileArgument { + NamedPath(std::path::PathBuf), + Stdin, + DevNull, +} + +impl FileArgument { + /// Return a `FileArgument` representing this command line + /// argument. + pub fn from_cli_argument(arg: &OsStr) -> Self { + if arg == "/dev/null" { + FileArgument::DevNull + } else if arg == "-" { + FileArgument::Stdin + } else { + FileArgument::NamedPath(PathBuf::from(arg)) + } + } + + /// Return a `FileArgument` that always represents a path that + /// exists. + pub fn from_path_argument(arg: &OsStr) -> Self { + FileArgument::NamedPath(PathBuf::from(arg)) + } + + pub fn display(&self) -> String { + match self { + FileArgument::NamedPath(path) => path.display().to_string(), + FileArgument::Stdin => "(stdin)".to_string(), + FileArgument::DevNull => "/dev/null".to_string(), + } + } +} + pub enum Mode { Diff { graph_limit: usize, @@ -187,11 +222,13 @@ pub enum Mode { language_override: Option, /// The path where we can read the LHS file. This is often a /// temporary file generated by source control. - lhs_path: OsString, + lhs_path: FileArgument, /// The path where we can read the RHS file. This is often a /// temporary file generated by source control. - rhs_path: OsString, - /// The path that we should display for the LHS file. + rhs_path: FileArgument, + /// The path that we should display for the LHS file. This is + /// usually the same as `lhs_path`, but the six argument form + /// of git-diff specifies this separately. lhs_display_path: String, /// The path that we should display for the RHS file. rhs_display_path: String, @@ -268,8 +305,8 @@ pub fn parse_args() -> Mode { [lhs_path, rhs_path] => ( lhs_path.to_owned(), rhs_path.to_owned(), - lhs_path.to_owned(), - rhs_path.to_owned(), + FileArgument::from_cli_argument(lhs_path), + FileArgument::from_cli_argument(rhs_path), false, ), [display_path, lhs_tmp_file, _lhs_hash, _lhs_mode, rhs_tmp_file, _rhs_hash, _rhs_mode] => { @@ -277,8 +314,8 @@ pub fn parse_args() -> Mode { ( display_path.to_owned(), display_path.to_owned(), - lhs_tmp_file.to_owned(), - rhs_tmp_file.to_owned(), + FileArgument::from_path_argument(lhs_tmp_file), + FileArgument::from_path_argument(rhs_tmp_file), true, ) } @@ -289,8 +326,8 @@ pub fn parse_args() -> Mode { ( old_name.to_owned(), new_name.to_owned(), - lhs_tmp_file.to_owned(), - rhs_tmp_file.to_owned(), + FileArgument::from_path_argument(lhs_tmp_file), + FileArgument::from_path_argument(rhs_tmp_file), true, ) } @@ -381,8 +418,8 @@ pub fn parse_args() -> Mode { display_options, missing_as_empty, language_override, - lhs_path: lhs_path.to_owned(), - rhs_path: rhs_path.to_owned(), + lhs_path, + rhs_path, lhs_display_path: lhs_display_path.to_string_lossy().to_string(), rhs_display_path: rhs_display_path.to_string_lossy().to_string(), }