diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 270f5153a..3e8df57f7 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -6,6 +6,7 @@ // file that was distributed with this source code. use clap::App; +use clap::Arg; use clap::Shell; use std::cmp; use std::collections::hash_map::HashMap; @@ -122,31 +123,38 @@ fn main() { /// Prints completions for the utility in the first parameter for the shell in the second parameter to stdout fn gen_completions( - mut args: impl Iterator, + args: impl Iterator, util_map: UtilityMap, ) -> ! { - let utility = args - .next() - .expect("expected utility as the first parameter") - .to_str() - .expect("utility name was not valid utf-8") - .to_owned(); - let shell = args - .next() - .expect("expected shell as the second parameter") - .to_str() - .expect("shell name was not valid utf-8") - .to_owned(); + let all_utilities: Vec<_> = std::iter::once("coreutils") + .chain(util_map.keys().copied()) + .collect(); + + let matches = App::new("completion") + .about("Prints completions to stdout") + .arg( + Arg::with_name("utility") + .possible_values(&all_utilities) + .required(true), + ) + .arg( + Arg::with_name("shell") + .possible_values(&Shell::variants()) + .required(true), + ) + .get_matches_from(std::iter::once(OsString::from("completion")).chain(args)); + + let utility = matches.value_of("utility").unwrap(); + let shell = matches.value_of("shell").unwrap(); + let mut app = if utility == "coreutils" { gen_coreutils_app(util_map) - } else if let Some((_, app)) = util_map.get(utility.as_str()) { - app() } else { - eprintln!("{} is not a valid utility", utility); - process::exit(1) + util_map.get(utility).unwrap().1() }; let shell: Shell = shell.parse().unwrap(); - let bin_name = std::env::var("PROG_PREFIX").unwrap_or_default() + &utility; + let bin_name = std::env::var("PROG_PREFIX").unwrap_or_default() + utility; + app.gen_completions_to(bin_name, shell, &mut io::stdout()); io::stdout().flush().unwrap(); process::exit(0); diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 94ec97e98..ef12eb82a 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -6,6 +6,9 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[macro_use] extern crate uucore; diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 35a5308ed..8ad563c5d 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -10,6 +10,9 @@ // spell-checker:ignore (ToDO) nonprint nonblank nonprinting +// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[cfg(unix)] extern crate unix_socket; #[macro_use] diff --git a/src/uu/csplit/src/csplit_error.rs b/src/uu/csplit/src/csplit_error.rs index 637cf8890..e2f514ea9 100644 --- a/src/uu/csplit/src/csplit_error.rs +++ b/src/uu/csplit/src/csplit_error.rs @@ -1,3 +1,6 @@ +// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + use std::io; use thiserror::Error; diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index e5f0e6efc..05167853c 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -10,6 +10,7 @@ extern crate uucore; use chrono::prelude::DateTime; use chrono::Local; +use clap::ArgMatches; use clap::{crate_version, App, Arg}; use std::collections::HashSet; use std::convert::TryFrom; @@ -63,6 +64,7 @@ mod options { pub const TIME_STYLE: &str = "time-style"; pub const ONE_FILE_SYSTEM: &str = "one-file-system"; pub const DEREFERENCE: &str = "dereference"; + pub const INODES: &str = "inodes"; pub const FILE: &str = "FILE"; } @@ -89,6 +91,7 @@ struct Options { separate_dirs: bool, one_file_system: bool, dereference: bool, + inodes: bool, } #[derive(PartialEq, Eq, Hash, Clone, Copy)] @@ -102,6 +105,7 @@ struct Stat { is_dir: bool, size: u64, blocks: u64, + inodes: u64, inode: Option, created: Option, accessed: u64, @@ -127,6 +131,7 @@ impl Stat { is_dir: metadata.is_dir(), size: metadata.len(), blocks: metadata.blocks() as u64, + inodes: 1, inode: Some(file_info), created: birth_u64(&metadata), accessed: metadata.atime() as u64, @@ -144,6 +149,7 @@ impl Stat { size: metadata.len(), blocks: size_on_disk / 1024 * 2, inode: file_info, + inodes: 1, created: windows_creation_time_to_unix_time(metadata.creation_time()), accessed: windows_time_to_unix_time(metadata.last_access_time()), modified: windows_time_to_unix_time(metadata.last_write_time()), @@ -257,6 +263,18 @@ fn read_block_size(s: Option<&str>) -> usize { } } +fn choose_size(matches: &ArgMatches, stat: &Stat) -> u64 { + if matches.is_present(options::INODES) { + stat.inodes + } else if matches.is_present(options::APPARENT_SIZE) || matches.is_present(options::BYTES) { + stat.size + } else { + // The st_blocks field indicates the number of blocks allocated to the file, 512-byte units. + // See: http://linux.die.net/man/2/stat + stat.blocks * 512 + } +} + // this takes `my_stat` to avoid having to stat files multiple times. // XXX: this should use the impl Trait return type when it is stabilized fn du( @@ -307,6 +325,7 @@ fn du( } else { my_stat.size += this_stat.size; my_stat.blocks += this_stat.blocks; + my_stat.inodes += 1; if options.all { stats.push(this_stat); } @@ -330,6 +349,7 @@ fn du( if !options.separate_dirs && stat.path.parent().unwrap() == my_stat.path { my_stat.size += stat.size; my_stat.blocks += stat.blocks; + my_stat.inodes += stat.inodes; } options .max_depth @@ -413,6 +433,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { separate_dirs: matches.is_present(options::SEPARATE_DIRS), one_file_system: matches.is_present(options::ONE_FILE_SYSTEM), dereference: matches.is_present(options::DEREFERENCE), + inodes: matches.is_present(options::INODES), }; let files = match matches.value_of(options::FILE) { @@ -420,6 +441,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { None => vec!["."], }; + if options.inodes + && (matches.is_present(options::APPARENT_SIZE) || matches.is_present(options::BYTES)) + { + show_warning!("options --apparent-size and -b are ineffective with --inodes") + } + let block_size = u64::try_from(read_block_size(matches.value_of(options::BLOCK_SIZE))).unwrap(); let threshold = matches.value_of(options::THRESHOLD).map(|s| { @@ -445,7 +472,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { convert_size_other } }; - let convert_size = |size| convert_size_fn(size, multiplier, block_size); + let convert_size = |size: u64| { + if options.inodes { + size.to_string() + } else { + convert_size_fn(size, multiplier, block_size) + } + }; let time_format_str = match matches.value_of("time-style") { Some(s) => match s { @@ -488,15 +521,7 @@ Try '{} --help' for more information.", let (_, len) = iter.size_hint(); let len = len.unwrap(); for (index, stat) in iter.enumerate() { - let size = if matches.is_present(options::APPARENT_SIZE) - || matches.is_present(options::BYTES) - { - stat.size - } else { - // C's stat is such that each block is assume to be 512 bytes - // See: http://linux.die.net/man/2/stat - stat.blocks * 512 - }; + let size = choose_size(&matches, &stat); if threshold.map_or(false, |threshold| threshold.should_exclude(size)) { continue; @@ -629,8 +654,8 @@ pub fn uu_app() -> App<'static, 'static> { .help("print sizes in human readable format (e.g., 1K 234M 2G)") ) .arg( - Arg::with_name("inodes") - .long("inodes") + Arg::with_name(options::INODES) + .long(options::INODES) .help( "list inode usage information instead of block usage like --block-size=1K" ) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index e45797750..bd227da56 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -15,6 +15,7 @@ extern crate uucore; use clap::{crate_version, App, Arg, ArgMatches}; use file_diff::diff; use filetime::{set_file_times, FileTime}; +use uucore::backup_control::{self, BackupMode}; use uucore::entries::{grp2gid, usr2uid}; use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity}; @@ -33,6 +34,7 @@ const DEFAULT_STRIP_PROGRAM: &str = "strip"; pub struct Behavior { main_function: MainFunction, specified_mode: Option, + backup_mode: BackupMode, suffix: String, owner: String, group: String, @@ -68,7 +70,7 @@ static ABOUT: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing static OPT_COMPARE: &str = "compare"; static OPT_BACKUP: &str = "backup"; -static OPT_BACKUP_2: &str = "backup2"; +static OPT_BACKUP_NO_ARG: &str = "backup2"; static OPT_DIRECTORY: &str = "directory"; static OPT_IGNORED: &str = "ignored"; static OPT_CREATE_LEADING: &str = "create-leading"; @@ -130,14 +132,17 @@ pub fn uu_app() -> App<'static, 'static> { .arg( Arg::with_name(OPT_BACKUP) .long(OPT_BACKUP) - .help("(unimplemented) make a backup of each existing destination file") + .help("make a backup of each existing destination file") + .takes_value(true) + .require_equals(true) + .min_values(0) .value_name("CONTROL") ) .arg( // TODO implement flag - Arg::with_name(OPT_BACKUP_2) + Arg::with_name(OPT_BACKUP_NO_ARG) .short("b") - .help("(unimplemented) like --backup but does not accept an argument") + .help("like --backup but does not accept an argument") ) .arg( Arg::with_name(OPT_IGNORED) @@ -210,7 +215,7 @@ pub fn uu_app() -> App<'static, 'static> { Arg::with_name(OPT_SUFFIX) .short("S") .long(OPT_SUFFIX) - .help("(unimplemented) override the usual backup suffix") + .help("override the usual backup suffix") .value_name("SUFFIX") .takes_value(true) .min_values(1) @@ -265,13 +270,7 @@ pub fn uu_app() -> App<'static, 'static> { /// /// fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> { - if matches.is_present(OPT_BACKUP) { - Err("--backup") - } else if matches.is_present(OPT_BACKUP_2) { - Err("-b") - } else if matches.is_present(OPT_SUFFIX) { - Err("--suffix, -S") - } else if matches.is_present(OPT_NO_TARGET_DIRECTORY) { + if matches.is_present(OPT_NO_TARGET_DIRECTORY) { Err("--no-target-directory, -T") } else if matches.is_present(OPT_PRESERVE_CONTEXT) { Err("--preserve-context, -P") @@ -309,18 +308,16 @@ fn behavior(matches: &ArgMatches) -> Result { None }; - let backup_suffix = if matches.is_present(OPT_SUFFIX) { - matches.value_of(OPT_SUFFIX).ok_or(1)? - } else { - "~" - }; - let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned()); Ok(Behavior { main_function, specified_mode, - suffix: backup_suffix.to_string(), + backup_mode: backup_control::determine_backup_mode( + matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP), + matches.value_of(OPT_BACKUP), + ), + suffix: backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)), owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(), group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(), verbose: matches.is_present(OPT_VERBOSE), @@ -517,6 +514,28 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> { if b.compare && !need_copy(from, to, b) { return Ok(()); } + // Declare the path here as we may need it for the verbose output below. + let mut backup_path = None; + + // Perform backup, if any, before overwriting 'to' + // + // The codes actually making use of the backup process don't seem to agree + // on how best to approach the issue. (mv and ln, for example) + if to.exists() { + backup_path = backup_control::get_backup_path(b.backup_mode, to, &b.suffix); + if let Some(ref backup_path) = backup_path { + // TODO!! + if let Err(err) = fs::rename(to, backup_path) { + show_error!( + "install: cannot backup file '{}' to '{}': {}", + to.display(), + backup_path.display(), + err + ); + return Err(()); + } + } + } if from.to_string_lossy() == "/dev/null" { /* workaround a limitation of fs::copy @@ -624,7 +643,11 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> { } if b.verbose { - show_error!("'{}' -> '{}'", from.display(), to.display()); + print!("'{}' -> '{}'", from.display(), to.display()); + match backup_path { + Some(path) => println!(" (backup: '{}')", path.display()), + None => println!(), + } } Ok(()) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 2748cbe24..a2c6f3481 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -7,6 +7,9 @@ // spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf +// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[macro_use] extern crate uucore; #[cfg(unix)] @@ -46,19 +49,12 @@ use unicode_width::UnicodeWidthStr; use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::{fs::display_permissions, version_cmp::version_cmp}; -static ABOUT: &str = " - By default, ls will list the files and contents of any directories on - the command line, expect that it will ignore files and directories - whose names start with '.' -"; -static AFTER_HELP: &str = "The TIME_STYLE argument can be full-iso, long-iso, iso. -Also the TIME_STYLE environment variable sets the default style to use."; - fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } pub mod options { + pub mod format { pub static ONE_LINE: &str = "1"; pub static LONG: &str = "long"; @@ -69,10 +65,12 @@ pub mod options { pub static LONG_NO_GROUP: &str = "o"; pub static LONG_NUMERIC_UID_GID: &str = "numeric-uid-gid"; } + pub mod files { pub static ALL: &str = "all"; pub static ALMOST_ALL: &str = "almost-all"; } + pub mod sort { pub static SIZE: &str = "S"; pub static TIME: &str = "t"; @@ -80,30 +78,36 @@ pub mod options { pub static VERSION: &str = "v"; pub static EXTENSION: &str = "X"; } + pub mod time { pub static ACCESS: &str = "u"; pub static CHANGE: &str = "c"; } + pub mod size { pub static HUMAN_READABLE: &str = "human-readable"; pub static SI: &str = "si"; } + pub mod quoting { pub static ESCAPE: &str = "escape"; pub static LITERAL: &str = "literal"; pub static C: &str = "quote-name"; } - pub static QUOTING_STYLE: &str = "quoting-style"; + pub mod indicator_style { pub static SLASH: &str = "p"; pub static FILE_TYPE: &str = "file-type"; pub static CLASSIFY: &str = "classify"; } + pub mod dereference { pub static ALL: &str = "dereference"; pub static ARGS: &str = "dereference-command-line"; pub static DIR_ARGS: &str = "dereference-command-line-symlink-to-dir"; } + + pub static QUOTING_STYLE: &str = "quoting-style"; pub static HIDE_CONTROL_CHARS: &str = "hide-control-chars"; pub static SHOW_CONTROL_CHARS: &str = "show-control-chars"; pub static WIDTH: &str = "width"; @@ -599,15 +603,27 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> App<'static, 'static> { App::new(executable!()) .version(crate_version!()) - .about(ABOUT) - + .about( + "By default, ls will list the files and contents of any directories on \ + the command line, expect that it will ignore files and directories \ + whose names start with '.'.", + ) // Format arguments .arg( Arg::with_name(options::FORMAT) .long(options::FORMAT) .help("Set the display format.") .takes_value(true) - .possible_values(&["long", "verbose", "single-column", "columns", "vertical", "across", "horizontal", "commas"]) + .possible_values(&[ + "long", + "verbose", + "single-column", + "columns", + "vertical", + "across", + "horizontal", + "commas", + ]) .hide_possible_values(true) .require_equals(true) .overrides_with_all(&[ @@ -677,41 +693,51 @@ pub fn uu_app() -> App<'static, 'static> { Arg::with_name(options::format::ONE_LINE) .short(options::format::ONE_LINE) .help("List one file per line.") - .multiple(true) + .multiple(true), ) .arg( Arg::with_name(options::format::LONG_NO_GROUP) .short(options::format::LONG_NO_GROUP) - .help("Long format without group information. Identical to --format=long with --no-group.") - .multiple(true) + .help( + "Long format without group information. \ + Identical to --format=long with --no-group.", + ) + .multiple(true), ) .arg( Arg::with_name(options::format::LONG_NO_OWNER) .short(options::format::LONG_NO_OWNER) .help("Long format without owner information.") - .multiple(true) + .multiple(true), ) .arg( Arg::with_name(options::format::LONG_NUMERIC_UID_GID) .short("n") .long(options::format::LONG_NUMERIC_UID_GID) .help("-l with numeric UIDs and GIDs.") - .multiple(true) + .multiple(true), ) - // Quoting style .arg( Arg::with_name(options::QUOTING_STYLE) .long(options::QUOTING_STYLE) .takes_value(true) .help("Set quoting style.") - .possible_values(&["literal", "shell", "shell-always", "shell-escape", "shell-escape-always", "c", "escape"]) + .possible_values(&[ + "literal", + "shell", + "shell-always", + "shell-escape", + "shell-escape-always", + "c", + "escape", + ]) .overrides_with_all(&[ options::QUOTING_STYLE, options::quoting::LITERAL, options::quoting::ESCAPE, options::quoting::C, - ]) + ]), ) .arg( Arg::with_name(options::quoting::LITERAL) @@ -723,7 +749,7 @@ pub fn uu_app() -> App<'static, 'static> { options::quoting::LITERAL, options::quoting::ESCAPE, options::quoting::C, - ]) + ]), ) .arg( Arg::with_name(options::quoting::ESCAPE) @@ -735,7 +761,7 @@ pub fn uu_app() -> App<'static, 'static> { options::quoting::LITERAL, options::quoting::ESCAPE, options::quoting::C, - ]) + ]), ) .arg( Arg::with_name(options::quoting::C) @@ -747,76 +773,63 @@ pub fn uu_app() -> App<'static, 'static> { options::quoting::LITERAL, options::quoting::ESCAPE, options::quoting::C, - ]) + ]), ) - // Control characters .arg( Arg::with_name(options::HIDE_CONTROL_CHARS) .short("q") .long(options::HIDE_CONTROL_CHARS) .help("Replace control characters with '?' if they are not escaped.") - .overrides_with_all(&[ - options::HIDE_CONTROL_CHARS, - options::SHOW_CONTROL_CHARS, - ]) + .overrides_with_all(&[options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS]), ) .arg( Arg::with_name(options::SHOW_CONTROL_CHARS) .long(options::SHOW_CONTROL_CHARS) .help("Show control characters 'as is' if they are not escaped.") - .overrides_with_all(&[ - options::HIDE_CONTROL_CHARS, - options::SHOW_CONTROL_CHARS, - ]) + .overrides_with_all(&[options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS]), ) - // Time arguments .arg( Arg::with_name(options::TIME) .long(options::TIME) - .help("Show time in :\n\ + .help( + "Show time in :\n\ \taccess time (-u): atime, access, use;\n\ \tchange time (-t): ctime, status.\n\ - \tbirth time: birth, creation;") + \tbirth time: birth, creation;", + ) .value_name("field") .takes_value(true) - .possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"]) + .possible_values(&[ + "atime", "access", "use", "ctime", "status", "birth", "creation", + ]) .hide_possible_values(true) .require_equals(true) - .overrides_with_all(&[ - options::TIME, - options::time::ACCESS, - options::time::CHANGE, - ]) + .overrides_with_all(&[options::TIME, options::time::ACCESS, options::time::CHANGE]), ) .arg( Arg::with_name(options::time::CHANGE) .short(options::time::CHANGE) - .help("If the long listing format (e.g., -l, -o) is being used, print the status \ + .help( + "If the long listing format (e.g., -l, -o) is being used, print the status \ change time (the 'ctime' in the inode) instead of the modification time. When \ explicitly sorting by time (--sort=time or -t) or when not using a long listing \ - format, sort according to the status change time.") - .overrides_with_all(&[ - options::TIME, - options::time::ACCESS, - options::time::CHANGE, - ]) + format, sort according to the status change time.", + ) + .overrides_with_all(&[options::TIME, options::time::ACCESS, options::time::CHANGE]), ) .arg( Arg::with_name(options::time::ACCESS) .short(options::time::ACCESS) - .help("If the long listing format (e.g., -l, -o) is being used, print the status \ + .help( + "If the long listing format (e.g., -l, -o) is being used, print the status \ access time instead of the modification time. When explicitly sorting by time \ (--sort=time or -t) or when not using a long listing format, sort according to the \ - access time.") - .overrides_with_all(&[ - options::TIME, - options::time::ACCESS, - options::time::CHANGE, - ]) + access time.", + ) + .overrides_with_all(&[options::TIME, options::time::ACCESS, options::time::CHANGE]), ) - // Hide and ignore .arg( Arg::with_name(options::HIDE) @@ -824,7 +837,9 @@ pub fn uu_app() -> App<'static, 'static> { .takes_value(true) .multiple(true) .value_name("PATTERN") - .help("do not list implied entries matching shell PATTERN (overridden by -a or -A)") + .help( + "do not list implied entries matching shell PATTERN (overridden by -a or -A)", + ), ) .arg( Arg::with_name(options::IGNORE) @@ -833,7 +848,7 @@ pub fn uu_app() -> App<'static, 'static> { .takes_value(true) .multiple(true) .value_name("PATTERN") - .help("do not list implied entries matching shell PATTERN") + .help("do not list implied entries matching shell PATTERN"), ) .arg( Arg::with_name(options::IGNORE_BACKUPS) @@ -841,7 +856,6 @@ pub fn uu_app() -> App<'static, 'static> { .long(options::IGNORE_BACKUPS) .help("Ignore entries which end with ~."), ) - // Sort arguments .arg( Arg::with_name(options::SORT) @@ -858,7 +872,7 @@ pub fn uu_app() -> App<'static, 'static> { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) .arg( Arg::with_name(options::sort::SIZE) @@ -871,7 +885,7 @@ pub fn uu_app() -> App<'static, 'static> { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) .arg( Arg::with_name(options::sort::TIME) @@ -884,7 +898,7 @@ pub fn uu_app() -> App<'static, 'static> { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) .arg( Arg::with_name(options::sort::VERSION) @@ -897,7 +911,7 @@ pub fn uu_app() -> App<'static, 'static> { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) .arg( Arg::with_name(options::sort::EXTENSION) @@ -910,14 +924,16 @@ pub fn uu_app() -> App<'static, 'static> { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) .arg( Arg::with_name(options::sort::NONE) .short(options::sort::NONE) - .help("Do not sort; list the files in whatever order they are stored in the \ - directory. This is especially useful when listing very large directories, \ - since not doing any sorting can be noticeably faster.") + .help( + "Do not sort; list the files in whatever order they are stored in the \ + directory. This is especially useful when listing very large directories, \ + since not doing any sorting can be noticeably faster.", + ) .overrides_with_all(&[ options::SORT, options::sort::SIZE, @@ -925,9 +941,8 @@ pub fn uu_app() -> App<'static, 'static> { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) - // Dereferencing .arg( Arg::with_name(options::dereference::ALL) @@ -941,48 +956,43 @@ pub fn uu_app() -> App<'static, 'static> { options::dereference::ALL, options::dereference::DIR_ARGS, options::dereference::ARGS, - ]) + ]), ) .arg( Arg::with_name(options::dereference::DIR_ARGS) .long(options::dereference::DIR_ARGS) .help( "Do not dereference symlinks except when they link to directories and are \ - given as command line arguments.", + given as command line arguments.", ) .overrides_with_all(&[ options::dereference::ALL, options::dereference::DIR_ARGS, options::dereference::ARGS, - ]) + ]), ) .arg( Arg::with_name(options::dereference::ARGS) .short("H") .long(options::dereference::ARGS) - .help( - "Do not dereference symlinks except when given as command line arguments.", - ) + .help("Do not dereference symlinks except when given as command line arguments.") .overrides_with_all(&[ options::dereference::ALL, options::dereference::DIR_ARGS, options::dereference::ARGS, - ]) + ]), ) - // Long format options .arg( Arg::with_name(options::NO_GROUP) .long(options::NO_GROUP) .short("-G") - .help("Do not show group in long format.") - ) - .arg( - Arg::with_name(options::AUTHOR) - .long(options::AUTHOR) - .help("Show author in long format. On the supported platforms, the author \ - always matches the file owner.") + .help("Do not show group in long format."), ) + .arg(Arg::with_name(options::AUTHOR).long(options::AUTHOR).help( + "Show author in long format. \ + On the supported platforms, the author always matches the file owner.", + )) // Other Flags .arg( Arg::with_name(options::files::ALL) @@ -995,9 +1005,9 @@ pub fn uu_app() -> App<'static, 'static> { .short("A") .long(options::files::ALMOST_ALL) .help( - "In a directory, do not ignore all file names that start with '.', only ignore \ - '.' and '..'.", - ), + "In a directory, do not ignore all file names that start with '.', \ +only ignore '.' and '..'.", + ), ) .arg( Arg::with_name(options::DIRECTORY) @@ -1020,7 +1030,7 @@ pub fn uu_app() -> App<'static, 'static> { .arg( Arg::with_name(options::size::SI) .long(options::size::SI) - .help("Print human readable file sizes using powers of 1000 instead of 1024.") + .help("Print human readable file sizes using powers of 1000 instead of 1024."), ) .arg( Arg::with_name(options::INODE) @@ -1032,9 +1042,11 @@ pub fn uu_app() -> App<'static, 'static> { Arg::with_name(options::REVERSE) .short("r") .long(options::REVERSE) - .help("Reverse whatever the sorting method is--e.g., list files in reverse \ + .help( + "Reverse whatever the sorting method is e.g., list files in reverse \ alphabetical order, youngest first, smallest first, or whatever.", - )) + ), + ) .arg( Arg::with_name(options::RECURSIVE) .short("R") @@ -1047,7 +1059,7 @@ pub fn uu_app() -> App<'static, 'static> { .short("w") .help("Assume that the terminal is COLS columns wide.") .value_name("COLS") - .takes_value(true) + .takes_value(true), ) .arg( Arg::with_name(options::COLOR) @@ -1060,8 +1072,10 @@ pub fn uu_app() -> App<'static, 'static> { .arg( Arg::with_name(options::INDICATOR_STYLE) .long(options::INDICATOR_STYLE) - .help(" append indicator with style WORD to entry names: none (default), slash\ - (-p), file-type (--file-type), classify (-F)") + .help( + "Append indicator with style WORD to entry names: \ + none (default), slash (-p), file-type (--file-type), classify (-F)", + ) .takes_value(true) .possible_values(&["none", "slash", "file-type", "classify"]) .overrides_with_all(&[ @@ -1069,21 +1083,24 @@ pub fn uu_app() -> App<'static, 'static> { options::indicator_style::SLASH, options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, - ])) - .arg( + ]), + ) + .arg( Arg::with_name(options::indicator_style::CLASSIFY) .short("F") .long(options::indicator_style::CLASSIFY) - .help("Append a character to each file name indicating the file type. Also, for \ - regular files that are executable, append '*'. The file type indicators are \ - '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ - '>' for doors, and nothing for regular files.") + .help( + "Append a character to each file name indicating the file type. Also, for \ + regular files that are executable, append '*'. The file type indicators are \ + '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ + '>' for doors, and nothing for regular files.", + ) .overrides_with_all(&[ options::indicator_style::FILE_TYPE, options::indicator_style::SLASH, options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, - ]) + ]), ) .arg( Arg::with_name(options::indicator_style::FILE_TYPE) @@ -1094,18 +1111,19 @@ pub fn uu_app() -> App<'static, 'static> { options::indicator_style::SLASH, options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, - ])) + ]), + ) .arg( Arg::with_name(options::indicator_style::SLASH) .short(options::indicator_style::SLASH) - .help("Append / indicator to directories." - ) + .help("Append / indicator to directories.") .overrides_with_all(&[ options::indicator_style::FILE_TYPE, options::indicator_style::SLASH, options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, - ])) + ]), + ) .arg( //This still needs support for posix-*, +FORMAT Arg::with_name(options::TIME_STYLE) @@ -1113,27 +1131,25 @@ pub fn uu_app() -> App<'static, 'static> { .help("time/date format with -l; see TIME_STYLE below") .value_name("TIME_STYLE") .env("TIME_STYLE") - .possible_values(&[ - "full-iso", - "long-iso", - "iso", - "locale", - ]) - .overrides_with_all(&[ - options::TIME_STYLE - ]) + .possible_values(&["full-iso", "long-iso", "iso", "locale"]) + .overrides_with_all(&[options::TIME_STYLE]), ) .arg( Arg::with_name(options::FULL_TIME) - .long(options::FULL_TIME) - .overrides_with(options::FULL_TIME) - .help("like -l --time-style=full-iso") + .long(options::FULL_TIME) + .overrides_with(options::FULL_TIME) + .help("like -l --time-style=full-iso"), + ) + // Positional arguments + .arg( + Arg::with_name(options::PATHS) + .multiple(true) + .takes_value(true), + ) + .after_help( + "The TIME_STYLE argument can be full-iso, long-iso, iso. \ + Also the TIME_STYLE environment variable sets the default style to use.", ) - - // Positional arguments - .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)) - - .after_help(AFTER_HELP) } /// Represents a Path along with it's associated data diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index a99867570..7362601ba 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -5,6 +5,9 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[macro_use] extern crate uucore; diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 8a4b472aa..ef5c41abf 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -8,6 +8,9 @@ // spell-checker:ignore (paths) GPGHome +// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[macro_use] extern crate uucore; diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 8f25cd7e4..ecc779ba6 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -349,12 +349,10 @@ impl<'a> Pager<'a> { let status = format!("--More--({})", status_inner); let banner = match (self.silent, wrong_key) { - (true, Some(key)) => { - format!( - "{} [Unknown key: '{}'. Press 'h' for instructions. (unimplemented)]", - status, key - ) - } + (true, Some(key)) => format!( + "{} [Unknown key: '{}'. Press 'h' for instructions. (unimplemented)]", + status, key + ), (true, None) => format!("{}[Press space to continue, 'q' to quit.]", status), (false, Some(_)) => format!("{}{}", status, BELL), (false, None) => status, diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index dd2b05d0e..bfc7a4197 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -8,6 +8,9 @@ // spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm +// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + pub extern crate filetime; #[macro_use] diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 0bcc66664..95d71e77a 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -4,6 +4,8 @@ // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] #[macro_use] extern crate uucore; diff --git a/src/uucore/src/lib/features/encoding.rs b/src/uucore/src/lib/features/encoding.rs index 03fa0ed8b..08c0d27e9 100644 --- a/src/uucore/src/lib/features/encoding.rs +++ b/src/uucore/src/lib/features/encoding.rs @@ -7,6 +7,9 @@ // spell-checker:ignore (strings) ABCDEFGHIJKLMNOPQRSTUVWXYZ +// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + extern crate data_encoding; use self::data_encoding::{DecodeError, BASE32, BASE64}; diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index e4d83e746..6e3a2166f 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -27,6 +27,9 @@ macro_rules! show( let e = $err; uucore::error::set_exit_code(e.code()); eprintln!("{}: {}", executable!(), e); + if e.usage() { + eprintln!("Try '{} --help' for more information.", executable!()); + } }) ); diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index 83268d351..b8f389c83 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -37,6 +37,19 @@ pub fn determine_backup_suffix(supplied_suffix: Option<&str>) -> String { } } +/// # TODO +/// +/// This function currently deviates slightly from how the [manual][1] describes +/// that it should work. In particular, the current implementation: +/// +/// 1. Doesn't strictly respect the order in which to determine the backup type, +/// which is (in order of precedence) +/// 1. Take a valid value to the '--backup' option +/// 2. Take the value of the `VERSION_CONTROL` env var +/// 3. default to 'existing' +/// 2. Doesn't accept abbreviations to the 'backup_option' parameter +/// +/// [1]: https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html pub fn determine_backup_mode(backup_opt_exists: bool, backup_opt: Option<&str>) -> BackupMode { if backup_opt_exists { match backup_opt.map(String::from) { diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index f13c777b6..ae509ff00 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -144,6 +144,13 @@ impl UError { UError::Custom(e) => e.code(), } } + + pub fn usage(&self) -> bool { + match self { + UError::Common(e) => e.usage(), + UError::Custom(e) => e.usage(), + } + } } impl From for UError { @@ -220,6 +227,10 @@ pub trait UCustomError: Error { fn code(&self) -> i32 { 1 } + + fn usage(&self) -> bool { + false + } } impl From> for i32 { @@ -291,6 +302,37 @@ impl UCustomError for USimpleError { } } +#[derive(Debug)] +pub struct UUsageError { + pub code: i32, + pub message: String, +} + +impl UUsageError { + #[allow(clippy::new_ret_no_self)] + pub fn new(code: i32, message: String) -> UError { + UError::Custom(Box::new(Self { code, message })) + } +} + +impl Error for UUsageError {} + +impl Display for UUsageError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + self.message.fmt(f) + } +} + +impl UCustomError for UUsageError { + fn code(&self) -> i32 { + self.code + } + + fn usage(&self) -> bool { + true + } +} + /// Wrapper type around [`std::io::Error`]. /// /// The messages displayed by [`UIoError`] should match the error messages displayed by GNU @@ -331,6 +373,10 @@ impl UIoError { pub fn code(&self) -> i32 { 1 } + + pub fn usage(&self) -> bool { + false + } } impl Error for UIoError {} @@ -427,6 +473,10 @@ impl UCommonError { pub fn code(&self) -> i32 { 1 } + + pub fn usage(&self) -> bool { + false + } } impl From for i32 { diff --git a/src/uucore_procs/src/lib.rs b/src/uucore_procs/src/lib.rs index 93567a12d..f62e4178e 100644 --- a/src/uucore_procs/src/lib.rs +++ b/src/uucore_procs/src/lib.rs @@ -103,6 +103,9 @@ pub fn gen_uumain(_args: TokenStream, stream: TokenStream) -> TokenStream { if s != "" { show_error!("{}", s); } + if e.usage() { + eprintln!("Try '{} --help' for more information.", executable!()); + } e.code() } } diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 67036be44..029f5e516 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -292,6 +292,77 @@ fn _du_dereference(s: &str) { } } +#[test] +fn test_du_inodes_basic() { + let scene = TestScenario::new(util_name!()); + let result = scene.ucmd().arg("--inodes").succeeds(); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg("--inodes").run(); + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + } + + #[cfg(not(target_os = "linux"))] + _du_inodes_basic(result.stdout_str()); +} + +#[cfg(target_os = "windows")] +fn _du_inodes_basic(s: &str) { + assert_eq!( + s, + "2\t.\\subdir\\deeper\\deeper_dir +4\t.\\subdir\\deeper +3\t.\\subdir\\links +8\t.\\subdir +11\t. +" + ); +} + +#[cfg(not(target_os = "windows"))] +fn _du_inodes_basic(s: &str) { + assert_eq!( + s, + "2\t./subdir/deeper/deeper_dir +4\t./subdir/deeper +3\t./subdir/links +8\t./subdir +11\t. +" + ); +} + +#[test] +fn test_du_inodes() { + let scene = TestScenario::new(util_name!()); + + scene + .ucmd() + .arg("--summarize") + .arg("--inodes") + .succeeds() + .stdout_only("11\t.\n"); + + let result = scene + .ucmd() + .arg("--separate-dirs") + .arg("--inodes") + .succeeds(); + + #[cfg(target_os = "windows")] + result.stdout_contains("3\t.\\subdir\\links\n"); + #[cfg(not(target_os = "windows"))] + result.stdout_contains("3\t./subdir/links\n"); + result.stdout_contains("3\t.\n"); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg("--separate-dirs").arg("--inodes").run(); + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + } +} + #[test] fn test_du_h_flag_empty_file() { new_ucmd!() @@ -419,3 +490,96 @@ fn test_du_threshold() { .stdout_does_not_contain("links") .stdout_contains("deeper_dir"); } + +#[test] +fn test_du_apparent_size() { + let scene = TestScenario::new(util_name!()); + let result = scene.ucmd().arg("--apparent-size").succeeds(); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg("--apparent-size").run(); + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + } + + #[cfg(not(target_os = "linux"))] + _du_apparent_size(result.stdout_str()); +} + +#[cfg(target_os = "windows")] +fn _du_apparent_size(s: &str) { + assert_eq!( + s, + "1\t.\\subdir\\deeper\\deeper_dir +1\t.\\subdir\\deeper +6\t.\\subdir\\links +6\t.\\subdir +6\t. +" + ); +} +#[cfg(target_vendor = "apple")] +fn _du_apparent_size(s: &str) { + assert_eq!( + s, + "1\t./subdir/deeper/deeper_dir +1\t./subdir/deeper +6\t./subdir/links +6\t./subdir +6\t. +" + ); +} +#[cfg(target_os = "freebsd")] +fn _du_apparent_size(s: &str) { + assert_eq!( + s, + "1\t./subdir/deeper/deeper_dir +2\t./subdir/deeper +6\t./subdir/links +8\t./subdir +8\t. +" + ); +} +#[cfg(all( + not(target_vendor = "apple"), + not(target_os = "windows"), + not(target_os = "freebsd") +))] +fn _du_apparent_size(s: &str) { + assert_eq!( + s, + "5\t./subdir/deeper/deeper_dir +9\t./subdir/deeper +10\t./subdir/links +22\t./subdir +26\t. +" + ); +} + +#[test] +fn test_du_bytes() { + let scene = TestScenario::new(util_name!()); + let result = scene.ucmd().arg("--bytes").succeeds(); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg("--bytes").run(); + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + } + + #[cfg(target_os = "windows")] + result.stdout_contains("5145\t.\\subdir\n"); + #[cfg(target_vendor = "apple")] + result.stdout_contains("5625\t./subdir\n"); + #[cfg(target_os = "freebsd")] + result.stdout_contains("7193\t./subdir\n"); + #[cfg(all( + not(target_vendor = "apple"), + not(target_os = "windows"), + not(target_os = "freebsd") + ))] + result.stdout_contains("21529\t./subdir\n"); +} diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 8065cb490..246f5b62a 100755 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -250,6 +250,28 @@ hello ); } +#[test] +fn test_bad_utf8() { + let bytes: &[u8] = b"\xfc\x80\x80\x80\x80\xaf"; + new_ucmd!() + .args(&["-c", "6"]) + .pipe_in(bytes) + .succeeds() + .stdout_is_bytes(bytes); +} + +#[test] +fn test_bad_utf8_lines() { + let input: &[u8] = + b"\xfc\x80\x80\x80\x80\xaf\nb\xfc\x80\x80\x80\x80\xaf\nb\xfc\x80\x80\x80\x80\xaf"; + let output = b"\xfc\x80\x80\x80\x80\xaf\nb\xfc\x80\x80\x80\x80\xaf\n"; + new_ucmd!() + .args(&["-n", "2"]) + .pipe_in(input) + .succeeds() + .stdout_is_bytes(output); +} + #[test] fn test_head_invalid_num() { new_ucmd!() diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index ea2c2818e..06808db6b 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -696,3 +696,388 @@ fn test_install_dir() { assert!(at.file_exists(&format!("{}/{}", dir, file1))); assert!(at.file_exists(&format!("{}/{}", dir, file2))); } +// +// test backup functionality +#[test] +fn test_install_backup_short_no_args_files() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_simple_backup_file_a"; + let file_b = "test_install_simple_backup_file_b"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("-b") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}~", file_b))); +} + +#[test] +fn test_install_backup_short_no_args_file_to_dir() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file = "test_install_simple_backup_file_a"; + let dest_dir = "test_install_dest/"; + let expect = format!("{}{}", dest_dir, file); + + at.touch(file); + at.mkdir(dest_dir); + at.touch(&expect); + scene + .ucmd() + .arg("-b") + .arg(file) + .arg(dest_dir) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file)); + assert!(at.file_exists(&expect)); + assert!(at.file_exists(&format!("{}~", expect))); +} + +// Long --backup option is tested separately as it requires a slightly different +// handling than '-b' does. +#[test] +fn test_install_backup_long_no_args_files() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_simple_backup_file_a"; + let file_b = "test_install_simple_backup_file_b"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("--backup") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}~", file_b))); +} + +#[test] +fn test_install_backup_long_no_args_file_to_dir() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file = "test_install_simple_backup_file_a"; + let dest_dir = "test_install_dest/"; + let expect = format!("{}{}", dest_dir, file); + + at.touch(file); + at.mkdir(dest_dir); + at.touch(&expect); + scene + .ucmd() + .arg("--backup") + .arg(file) + .arg(dest_dir) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file)); + assert!(at.file_exists(&expect)); + assert!(at.file_exists(&format!("{}~", expect))); +} + +#[test] +fn test_install_backup_short_custom_suffix() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_custom_suffix_file_a"; + let file_b = "test_install_backup_custom_suffix_file_b"; + let suffix = "super-suffix-of-the-century"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("-b") + .arg(format!("--suffix={}", suffix)) + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}{}", file_b, suffix))); +} + +#[test] +fn test_install_backup_custom_suffix_via_env() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_custom_suffix_file_a"; + let file_b = "test_install_backup_custom_suffix_file_b"; + let suffix = "super-suffix-of-the-century"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("-b") + .env("SIMPLE_BACKUP_SUFFIX", suffix) + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}{}", file_b, suffix))); +} + +#[test] +fn test_install_backup_numbered_with_t() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_numbering_file_a"; + let file_b = "test_install_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("--backup=t") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}.~1~", file_b))); +} + +#[test] +fn test_install_backup_numbered_with_numbered() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_numbering_file_a"; + let file_b = "test_install_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("--backup=numbered") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}.~1~", file_b))); +} + +#[test] +fn test_install_backup_existing() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_numbering_file_a"; + let file_b = "test_install_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("--backup=existing") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}~", file_b))); +} + +#[test] +fn test_install_backup_nil() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_numbering_file_a"; + let file_b = "test_install_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("--backup=nil") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}~", file_b))); +} + +#[test] +fn test_install_backup_numbered_if_existing_backup_existing() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_numbering_file_a"; + let file_b = "test_install_backup_numbering_file_b"; + let file_b_backup = "test_install_backup_numbering_file_b.~1~"; + + at.touch(file_a); + at.touch(file_b); + at.touch(file_b_backup); + scene + .ucmd() + .arg("--backup=existing") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(file_b_backup)); + assert!(at.file_exists(&*format!("{}.~2~", file_b))); +} + +#[test] +fn test_install_backup_numbered_if_existing_backup_nil() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_numbering_file_a"; + let file_b = "test_install_backup_numbering_file_b"; + let file_b_backup = "test_install_backup_numbering_file_b.~1~"; + + at.touch(file_a); + at.touch(file_b); + at.touch(file_b_backup); + scene + .ucmd() + .arg("--backup=nil") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(file_b_backup)); + assert!(at.file_exists(&*format!("{}.~2~", file_b))); +} + +#[test] +fn test_install_backup_simple() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_numbering_file_a"; + let file_b = "test_install_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("--backup=simple") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}~", file_b))); +} + +#[test] +fn test_install_backup_never() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_numbering_file_a"; + let file_b = "test_install_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("--backup=never") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}~", file_b))); +} + +#[test] +fn test_install_backup_none() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_numbering_file_a"; + let file_b = "test_install_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("--backup=none") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(!at.file_exists(&format!("{}~", file_b))); +} + +#[test] +fn test_install_backup_off() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_numbering_file_a"; + let file_b = "test_install_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("--backup=off") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(!at.file_exists(&format!("{}~", file_b))); +}