From 80b9bfdd18a5b3b26b586fc7bfa5ae6d57fbe00b Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Fri, 28 May 2021 14:08:46 -0400 Subject: [PATCH 001/320] switch from blake2-rfc to blake2b_simd --- Cargo.lock | 24 ++++++++++++++---------- src/uu/hashsum/Cargo.toml | 2 +- src/uu/hashsum/src/digest.rs | 9 ++++----- src/uu/hashsum/src/hashsum.rs | 9 ++++++--- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e1470c88..19d44e476 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,13 +50,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6945cc5422176fc5e602e590c2878d2c2acd9a4fe20a4baa7c28022521698ec6" [[package]] -name = "arrayvec" -version = "0.4.12" +name = "arrayref" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" -dependencies = [ - "nodrop", -] +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "atty" @@ -106,11 +109,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] -name = "blake2-rfc" -version = "0.2.18" +name = "blake2b_simd" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" dependencies = [ + "arrayref", "arrayvec", "constant_time_eq", ] @@ -2098,7 +2102,7 @@ dependencies = [ name = "uu_hashsum" version = "0.0.6" dependencies = [ - "blake2-rfc", + "blake2b_simd", "clap", "digest", "hex", diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 04a22cac7..11388ebf8 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -25,7 +25,7 @@ regex-syntax = "0.6.7" sha1 = "0.6.0" sha2 = "0.6.0" sha3 = "0.6.0" -blake2-rfc = "0.2.18" +blake2b_simd = "0.5.11" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/hashsum/src/digest.rs b/src/uu/hashsum/src/digest.rs index 218de0a36..25bc7f4c3 100644 --- a/src/uu/hashsum/src/digest.rs +++ b/src/uu/hashsum/src/digest.rs @@ -1,4 +1,3 @@ -extern crate blake2_rfc; extern crate digest; extern crate md5; extern crate sha1; @@ -49,9 +48,9 @@ impl Digest for md5::Context { } } -impl Digest for blake2_rfc::blake2b::Blake2b { +impl Digest for blake2b_simd::State { fn new() -> Self { - blake2_rfc::blake2b::Blake2b::new(64) + Self::new() } fn input(&mut self, input: &[u8]) { @@ -59,12 +58,12 @@ impl Digest for blake2_rfc::blake2b::Blake2b { } fn result(&mut self, out: &mut [u8]) { - let hash_result = &self.clone().finalize(); + let hash_result = &self.finalize(); out.copy_from_slice(&hash_result.as_bytes()); } fn reset(&mut self) { - *self = blake2_rfc::blake2b::Blake2b::new(64); + *self = Self::new(); } fn output_bits(&self) -> usize { diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 2e31ddd25..e0014bb6a 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -19,7 +19,6 @@ mod digest; use self::digest::Digest; -use blake2_rfc::blake2b::Blake2b; use clap::{App, Arg, ArgMatches}; use hex::ToHex; use md5::Context as Md5; @@ -76,7 +75,11 @@ fn detect_algo<'a>( "sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box, 256), "sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box, 384), "sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box, 512), - "b2sum" => ("BLAKE2", Box::new(Blake2b::new(64)) as Box, 512), + "b2sum" => ( + "BLAKE2", + Box::new(blake2b_simd::State::new()) as Box, + 512, + ), "sha3sum" => match matches.value_of("bits") { Some(bits_str) => match (&bits_str).parse::() { Ok(224) => ( @@ -178,7 +181,7 @@ fn detect_algo<'a>( set_or_crash("SHA512", Box::new(Sha512::new()), 512) } if matches.is_present("b2sum") { - set_or_crash("BLAKE2", Box::new(Blake2b::new(64)), 512) + set_or_crash("BLAKE2", Box::new(blake2b_simd::State::new()), 512) } if matches.is_present("sha3") { match matches.value_of("bits") { From 0c502f587bf8db66b07fe614dbaca1bd3bf71a97 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 28 May 2021 17:02:25 +0200 Subject: [PATCH 002/320] uucore: add new module "parse_size" This adds a function to parse size strings, e.g. "2KiB" or "3MB". It is based on similar functions used by head/tail/truncate, etc. --- src/uucore/src/lib/lib.rs | 7 +- src/uucore/src/lib/parser.rs | 1 + src/uucore/src/lib/parser/parse_size.rs | 242 ++++++++++++++++++++++++ 3 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 src/uucore/src/lib/parser.rs create mode 100644 src/uucore/src/lib/parser/parse_size.rs diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index c17f14516..a60af57fa 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -19,10 +19,10 @@ pub extern crate winapi; //## internal modules -mod macros; // crate macros (macro_rules-type; exported to `crate::...`) - mod features; // feature-gated code modules +mod macros; // crate macros (macro_rules-type; exported to `crate::...`) mod mods; // core cross-platform modules +mod parser; // string parsing moduls // * cross-platform modules pub use crate::mods::backup_control; @@ -31,6 +31,9 @@ pub use crate::mods::os; pub use crate::mods::panic; pub use crate::mods::ranges; +// * string parsing modules +pub use crate::parser::parse_size; + // * feature-gated modules #[cfg(feature = "encoding")] pub use crate::features::encoding; diff --git a/src/uucore/src/lib/parser.rs b/src/uucore/src/lib/parser.rs new file mode 100644 index 000000000..21adefa1a --- /dev/null +++ b/src/uucore/src/lib/parser.rs @@ -0,0 +1 @@ +pub mod parse_size; diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs new file mode 100644 index 000000000..a7260306c --- /dev/null +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -0,0 +1,242 @@ +use std::convert::TryFrom; +use std::error::Error; +use std::fmt; + +/// Parse a size string into a number of bytes. +/// +/// A size string comprises an integer and an optional unit. The unit +/// may be K, M, G, T, P, E, Z or Y (powers of 1024), or KB, MB, +/// etc. (powers of 1000), or b which is 512. +/// Binary prefixes can be used, too: KiB=K, MiB=M, and so on. +/// +/// # Errors +/// +/// Will return `ParseSizeError` if it’s not possible to parse this +/// string into a number, e.g. if the string does not begin with a +/// numeral, or if the unit is not one of the supported units described +/// in the preceding section. +/// +/// # Examples +/// +/// ```rust +/// use uucore::parse_size::parse_size; +/// assert_eq!(Ok(123), parse_size("123")); +/// assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000 +/// assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024 +/// ``` +pub fn parse_size(size: &str) -> Result { + if size.is_empty() { + return Err(ParseSizeError::parse_failure(size)); + } + // Get the numeric part of the size argument. For example, if the + // argument is "123K", then the numeric part is "123". + let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect(); + let number: usize = if !numeric_string.is_empty() { + match numeric_string.parse() { + Ok(n) => n, + Err(_) => return Err(ParseSizeError::parse_failure(size)), + } + } else { + 1 + }; + + // Get the alphabetic units part of the size argument and compute + // the factor it represents. For example, if the argument is "123K", + // then the unit part is "K" and the factor is 1024. This may be the + // empty string, in which case, the factor is 1. + let unit = &size[numeric_string.len()..]; + let (base, exponent): (u128, u32) = match unit { + "" => (1, 0), + "b" => (512, 1), // (`head` and `tail` use "b") + "KiB" | "K" | "k" => (1024, 1), + "MiB" | "M" | "m" => (1024, 2), + "GiB" | "G" | "g" => (1024, 3), + "TiB" | "T" | "t" => (1024, 4), + "PiB" | "P" | "p" => (1024, 5), + "EiB" | "E" | "e" => (1024, 6), + "ZiB" | "Z" | "z" => (1024, 7), + "YiB" | "Y" | "y" => (1024, 8), + "KB" | "kB" => (1000, 1), + "MB" | "mB" => (1000, 2), + "GB" | "gB" => (1000, 3), + "TB" | "tB" => (1000, 4), + "PB" | "pB" => (1000, 5), + "EB" | "eB" => (1000, 6), + "ZB" | "zB" => (1000, 7), + "YB" | "yB" => (1000, 8), + _ => return Err(ParseSizeError::parse_failure(size)), + }; + let factor = match usize::try_from(base.pow(exponent)) { + Ok(n) => n, + Err(_) => return Err(ParseSizeError::size_too_big(size)), + }; + match number.checked_mul(factor) { + Some(n) => Ok(n), + None => Err(ParseSizeError::size_too_big(size)), + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseSizeError { + ParseFailure(String), // Syntax + SizeTooBig(String), // Overflow +} + +impl Error for ParseSizeError { + fn description(&self) -> &str { + match *self { + ParseSizeError::ParseFailure(ref s) => &*s, + ParseSizeError::SizeTooBig(ref s) => &*s, + } + } +} + +impl fmt::Display for ParseSizeError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + let s = match self { + ParseSizeError::ParseFailure(s) => s, + ParseSizeError::SizeTooBig(s) => s, + }; + write!(f, "{}", s) + } +} + +impl ParseSizeError { + fn parse_failure(s: &str) -> ParseSizeError { + // has to be handled in the respective uutils because strings differ, e.g. + // truncate: Invalid number: ‘fb’ + // tail: invalid number of bytes: ‘fb’ + ParseSizeError::ParseFailure(format!("‘{}’", s)) + } + + fn size_too_big(s: &str) -> ParseSizeError { + // has to be handled in the respective uutils because strings differ, e.g. + // truncate: Invalid number: ‘1Y’: Value too large to be stored in data type + // tail: invalid number of bytes: ‘1Y’: Value too large to be stored in data type + ParseSizeError::SizeTooBig(format!( + "‘{}’: Value too large to be stored in data type", + s + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn variant_eq(a: &ParseSizeError, b: &ParseSizeError) -> bool { + std::mem::discriminant(a) == std::mem::discriminant(b) + } + + #[test] + fn all_suffixes() { + // Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000). + // Binary prefixes can be used, too: KiB=K, MiB=M, and so on. + let suffixes = [ + ('K', 1u32), + ('M', 2u32), + ('G', 3u32), + ('T', 4u32), + ('P', 5u32), + ('E', 6u32), + #[cfg(target_pointer_width = "128")] + ('Z', 7u32), // ParseSizeError::SizeTooBig on x64 + #[cfg(target_pointer_width = "128")] + ('Y', 8u32), // ParseSizeError::SizeTooBig on x64 + ]; + + for &(c, exp) in &suffixes { + let s = format!("2{}B", c); // KB + assert_eq!(Ok((2 * (1000 as u128).pow(exp)) as usize), parse_size(&s)); + let s = format!("2{}", c); // K + assert_eq!(Ok((2 * (1024 as u128).pow(exp)) as usize), parse_size(&s)); + let s = format!("2{}iB", c); // KiB + assert_eq!(Ok((2 * (1024 as u128).pow(exp)) as usize), parse_size(&s)); + + // suffix only + let s = format!("{}B", c); // KB + assert_eq!(Ok(((1000 as u128).pow(exp)) as usize), parse_size(&s)); + let s = format!("{}", c); // K + assert_eq!(Ok(((1024 as u128).pow(exp)) as usize), parse_size(&s)); + let s = format!("{}iB", c); // KiB + assert_eq!(Ok(((1024 as u128).pow(exp)) as usize), parse_size(&s)); + } + } + + #[test] + #[cfg(not(target_pointer_width = "128"))] + fn overflow_x64() { + assert!(parse_size("10000000000000000000000").is_err()); + assert!(parse_size("1000000000T").is_err()); + assert!(parse_size("100000P").is_err()); + assert!(parse_size("100E").is_err()); + assert!(parse_size("1Z").is_err()); + assert!(parse_size("1Y").is_err()); + + assert!(variant_eq( + &parse_size("1Z").unwrap_err(), + &ParseSizeError::SizeTooBig(String::new()) + )); + + assert_eq!( + ParseSizeError::SizeTooBig( + "‘1Y’: Value too large to be stored in data type".to_string() + ), + parse_size("1Y").unwrap_err() + ); + } + + #[test] + #[cfg(target_pointer_width = "32")] + fn overflow_x32() { + assert!(variant_eq( + &parse_size("1T").unwrap_err(), + &ParseSizeError::SizeTooBig(String::new()) + )); + assert!(variant_eq( + &parse_size("1000G").unwrap_err(), + &ParseSizeError::SizeTooBig(String::new()) + )); + } + + #[test] + fn invalid_syntax() { + let test_strings = ["328hdsf3290", "5MiB nonsense", "5mib", "biB", "-", ""]; + for &test_string in &test_strings { + assert_eq!( + parse_size(test_string).unwrap_err(), + ParseSizeError::ParseFailure(format!("‘{}’", test_string)) + ); + } + } + + #[test] + fn b_suffix() { + assert_eq!(Ok(3 * 512), parse_size("3b")); // b is 512 + } + + #[test] + fn no_suffix() { + assert_eq!(Ok(1234), parse_size("1234")); + assert_eq!(Ok(0), parse_size("0")); + } + + #[test] + fn kilobytes_suffix() { + assert_eq!(Ok(123 * 1000), parse_size("123KB")); // KB is 1000 + assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000 + assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024 + assert_eq!(Ok(0), parse_size("0K")); + assert_eq!(Ok(0), parse_size("0KB")); + assert_eq!(Ok(1000), parse_size("KB")); + assert_eq!(Ok(1024), parse_size("K")); + } + + #[test] + fn megabytes_suffix() { + assert_eq!(Ok(123 * 1024 * 1024), parse_size("123M")); + assert_eq!(Ok(123 * 1000 * 1000), parse_size("123MB")); + assert_eq!(Ok(1024 * 1024), parse_size("M")); + assert_eq!(Ok(1000 * 1000), parse_size("MB")); + } +} From b1b3475e11ff4ef947dfbb8740c7e405ff7776b9 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 28 May 2021 19:57:50 +0200 Subject: [PATCH 003/320] truncate: use "parse_size" from uucore --- src/uu/truncate/src/truncate.rs | 101 ++---------------------- src/uucore/src/lib/parser/parse_size.rs | 5 ++ 2 files changed, 13 insertions(+), 93 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 03b18723c..86a0c9ffc 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -11,9 +11,11 @@ extern crate uucore; use clap::{App, Arg}; +use std::convert::TryFrom; use std::fs::{metadata, OpenOptions}; use std::io::ErrorKind; use std::path::Path; +use uucore::parse_size::parse_size; #[derive(Eq, PartialEq)] enum TruncateMode { @@ -159,14 +161,14 @@ fn truncate( }; let num_bytes = match parse_size(size_string) { Ok(b) => b, - Err(_) => crash!(1, "Invalid number: ‘{}’", size_string), + Err(e) => crash!(1, "Invalid number: {}", e.to_string()), }; (num_bytes, mode) } None => (0, TruncateMode::Reference), }; - let refsize = match reference { + let refsize: usize = match reference { Some(ref rfilename) => { match mode { // Only Some modes work with a reference @@ -176,7 +178,7 @@ fn truncate( _ => crash!(1, "you must specify a relative ‘--size’ with ‘--reference’"), }; match metadata(rfilename) { - Ok(meta) => meta.len(), + Ok(meta) => usize::try_from(meta.len()).unwrap(), Err(f) => match f.kind() { ErrorKind::NotFound => { crash!(1, "cannot stat '{}': No such file or directory", rfilename) @@ -199,14 +201,14 @@ fn truncate( let fsize = match reference { Some(_) => refsize, None => match metadata(filename) { - Ok(meta) => meta.len(), + Ok(meta) => usize::try_from(meta.len()).unwrap(), Err(f) => { show_warning!("{}", f.to_string()); continue; } }, }; - let tsize: u64 = match mode { + let tsize: usize = match mode { TruncateMode::Absolute => modsize, TruncateMode::Reference => fsize, TruncateMode::Extend => fsize + modsize, @@ -216,7 +218,7 @@ fn truncate( TruncateMode::RoundDown => fsize - fsize % modsize, TruncateMode::RoundUp => fsize + fsize % modsize, }; - match file.set_len(tsize) { + match file.set_len(u64::try_from(tsize).unwrap()) { Ok(_) => {} Err(f) => crash!(1, "{}", f.to_string()), }; @@ -225,90 +227,3 @@ fn truncate( } } } - -/// Parse a size string into a number of bytes. -/// -/// A size string comprises an integer and an optional unit. The unit -/// may be K, M, G, T, P, E, Z, or Y (powers of 1024) or KB, MB, -/// etc. (powers of 1000). -/// -/// # Errors -/// -/// This function returns an error if the string does not begin with a -/// numeral, or if the unit is not one of the supported units described -/// in the preceding section. -/// -/// # Examples -/// -/// ```rust,ignore -/// assert_eq!(parse_size("123").unwrap(), 123); -/// assert_eq!(parse_size("123K").unwrap(), 123 * 1024); -/// assert_eq!(parse_size("123KB").unwrap(), 123 * 1000); -/// ``` -fn parse_size(size: &str) -> Result { - // Get the numeric part of the size argument. For example, if the - // argument is "123K", then the numeric part is "123". - let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect(); - let number: u64 = match numeric_string.parse() { - Ok(n) => n, - Err(_) => return Err(()), - }; - - // Get the alphabetic units part of the size argument and compute - // the factor it represents. For example, if the argument is "123K", - // then the unit part is "K" and the factor is 1024. This may be the - // empty string, in which case, the factor is 1. - let n = numeric_string.len(); - let (base, exponent): (u64, u32) = match &size[n..] { - "" => (1, 0), - "K" | "k" => (1024, 1), - "M" | "m" => (1024, 2), - "G" | "g" => (1024, 3), - "T" | "t" => (1024, 4), - "P" | "p" => (1024, 5), - "E" | "e" => (1024, 6), - "Z" | "z" => (1024, 7), - "Y" | "y" => (1024, 8), - "KB" | "kB" => (1000, 1), - "MB" | "mB" => (1000, 2), - "GB" | "gB" => (1000, 3), - "TB" | "tB" => (1000, 4), - "PB" | "pB" => (1000, 5), - "EB" | "eB" => (1000, 6), - "ZB" | "zB" => (1000, 7), - "YB" | "yB" => (1000, 8), - _ => return Err(()), - }; - let factor = base.pow(exponent); - Ok(number * factor) -} - -#[cfg(test)] -mod tests { - use crate::parse_size; - - #[test] - fn test_parse_size_zero() { - assert_eq!(parse_size("0").unwrap(), 0); - assert_eq!(parse_size("0K").unwrap(), 0); - assert_eq!(parse_size("0KB").unwrap(), 0); - } - - #[test] - fn test_parse_size_without_factor() { - assert_eq!(parse_size("123").unwrap(), 123); - } - - #[test] - fn test_parse_size_kilobytes() { - assert_eq!(parse_size("123K").unwrap(), 123 * 1024); - assert_eq!(parse_size("123KB").unwrap(), 123 * 1000); - } - - #[test] - fn test_parse_size_megabytes() { - assert_eq!(parse_size("123").unwrap(), 123); - assert_eq!(parse_size("123M").unwrap(), 123 * 1024 * 1024); - assert_eq!(parse_size("123MB").unwrap(), 123 * 1000 * 1000); - } -} diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index a7260306c..e8ede8cad 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -1,3 +1,8 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + use std::convert::TryFrom; use std::error::Error; use std::fmt; From 0bf14da490c49bacfa078d8acfee14def48ed701 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 28 May 2021 21:12:03 +0200 Subject: [PATCH 004/320] tail: use "parse_size" from uucore --- src/uu/tail/src/tail.rs | 124 +++++-------------------------------- tests/by-util/test_tail.rs | 36 ----------- 2 files changed, 16 insertions(+), 144 deletions(-) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 15a819d35..a4634714c 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -21,13 +21,13 @@ use chunks::ReverseChunks; use clap::{App, Arg}; use std::collections::VecDeque; -use std::error::Error; use std::fmt; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write}; use std::path::Path; use std::thread::sleep; use std::time::Duration; +use uucore::parse_size::parse_size; use uucore::ringbuffer::RingBuffer; pub mod options { @@ -47,8 +47,8 @@ pub mod options { static ARG_FILES: &str = "files"; enum FilterMode { - Bytes(u64), - Lines(u64, u8), // (number of lines, delimiter) + Bytes(usize), + Lines(usize, u8), // (number of lines, delimiter) } struct Settings { @@ -174,31 +174,31 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match matches.value_of(options::LINES) { Some(n) => { let mut slice: &str = n; - if slice.chars().next().unwrap_or('_') == '+' { - settings.beginning = true; + let c = slice.chars().next().unwrap_or('_'); + if c == '+' || c == '-' { slice = &slice[1..]; + if c == '+' { + settings.beginning = true; + } } match parse_size(slice) { Ok(m) => settings.mode = FilterMode::Lines(m, b'\n'), - Err(e) => { - show_error!("{}", e.to_string()); - return 1; - } + Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()), } } None => { if let Some(n) = matches.value_of(options::BYTES) { let mut slice: &str = n; - if slice.chars().next().unwrap_or('_') == '+' { - settings.beginning = true; + let c = slice.chars().next().unwrap_or('_'); + if c == '+' || c == '-' { slice = &slice[1..]; + if c == '+' { + settings.beginning = true; + } } match parse_size(slice) { Ok(m) => settings.mode = FilterMode::Bytes(m), - Err(e) => { - show_error!("{}", e.to_string()); - return 1; - } + Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()), } } } @@ -264,98 +264,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } -#[derive(Debug, PartialEq, Eq)] -pub enum ParseSizeErr { - ParseFailure(String), - SizeTooBig(String), -} - -impl Error for ParseSizeErr { - fn description(&self) -> &str { - match *self { - ParseSizeErr::ParseFailure(ref s) => &*s, - ParseSizeErr::SizeTooBig(ref s) => &*s, - } - } -} - -impl fmt::Display for ParseSizeErr { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - let s = match self { - ParseSizeErr::ParseFailure(s) => s, - ParseSizeErr::SizeTooBig(s) => s, - }; - write!(f, "{}", s) - } -} - -impl ParseSizeErr { - fn parse_failure(s: &str) -> ParseSizeErr { - ParseSizeErr::ParseFailure(format!("invalid size: '{}'", s)) - } - - fn size_too_big(s: &str) -> ParseSizeErr { - ParseSizeErr::SizeTooBig(format!( - "invalid size: '{}': Value too large to be stored in data type", - s - )) - } -} - -pub type ParseSizeResult = Result; - -pub fn parse_size(mut size_slice: &str) -> Result { - let mut base = if size_slice.chars().last().unwrap_or('_') == 'B' { - size_slice = &size_slice[..size_slice.len() - 1]; - 1000u64 - } else { - 1024u64 - }; - - let exponent = if !size_slice.is_empty() { - let mut has_suffix = true; - let exp = match size_slice.chars().last().unwrap_or('_') { - 'K' | 'k' => 1u64, - 'M' => 2u64, - 'G' => 3u64, - 'T' => 4u64, - 'P' => 5u64, - 'E' => 6u64, - 'Z' | 'Y' => { - return Err(ParseSizeErr::size_too_big(size_slice)); - } - 'b' => { - base = 512u64; - 1u64 - } - _ => { - has_suffix = false; - 0u64 - } - }; - if has_suffix { - size_slice = &size_slice[..size_slice.len() - 1]; - } - exp - } else { - 0u64 - }; - - let mut multiplier = 1u64; - for _ in 0u64..exponent { - multiplier *= base; - } - if base == 1000u64 && exponent == 0u64 { - // sole B is not a valid suffix - Err(ParseSizeErr::parse_failure(size_slice)) - } else { - let value: Option = size_slice.parse().ok(); - value - .map(|v| Ok((multiplier as i64 * v.abs()) as u64)) - .unwrap_or_else(|| Err(ParseSizeErr::parse_failure(size_slice))) - } -} - fn follow(readers: &mut [BufReader], filenames: &[String], settings: &Settings) { assert!(settings.follow); let mut last = readers.len() - 1; @@ -469,7 +377,7 @@ fn bounded_tail(file: &mut File, settings: &Settings) { /// If any element of `iter` is an [`Err`], then this function panics. fn unbounded_tail_collect( iter: impl Iterator>, - count: u64, + count: usize, beginning: bool, ) -> VecDeque where diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index f3c9a7b11..27e94a78f 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -1,6 +1,5 @@ extern crate tail; -use self::tail::parse_size; use crate::common::util::*; use std::char::from_digit; use std::io::Write; @@ -236,41 +235,6 @@ fn test_bytes_big() { } } -#[test] -fn test_parse_size() { - // No suffix. - assert_eq!(Ok(1234), parse_size("1234")); - - // kB is 1000 - assert_eq!(Ok(9 * 1000), parse_size("9kB")); - - // K is 1024 - assert_eq!(Ok(2 * 1024), parse_size("2K")); - - let suffixes = [ - ('M', 2u32), - ('G', 3u32), - ('T', 4u32), - ('P', 5u32), - ('E', 6u32), - ]; - - for &(c, exp) in &suffixes { - let s = format!("2{}B", c); - assert_eq!(Ok(2 * (1000 as u64).pow(exp)), parse_size(&s)); - - let s = format!("2{}", c); - assert_eq!(Ok(2 * (1024 as u64).pow(exp)), parse_size(&s)); - } - - // Sizes that are too big. - assert!(parse_size("1Z").is_err()); - assert!(parse_size("1Y").is_err()); - - // Bad number - assert!(parse_size("328hdsf3290").is_err()); -} - #[test] fn test_lines_with_size_suffix() { const FILE: &'static str = "test_lines_with_size_suffix.txt"; From 1c41efd73290aea82994f08b40e7e35f97c8745c Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 31 May 2021 09:35:46 +0200 Subject: [PATCH 005/320] stdbuf: use "parse_size" from uucore --- src/uu/stdbuf/src/stdbuf.rs | 45 ++++-------------------------------- tests/by-util/test_stdbuf.rs | 9 +++++++- 2 files changed, 13 insertions(+), 41 deletions(-) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 485b3c70e..e39af3816 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -19,14 +19,14 @@ use std::path::PathBuf; use std::process::Command; use tempfile::tempdir; use tempfile::TempDir; +use uucore::parse_size::parse_size; use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Run COMMAND, with modified buffering operations for its standard streams.\n\n\ Mandatory arguments to long options are mandatory for short options too."; -static LONG_HELP: &str = - "If MODE is 'L' the corresponding stream will be line buffered.\n\ +static LONG_HELP: &str = "If MODE is 'L' the corresponding stream will be line buffered.\n\ This option is invalid with standard input.\n\n\ If MODE is '0' the corresponding stream will be unbuffered.\n\n\ Otherwise MODE is a number which may be followed by one of the following:\n\n\ @@ -57,7 +57,7 @@ const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf enum BufferType { Default, Line, - Size(u64), + Size(usize), } struct ProgramOptions { @@ -106,41 +106,6 @@ fn preload_strings() -> (&'static str, &'static str) { crash!(1, "Command not supported for this operating system!") } -fn parse_size(size: &str) -> Option { - let ext = size.trim_start_matches(|c: char| c.is_digit(10)); - let num = size.trim_end_matches(char::is_alphabetic); - let mut recovered = num.to_owned(); - recovered.push_str(ext); - if recovered != size { - return None; - } - let buf_size: u64 = match num.parse().ok() { - Some(m) => m, - None => return None, - }; - let (power, base): (u32, u64) = match ext { - "" => (0, 0), - "KB" => (1, 1024), - "K" => (1, 1000), - "MB" => (2, 1024), - "M" => (2, 1000), - "GB" => (3, 1024), - "G" => (3, 1000), - "TB" => (4, 1024), - "T" => (4, 1000), - "PB" => (5, 1024), - "P" => (5, 1000), - "EB" => (6, 1024), - "E" => (6, 1000), - "ZB" => (7, 1024), - "Z" => (7, 1000), - "YB" => (8, 1024), - "Y" => (8, 1000), - _ => return None, - }; - Some(buf_size * base.pow(power)) -} - fn check_option(matches: &ArgMatches, name: &str) -> Result { match matches.value_of(name) { Some(value) => match value { @@ -155,8 +120,8 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result { let size = match parse_size(x) { - Some(m) => m, - None => return Err(ProgramOptionsError(format!("invalid mode {}", x))), + Ok(m) => m, + Err(e) => return Err(ProgramOptionsError(format!("invalid mode {}", e))), }; Ok(BufferType::Size(size)) } diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index 2e09601ce..e5d784edb 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -57,8 +57,15 @@ fn test_stdbuf_line_buffering_stdin_fails() { #[cfg(not(target_os = "windows"))] #[test] fn test_stdbuf_invalid_mode_fails() { + // TODO: GNU's `stdbuf` (8.32) does not return "\nTry 'stdbuf --help' for more information." + // for invalid modes. new_ucmd!() .args(&["-i", "1024R", "head"]) .fails() - .stderr_is("stdbuf: invalid mode 1024R\nTry 'stdbuf --help' for more information."); + .stderr_contains("stdbuf: invalid mode ‘1024R’"); + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .args(&["--error", "1Y", "head"]) + .fails() + .stderr_contains("stdbuf: invalid mode ‘1Y’: Value too large to be stored in data type"); } From 3a6605844ffe0db223e086b77c408535a32ec078 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 31 May 2021 09:54:31 +0200 Subject: [PATCH 006/320] uucore: move 'parse_time' to 'parser' "parse_time" only uses stdlib and does not need to be feature gated. A more suitable place is the newly created "src/uucore/src/lib/parser/" --- src/uu/sleep/Cargo.toml | 2 +- src/uu/timeout/Cargo.toml | 2 +- src/uucore/Cargo.toml | 1 - src/uucore/src/lib/features.rs | 2 -- src/uucore/src/lib/lib.rs | 3 +-- src/uucore/src/lib/parser.rs | 1 + src/uucore/src/lib/{features => parser}/parse_time.rs | 0 7 files changed, 4 insertions(+), 7 deletions(-) rename src/uucore/src/lib/{features => parser}/parse_time.rs (100%) diff --git a/src/uu/sleep/Cargo.toml b/src/uu/sleep/Cargo.toml index fe7ee2941..618ea7e28 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -16,7 +16,7 @@ path = "src/sleep.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index 206a98c08..5116a163c 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -18,7 +18,7 @@ path = "src/timeout.rs" clap = "2.33" getopts = "0.2.18" libc = "0.2.42" -uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time", "process", "signals"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["process", "signals"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 482252680..0c11d2c15 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -44,7 +44,6 @@ entries = ["libc"] fs = ["libc"] fsext = ["libc", "time"] mode = ["libc"] -parse_time = [] perms = ["libc"] process = ["libc"] ringbuffer = [] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 310a41fe1..c1e1ec31e 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -6,8 +6,6 @@ pub mod encoding; pub mod fs; #[cfg(feature = "fsext")] pub mod fsext; -#[cfg(feature = "parse_time")] -pub mod parse_time; #[cfg(feature = "ringbuffer")] pub mod ringbuffer; #[cfg(feature = "zero-copy")] diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index a60af57fa..69819fd05 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -33,6 +33,7 @@ pub use crate::mods::ranges; // * string parsing modules pub use crate::parser::parse_size; +pub use crate::parser::parse_time; // * feature-gated modules #[cfg(feature = "encoding")] @@ -41,8 +42,6 @@ pub use crate::features::encoding; pub use crate::features::fs; #[cfg(feature = "fsext")] pub use crate::features::fsext; -#[cfg(feature = "parse_time")] -pub use crate::features::parse_time; #[cfg(feature = "ringbuffer")] pub use crate::features::ringbuffer; #[cfg(feature = "zero-copy")] diff --git a/src/uucore/src/lib/parser.rs b/src/uucore/src/lib/parser.rs index 21adefa1a..d09777e10 100644 --- a/src/uucore/src/lib/parser.rs +++ b/src/uucore/src/lib/parser.rs @@ -1 +1,2 @@ pub mod parse_size; +pub mod parse_time; diff --git a/src/uucore/src/lib/features/parse_time.rs b/src/uucore/src/lib/parser/parse_time.rs similarity index 100% rename from src/uucore/src/lib/features/parse_time.rs rename to src/uucore/src/lib/parser/parse_time.rs From f9a088cecf4beb8138a61849ed5273bdd2b3a428 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 31 May 2021 15:22:37 +0200 Subject: [PATCH 007/320] du: use "parse_size" from uucore * fix stderr to be the same than GNU's `du` in case of invalid SIZE --- src/uu/du/src/du.rs | 101 +++++++++++---------------------------- tests/by-util/test_du.rs | 17 +++++++ 2 files changed, 45 insertions(+), 73 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 6bd4f23e4..8394741cc 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -14,6 +14,7 @@ use chrono::prelude::DateTime; use chrono::Local; use clap::{App, Arg}; use std::collections::HashSet; +use std::convert::TryFrom; use std::env; use std::fs; use std::io::{stderr, ErrorKind, Result, Write}; @@ -26,6 +27,7 @@ use std::os::windows::fs::MetadataExt; use std::os::windows::io::AsRawHandle; use std::path::PathBuf; use std::time::{Duration, UNIX_EPOCH}; +use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::InvalidEncodingHandling; #[cfg(windows)] use winapi::shared::minwindef::{DWORD, LPVOID}; @@ -211,64 +213,31 @@ fn get_file_info(path: &PathBuf) -> Option { result } -fn unit_string_to_number(s: &str) -> Option { - let mut offset = 0; - let mut s_chars = s.chars().rev(); - - let (mut ch, multiple) = match s_chars.next() { - Some('B') | Some('b') => ('B', 1000u64), - Some(ch) => (ch, 1024u64), - None => return None, - }; - if ch == 'B' { - ch = s_chars.next()?; - offset += 1; - } - ch = ch.to_ascii_uppercase(); - - let unit = UNITS - .iter() - .rev() - .find(|&&(unit_ch, _)| unit_ch == ch) - .map(|&(_, val)| { - // we found a match, so increment offset - offset += 1; - val - }) - .or_else(|| if multiple == 1024 { Some(0) } else { None })?; - - let number = s[..s.len() - offset].parse::().ok()?; - - Some(number * multiple.pow(unit)) -} - -fn translate_to_pure_number(s: &Option<&str>) -> Option { - match *s { - Some(ref s) => unit_string_to_number(s), - None => None, - } -} - -fn read_block_size(s: Option<&str>) -> u64 { - match translate_to_pure_number(&s) { - Some(v) => v, - None => { - if let Some(value) = s { - show_error!("invalid --block-size argument '{}'", value); - }; - - for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { - let env_size = env::var(env_var).ok(); - if let Some(quantity) = translate_to_pure_number(&env_size.as_deref()) { - return quantity; +fn read_block_size(s: Option<&str>) -> usize { + if let Some(size_arg) = s { + match parse_size(size_arg) { + Ok(v) => v, + Err(e) => match e { + ParseSizeError::ParseFailure(_) => { + crash!(1, "invalid suffix in --block-size argument '{}'", size_arg) + } + ParseSizeError::SizeTooBig(_) => { + crash!(1, "--block-size argument '{}' too large", size_arg) + } + }, + } + } else { + for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { + if let Ok(env_size) = env::var(env_var) { + if let Ok(v) = parse_size(&env_size) { + return v; } } - - if env::var("POSIXLY_CORRECT").is_ok() { - 512 - } else { - 1024 - } + } + if env::var("POSIXLY_CORRECT").is_ok() { + 512 + } else { + 1024 } } } @@ -595,7 +564,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } }; - let block_size = read_block_size(matches.value_of(options::BLOCK_SIZE)); + let block_size = u64::try_from(read_block_size(matches.value_of(options::BLOCK_SIZE))).unwrap(); let multiplier: u64 = if matches.is_present(options::SI) { 1000 @@ -733,26 +702,12 @@ mod test_du { #[allow(unused_imports)] use super::*; - #[test] - fn test_translate_to_pure_number() { - let test_data = [ - (Some("10".to_string()), Some(10)), - (Some("10K".to_string()), Some(10 * 1024)), - (Some("5M".to_string()), Some(5 * 1024 * 1024)), - (Some("900KB".to_string()), Some(900 * 1000)), - (Some("BAD_STRING".to_string()), None), - ]; - for it in test_data.iter() { - assert_eq!(translate_to_pure_number(&it.0.as_deref()), it.1); - } - } - #[test] fn test_read_block_size() { let test_data = [ - (Some("10".to_string()), 10), + (Some("1024".to_string()), 1024), + (Some("K".to_string()), 1024), (None, 1024), - (Some("BAD_STRING".to_string()), 1024), ]; for it in test_data.iter() { assert_eq!(read_block_size(it.0.as_deref()), it.1); diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index c5d262c3b..a8a3049f7 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -71,6 +71,23 @@ fn _du_basics_subdir(s: &str) { } } +#[test] +fn test_du_invalid_size() { + new_ucmd!() + .arg("--block-size=1fb4t") + .arg("/tmp") + .fails() + .code_is(1) + .stderr_only("du: invalid suffix in --block-size argument '1fb4t'"); + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .arg("--block-size=1Y") + .arg("/tmp") + .fails() + .code_is(1) + .stderr_only("du: --block-size argument '1Y' too large"); +} + #[test] fn test_du_basics_bad_name() { new_ucmd!() From 84f2bff778b93dcc7a8ae401db1eb62aa723f8a2 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Tue, 1 Jun 2021 09:30:43 +0200 Subject: [PATCH 008/320] head: use "parse_size" from uucore --- src/uu/head/src/head.rs | 9 +-- src/uu/head/src/parse.rs | 135 +++++-------------------------------- tests/by-util/test_head.rs | 25 +++++++ 3 files changed, 42 insertions(+), 127 deletions(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 3602b4a73..78d769020 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -107,12 +107,7 @@ where { match parse::parse_num(src) { Ok((n, last)) => Ok((closure(n), last)), - Err(reason) => match reason { - parse::ParseError::Syntax => Err(format!("'{}'", src)), - parse::ParseError::Overflow => { - Err(format!("'{}': Value too large for defined datatype", src)) - } - }, + Err(e) => Err(e.to_string()), } } @@ -473,7 +468,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let args = match HeadOptions::get_from(args) { Ok(o) => o, Err(s) => { - crash!(EXIT_FAILURE, "head: {}", s); + crash!(EXIT_FAILURE, "{}", s); } }; match uu_head(&args) { diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 0cf20be42..3b788f819 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -1,5 +1,5 @@ -use std::convert::TryFrom; use std::ffi::OsString; +use uucore::parse_size::{parse_size, ParseSizeError}; #[derive(PartialEq, Debug)] pub enum ParseError { @@ -92,92 +92,25 @@ pub fn parse_obsolete(src: &str) -> Option } /// Parses an -c or -n argument, /// the bool specifies whether to read from the end -pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> { - let mut num_start = 0; - let mut chars = src.char_indices(); - let (mut chars, all_but_last) = match chars.next() { - Some((_, c)) => { - if c == '-' { - num_start += 1; - (chars, true) - } else { - (src.char_indices(), false) - } - } - None => return Err(ParseError::Syntax), - }; - let mut num_end = 0usize; - let mut last_char = 0 as char; - let mut num_count = 0usize; - for (n, c) in &mut chars { - if c.is_numeric() { - num_end = n; - num_count += 1; - } else { - last_char = c; - break; +pub fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> { + let mut size_string = src.trim(); + let mut all_but_last = false; + + if let Some(c) = size_string.chars().next() { + if c == '-' { + size_string = &size_string[1..]; + all_but_last = true; } + } else { + return Err(ParseSizeError::ParseFailure(src.to_string())); } - let num = if num_count > 0 { - match src[num_start..=num_end].parse::() { - Ok(n) => Some(n), - Err(_) => return Err(ParseError::Overflow), - } - } else { - None - }; - - if last_char == 0 as char { - if let Some(n) = num { - Ok((n, all_but_last)) - } else { - Err(ParseError::Syntax) - } - } else { - let base: u128 = match chars.next() { - Some((_, c)) => { - let b = match c { - 'B' if last_char != 'b' => 1000, - 'i' if last_char != 'b' => { - if let Some((_, 'B')) = chars.next() { - 1024 - } else { - return Err(ParseError::Syntax); - } - } - _ => return Err(ParseError::Syntax), - }; - if chars.next().is_some() { - return Err(ParseError::Syntax); - } else { - b - } - } - None => 1024, - }; - let mul = match last_char.to_lowercase().next().unwrap() { - 'b' => 512, - 'k' => base.pow(1), - 'm' => base.pow(2), - 'g' => base.pow(3), - 't' => base.pow(4), - 'p' => base.pow(5), - 'e' => base.pow(6), - 'z' => base.pow(7), - 'y' => base.pow(8), - _ => return Err(ParseError::Syntax), - }; - let mul = match usize::try_from(mul) { - Ok(n) => n, - Err(_) => return Err(ParseError::Overflow), - }; - match num.unwrap_or(1).checked_mul(mul) { - Some(n) => Ok((n, all_but_last)), - None => Err(ParseError::Overflow), - } + match parse_size(&size_string) { + Ok(n) => Ok((n, all_but_last)), + Err(e) => Err(e), } } + #[cfg(test)] mod tests { use super::*; @@ -195,44 +128,6 @@ mod tests { Some(Ok(src.iter().map(|s| s.to_string()).collect())) } #[test] - #[cfg(not(target_pointer_width = "128"))] - fn test_parse_overflow_x64() { - assert_eq!(parse_num("1Y"), Err(ParseError::Overflow)); - assert_eq!(parse_num("1Z"), Err(ParseError::Overflow)); - assert_eq!(parse_num("100E"), Err(ParseError::Overflow)); - assert_eq!(parse_num("100000P"), Err(ParseError::Overflow)); - assert_eq!(parse_num("1000000000T"), Err(ParseError::Overflow)); - assert_eq!( - parse_num("10000000000000000000000"), - Err(ParseError::Overflow) - ); - } - #[test] - #[cfg(target_pointer_width = "32")] - fn test_parse_overflow_x32() { - assert_eq!(parse_num("1T"), Err(ParseError::Overflow)); - assert_eq!(parse_num("1000G"), Err(ParseError::Overflow)); - } - #[test] - fn test_parse_bad_syntax() { - assert_eq!(parse_num("5MiB nonsense"), Err(ParseError::Syntax)); - assert_eq!(parse_num("Nonsense string"), Err(ParseError::Syntax)); - assert_eq!(parse_num("5mib"), Err(ParseError::Syntax)); - assert_eq!(parse_num("biB"), Err(ParseError::Syntax)); - assert_eq!(parse_num("-"), Err(ParseError::Syntax)); - assert_eq!(parse_num(""), Err(ParseError::Syntax)); - } - #[test] - fn test_parse_numbers() { - assert_eq!(parse_num("k"), Ok((1024, false))); - assert_eq!(parse_num("MiB"), Ok((1024 * 1024, false))); - assert_eq!(parse_num("-5"), Ok((5, true))); - assert_eq!(parse_num("b"), Ok((512, false))); - assert_eq!(parse_num("-2GiB"), Ok((2 * 1024 * 1024 * 1024, true))); - assert_eq!(parse_num("5M"), Ok((5 * 1024 * 1024, false))); - assert_eq!(parse_num("5MB"), Ok((5 * 1000 * 1000, false))); - } - #[test] fn test_parse_numbers_obsolete() { assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"])); assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"])); diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index cf7c9c2ee..349fc05d3 100755 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -242,3 +242,28 @@ hello ", ); } +#[test] +fn test_head_invalid_num() { + new_ucmd!() + .args(&["-c", "1024R", "emptyfile.txt"]) + .fails() + .stderr_is("head: invalid number of bytes: ‘1024R’"); + new_ucmd!() + .args(&["-n", "1024R", "emptyfile.txt"]) + .fails() + .stderr_is("head: invalid number of lines: ‘1024R’"); + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .args(&["-c", "1Y", "emptyfile.txt"]) + .fails() + .stderr_is( + "head: invalid number of bytes: ‘1Y’: Value too large to be stored in data type", + ); + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .args(&["-n", "1Y", "emptyfile.txt"]) + .fails() + .stderr_is( + "head: invalid number of lines: ‘1Y’: Value too large to be stored in data type", + ); +} From a3e047ff166205b066174032274594a2eeab2a50 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Tue, 1 Jun 2021 10:22:44 +0200 Subject: [PATCH 009/320] uucore: add more tests to parse_size --- src/uucore/src/lib/parser/parse_size.rs | 32 ++++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index e8ede8cad..4339cd7fb 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -53,14 +53,14 @@ pub fn parse_size(size: &str) -> Result { let (base, exponent): (u128, u32) = match unit { "" => (1, 0), "b" => (512, 1), // (`head` and `tail` use "b") - "KiB" | "K" | "k" => (1024, 1), - "MiB" | "M" | "m" => (1024, 2), - "GiB" | "G" | "g" => (1024, 3), - "TiB" | "T" | "t" => (1024, 4), - "PiB" | "P" | "p" => (1024, 5), - "EiB" | "E" | "e" => (1024, 6), - "ZiB" | "Z" | "z" => (1024, 7), - "YiB" | "Y" | "y" => (1024, 8), + "KiB" | "kiB" | "K" | "k" => (1024, 1), + "MiB" | "miB" | "M" | "m" => (1024, 2), + "GiB" | "giB" | "G" | "g" => (1024, 3), + "TiB" | "tiB" | "T" | "t" => (1024, 4), + "PiB" | "piB" | "P" | "p" => (1024, 5), + "EiB" | "eiB" | "E" | "e" => (1024, 6), + "ZiB" | "ziB" | "Z" | "z" => (1024, 7), + "YiB" | "yiB" | "Y" | "y" => (1024, 8), "KB" | "kB" => (1000, 1), "MB" | "mB" => (1000, 2), "GB" | "gB" => (1000, 3), @@ -152,19 +152,23 @@ mod tests { for &(c, exp) in &suffixes { let s = format!("2{}B", c); // KB - assert_eq!(Ok((2 * (1000 as u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok((2 * (1000_u128).pow(exp)) as usize), parse_size(&s)); let s = format!("2{}", c); // K - assert_eq!(Ok((2 * (1024 as u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s)); let s = format!("2{}iB", c); // KiB - assert_eq!(Ok((2 * (1024 as u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s)); + let s = format!("2{}iB", c.to_lowercase()); // kiB + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s)); // suffix only let s = format!("{}B", c); // KB - assert_eq!(Ok(((1000 as u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok(((1000_u128).pow(exp)) as usize), parse_size(&s)); let s = format!("{}", c); // K - assert_eq!(Ok(((1024 as u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s)); let s = format!("{}iB", c); // KiB - assert_eq!(Ok(((1024 as u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s)); + let s = format!("{}iB", c.to_lowercase()); // kiB + assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s)); } } From 3c7175f00d784768a793a4141c067650bb0ef150 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Tue, 1 Jun 2021 12:17:11 +0200 Subject: [PATCH 010/320] head/tail: add fixes and tests for bytes/lines NUM arg (undocumented sign) * change tail bytes/lines clap parsing to fix posix override behavior * change tail bytes/lines NUM parsing logic to be consistent with head --- src/uu/head/src/parse.rs | 7 ++-- src/uu/tail/src/tail.rs | 74 ++++++++++++++++++++------------------ tests/by-util/test_head.rs | 22 ++++++++++++ tests/by-util/test_tail.rs | 48 +++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 36 deletions(-) diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 3b788f819..b395b330b 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -97,9 +97,12 @@ pub fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> { let mut all_but_last = false; if let Some(c) = size_string.chars().next() { - if c == '-' { + if c == '+' || c == '-' { + // head: '+' is not documented (8.32 man pages) size_string = &size_string[1..]; - all_but_last = true; + if c == '-' { + all_but_last = true; + } } } else { return Err(ParseSizeError::ParseFailure(src.to_string())); diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index a4634714c..acaad8c30 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -27,7 +27,7 @@ use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write}; use std::path::Path; use std::thread::sleep; use std::time::Duration; -use uucore::parse_size::parse_size; +use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::ringbuffer::RingBuffer; pub mod options { @@ -42,10 +42,9 @@ pub mod options { pub static PID: &str = "pid"; pub static SLEEP_INT: &str = "sleep-interval"; pub static ZERO_TERM: &str = "zero-terminated"; + pub static ARG_FILES: &str = "files"; } -static ARG_FILES: &str = "files"; - enum FilterMode { Bytes(usize), Lines(usize, u8), // (number of lines, delimiter) @@ -84,6 +83,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::BYTES) .takes_value(true) .allow_hyphen_values(true) + .overrides_with_all(&[options::BYTES, options::LINES]) .help("Number of bytes to print"), ) .arg( @@ -98,6 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::LINES) .takes_value(true) .allow_hyphen_values(true) + .overrides_with_all(&[options::BYTES, options::LINES]) .help("Number of lines to print"), ) .arg( @@ -137,7 +138,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("Line delimiter is NUL, not newline"), ) .arg( - Arg::with_name(ARG_FILES) + Arg::with_name(options::ARG_FILES) .multiple(true) .takes_value(true) .min_values(1), @@ -171,38 +172,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } - match matches.value_of(options::LINES) { - Some(n) => { - let mut slice: &str = n; - let c = slice.chars().next().unwrap_or('_'); - if c == '+' || c == '-' { - slice = &slice[1..]; - if c == '+' { - settings.beginning = true; - } - } - match parse_size(slice) { - Ok(m) => settings.mode = FilterMode::Lines(m, b'\n'), - Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()), - } + let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) { + match parse_num(arg) { + Ok((n, beginning)) => (FilterMode::Bytes(n), beginning), + Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()), } - None => { - if let Some(n) = matches.value_of(options::BYTES) { - let mut slice: &str = n; - let c = slice.chars().next().unwrap_or('_'); - if c == '+' || c == '-' { - slice = &slice[1..]; - if c == '+' { - settings.beginning = true; - } - } - match parse_size(slice) { - Ok(m) => settings.mode = FilterMode::Bytes(m), - Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()), - } - } + } else if let Some(arg) = matches.value_of(options::LINES) { + match parse_num(arg) { + Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning), + Err(e) => crash!(1, "invalid number of lines: {}", e.to_string()), } + } else { + (FilterMode::Lines(10, b'\n'), false) }; + settings.mode = mode_and_beginning.0; + settings.beginning = mode_and_beginning.1; if matches.is_present(options::ZERO_TERM) { if let FilterMode::Lines(count, _) = settings.mode { @@ -215,7 +199,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { || matches.is_present(options::verbosity::SILENT); let files: Vec = matches - .values_of(ARG_FILES) + .values_of(options::ARG_FILES) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); @@ -422,3 +406,25 @@ fn print_byte(stdout: &mut T, ch: u8) { crash!(1, "{}", err); } } + +fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> { + let mut size_string = src.trim(); + let mut starting_with = false; + + if let Some(c) = size_string.chars().next() { + if c == '+' || c == '-' { + // tail: '-' is not documented (8.32 man pages) + size_string = &size_string[1..]; + if c == '+' { + starting_with = true; + } + } + } else { + return Err(ParseSizeError::ParseFailure(src.to_string())); + } + + match parse_size(&size_string) { + Ok(n) => Ok((n, starting_with)), + Err(e) => Err(e), + } +} diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 349fc05d3..f26447636 100755 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -267,3 +267,25 @@ fn test_head_invalid_num() { "head: invalid number of lines: ‘1Y’: Value too large to be stored in data type", ); } + +#[test] +fn test_head_num_with_undocumented_sign_bytes() { + // tail: '-' is not documented (8.32 man pages) + // head: '+' is not documented (8.32 man pages) + const ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz"; + new_ucmd!() + .args(&["-c", "5"]) + .pipe_in(ALPHABET) + .succeeds() + .stdout_is("abcde"); + new_ucmd!() + .args(&["-c", "-5"]) + .pipe_in(ALPHABET) + .succeeds() + .stdout_is("abcdefghijklmnopqrstu"); + new_ucmd!() + .args(&["-c", "+5"]) + .pipe_in(ALPHABET) + .succeeds() + .stdout_is("abcde"); +} diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 6227ac60b..9d0462c7a 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -352,3 +352,51 @@ fn test_positive_zero_lines() { .succeeds() .stdout_is("a\nb\nc\nd\ne\n"); } + +#[test] +fn test_tail_invalid_num() { + new_ucmd!() + .args(&["-c", "1024R", "emptyfile.txt"]) + .fails() + .stderr_is("tail: invalid number of bytes: ‘1024R’"); + new_ucmd!() + .args(&["-n", "1024R", "emptyfile.txt"]) + .fails() + .stderr_is("tail: invalid number of lines: ‘1024R’"); + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .args(&["-c", "1Y", "emptyfile.txt"]) + .fails() + .stderr_is( + "tail: invalid number of bytes: ‘1Y’: Value too large to be stored in data type", + ); + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .args(&["-n", "1Y", "emptyfile.txt"]) + .fails() + .stderr_is( + "tail: invalid number of lines: ‘1Y’: Value too large to be stored in data type", + ); +} + +#[test] +fn test_tail_num_with_undocumented_sign_bytes() { + // tail: '-' is not documented (8.32 man pages) + // head: '+' is not documented (8.32 man pages) + const ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz"; + new_ucmd!() + .args(&["-c", "5"]) + .pipe_in(ALPHABET) + .succeeds() + .stdout_is("vwxyz"); + new_ucmd!() + .args(&["-c", "-5"]) + .pipe_in(ALPHABET) + .succeeds() + .stdout_is("vwxyz"); + new_ucmd!() + .args(&["-c", "+5"]) + .pipe_in(ALPHABET) + .succeeds() + .stdout_is("efghijklmnopqrstuvwxyz"); +} From a900c7421aaeef2bf4cb225409b3ccd6d8926a9f Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Tue, 1 Jun 2021 22:07:29 +0200 Subject: [PATCH 011/320] od: use "parse_size" from uucore --- src/uu/od/src/parse_nrofbytes.rs | 51 +++---------------------- src/uucore/src/lib/parser/parse_size.rs | 40 ++++++++++++++++++- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/uu/od/src/parse_nrofbytes.rs b/src/uu/od/src/parse_nrofbytes.rs index d2ba1527b..9223d7e53 100644 --- a/src/uu/od/src/parse_nrofbytes.rs +++ b/src/uu/od/src/parse_nrofbytes.rs @@ -1,14 +1,18 @@ pub fn parse_number_of_bytes(s: &str) -> Result { let mut start = 0; let mut len = s.len(); - let mut radix = 10; + let mut radix = 16; let mut multiply = 1; if s.starts_with("0x") || s.starts_with("0X") { start = 2; - radix = 16; } else if s.starts_with('0') { radix = 8; + } else { + return match uucore::parse_size::parse_size(&s[start..]) { + Ok(n) => Ok(n), + Err(_) => Err("parse failed"), + }; } let mut ends_with = s.chars().rev(); @@ -75,22 +79,6 @@ fn parse_number_of_bytes_str(s: &str) -> Result { #[test] fn test_parse_number_of_bytes() { - // normal decimal numbers - assert_eq!(0, parse_number_of_bytes_str("0").unwrap()); - assert_eq!(5, parse_number_of_bytes_str("5").unwrap()); - assert_eq!(999, parse_number_of_bytes_str("999").unwrap()); - assert_eq!(2 * 512, parse_number_of_bytes_str("2b").unwrap()); - assert_eq!(2 * 1024, parse_number_of_bytes_str("2k").unwrap()); - assert_eq!(4 * 1024, parse_number_of_bytes_str("4K").unwrap()); - assert_eq!(2 * 1048576, parse_number_of_bytes_str("2m").unwrap()); - assert_eq!(4 * 1048576, parse_number_of_bytes_str("4M").unwrap()); - assert_eq!(1073741824, parse_number_of_bytes_str("1G").unwrap()); - assert_eq!(2000, parse_number_of_bytes_str("2kB").unwrap()); - assert_eq!(4000, parse_number_of_bytes_str("4KB").unwrap()); - assert_eq!(2000000, parse_number_of_bytes_str("2mB").unwrap()); - assert_eq!(4000000, parse_number_of_bytes_str("4MB").unwrap()); - assert_eq!(2000000000, parse_number_of_bytes_str("2GB").unwrap()); - // octal input assert_eq!(8, parse_number_of_bytes_str("010").unwrap()); assert_eq!(8 * 512, parse_number_of_bytes_str("010b").unwrap()); @@ -103,31 +91,4 @@ fn test_parse_number_of_bytes() { assert_eq!(27, parse_number_of_bytes_str("0x1b").unwrap()); assert_eq!(16 * 1024, parse_number_of_bytes_str("0x10k").unwrap()); assert_eq!(16 * 1048576, parse_number_of_bytes_str("0x10m").unwrap()); - - // invalid input - parse_number_of_bytes_str("").unwrap_err(); - parse_number_of_bytes_str("-1").unwrap_err(); - parse_number_of_bytes_str("1e2").unwrap_err(); - parse_number_of_bytes_str("xyz").unwrap_err(); - parse_number_of_bytes_str("b").unwrap_err(); - parse_number_of_bytes_str("1Y").unwrap_err(); - parse_number_of_bytes_str("∞").unwrap_err(); -} - -#[test] -#[cfg(target_pointer_width = "64")] -fn test_parse_number_of_bytes_64bits() { - assert_eq!(1099511627776, parse_number_of_bytes_str("1T").unwrap()); - assert_eq!(1125899906842624, parse_number_of_bytes_str("1P").unwrap()); - assert_eq!( - 1152921504606846976, - parse_number_of_bytes_str("1E").unwrap() - ); - - assert_eq!(2000000000000, parse_number_of_bytes_str("2TB").unwrap()); - assert_eq!(2000000000000000, parse_number_of_bytes_str("2PB").unwrap()); - assert_eq!( - 2000000000000000000, - parse_number_of_bytes_str("2EB").unwrap() - ); } diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 4339cd7fb..8b3e0bf03 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -52,7 +52,7 @@ pub fn parse_size(size: &str) -> Result { let unit = &size[numeric_string.len()..]; let (base, exponent): (u128, u32) = match unit { "" => (1, 0), - "b" => (512, 1), // (`head` and `tail` use "b") + "b" => (512, 1), // (`od`, `head` and `tail` use "b") "KiB" | "kiB" | "K" | "k" => (1024, 1), "MiB" | "miB" | "M" | "m" => (1024, 2), "GiB" | "giB" | "G" | "g" => (1024, 3), @@ -210,7 +210,18 @@ mod tests { #[test] fn invalid_syntax() { - let test_strings = ["328hdsf3290", "5MiB nonsense", "5mib", "biB", "-", ""]; + let test_strings = [ + "328hdsf3290", + "5MiB nonsense", + "5mib", + "biB", + "-", + "+", + "", + "-1", + "1e2", + "∞", + ]; for &test_string in &test_strings { assert_eq!( parse_size(test_string).unwrap_err(), @@ -228,6 +239,8 @@ mod tests { fn no_suffix() { assert_eq!(Ok(1234), parse_size("1234")); assert_eq!(Ok(0), parse_size("0")); + assert_eq!(Ok(5), parse_size("5")); + assert_eq!(Ok(999), parse_size("999")); } #[test] @@ -239,6 +252,8 @@ mod tests { assert_eq!(Ok(0), parse_size("0KB")); assert_eq!(Ok(1000), parse_size("KB")); assert_eq!(Ok(1024), parse_size("K")); + assert_eq!(Ok(2000), parse_size("2kB")); + assert_eq!(Ok(4000), parse_size("4KB")); } #[test] @@ -247,5 +262,26 @@ mod tests { assert_eq!(Ok(123 * 1000 * 1000), parse_size("123MB")); assert_eq!(Ok(1024 * 1024), parse_size("M")); assert_eq!(Ok(1000 * 1000), parse_size("MB")); + assert_eq!(Ok(2 * 1_048_576), parse_size("2m")); + assert_eq!(Ok(4 * 1_048_576), parse_size("4M")); + assert_eq!(Ok(2_000_000), parse_size("2mB")); + assert_eq!(Ok(4_000_000), parse_size("4MB")); + } + + #[test] + fn gigabytes_suffix() { + assert_eq!(Ok(1_073_741_824), parse_size("1G")); + assert_eq!(Ok(2_000_000_000), parse_size("2GB")); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn x64() { + assert_eq!(Ok(1_099_511_627_776), parse_size("1T")); + assert_eq!(Ok(1_125_899_906_842_624), parse_size("1P")); + assert_eq!(Ok(1_152_921_504_606_846_976), parse_size("1E")); + assert_eq!(Ok(2_000_000_000_000), parse_size("2TB")); + assert_eq!(Ok(2_000_000_000_000_000), parse_size("2PB")); + assert_eq!(Ok(2_000_000_000_000_000_000), parse_size("2EB")); } } From efe1850087a9c0a2a8b57c2385d09d62d5ba80a6 Mon Sep 17 00:00:00 2001 From: Mitchell Mebane Date: Tue, 1 Jun 2021 19:06:51 -0500 Subject: [PATCH 012/320] dircolors: replace getopts with clap Port argument parsing from getopts to clap. The only difference I have observed is that clap auto-generates -h and -V short options for help and version, and there is no way (in clap 2.x) to disable them. --- Cargo.lock | 1 + src/uu/dircolors/Cargo.toml | 1 + src/uu/dircolors/src/dircolors.rs | 118 +++++++++++++++++++++++------- 3 files changed, 93 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17fa9e2b7..01c154351 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1893,6 +1893,7 @@ dependencies = [ name = "uu_dircolors" version = "0.0.6" dependencies = [ + "clap", "glob 0.3.0", "uucore", "uucore_procs", diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index 5e822820e..7d47fa5c4 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/dircolors.rs" [dependencies] +clap = "2.33" glob = "0.3.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index b6942c2d2..8e13e281c 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -1,6 +1,7 @@ // This file is part of the uutils coreutils package. // // (c) Jian Zeng +// (c) Mitchell Mebane // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -15,6 +16,17 @@ use std::env; use std::fs::File; use std::io::{BufRead, BufReader}; +use clap::{App, Arg, crate_version}; + +mod options { + pub const SH: &str = "sh"; + pub const BOURNE_SHELL: &str = "bourne-shell"; + pub const CSH: &str = "csh"; + pub const C_SHELL: &str = "c-shell"; + pub const PRINT_DATABASE: &str = "print-database"; + pub const FILE: &str = "FILE"; +} + static SYNTAX: &str = "[OPTION]... [FILE]"; static SUMMARY: &str = "Output commands to set the LS_COLORS environment variable."; static LONG_HELP: &str = " @@ -52,28 +64,80 @@ pub fn guess_syntax() -> OutputFmt { } } +fn get_usage() -> String { + format!( + "{0} {1}", + executable!(), + SYNTAX + ) +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optflag("b", "sh", "output Bourne shell code to set LS_COLORS") - .optflag( - "", - "bourne-shell", - "output Bourne shell code to set LS_COLORS", - ) - .optflag("c", "csh", "output C shell code to set LS_COLORS") - .optflag("", "c-shell", "output C shell code to set LS_COLORS") - .optflag("p", "print-database", "print the byte counts") - .parse(args); + let usage = get_usage(); - if (matches.opt_present("csh") - || matches.opt_present("c-shell") - || matches.opt_present("sh") - || matches.opt_present("bourne-shell")) - && matches.opt_present("print-database") + /* Clap has .visible_alias, but it generates help like this + * -b, --sh output Bourne shell code to set LS_COLORS [aliases: bourne-shell] + * whereas we want help like this + * -b, --sh output Bourne shell code to set LS_COLORS + * --bourne-shell output Bourne shell code to set LS_COLORS + * (or preferably like the original, but that doesn't seem possible with clap:) + * -b, --sh, --bourne-shell output Bourne shell code to set LS_COLORS + * therefore, command aliases are defined manually as multiple commands + */ + let matches = App::new(executable!()) + .version(crate_version!()) + .about(SUMMARY) + .usage(&usage[..]) + .after_help(LONG_HELP) + .arg(Arg::with_name(options::SH) + .long("sh") + .short("b") + .help("output Bourne shell code to set LS_COLORS") + .display_order(1) + ) + .arg(Arg::with_name(options::BOURNE_SHELL) + .long("bourne-shell") + .help("output Bourne shell code to set LS_COLORS") + .display_order(2) + ) + .arg(Arg::with_name(options::CSH) + .long("csh") + .short("c") + .help("output C shell code to set LS_COLORS") + .display_order(3) + ) + .arg(Arg::with_name(options::C_SHELL) + .long("c-shell") + .help("output C shell code to set LS_COLORS") + .display_order(4) + ) + .arg(Arg::with_name(options::PRINT_DATABASE) + .long("print-database") + .short("p") + .help("print the byte counts") + .display_order(5) + ) + .arg(Arg::with_name(options::FILE) + .hidden(true) + .multiple(true) + ) + .get_matches_from(&args); + + let files = matches.values_of(options::FILE) + .map_or(vec![], |file_values| file_values.collect()); + + // clap provides .conflicts_with / .conflicts_with_all, but we want to + // manually handle conflicts so we can match the output of GNU coreutils + if (matches.is_present(options::CSH) + || matches.is_present(options::C_SHELL) + || matches.is_present(options::SH) + || matches.is_present(options::BOURNE_SHELL) + ) + && matches.is_present(options::PRINT_DATABASE) { show_usage_error!( "the options to output dircolors' internal database and\nto select a shell \ @@ -82,12 +146,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return 1; } - if matches.opt_present("print-database") { - if !matches.free.is_empty() { + if matches.is_present(options::PRINT_DATABASE) { + if !files.is_empty() { show_usage_error!( "extra operand ‘{}’\nfile operands cannot be combined with \ --print-database (-p)", - matches.free[0] + files[0] ); return 1; } @@ -96,9 +160,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } let mut out_format = OutputFmt::Unknown; - if matches.opt_present("csh") || matches.opt_present("c-shell") { + if matches.is_present(options::CSH) || matches.is_present(options::C_SHELL) { out_format = OutputFmt::CShell; - } else if matches.opt_present("sh") || matches.opt_present("bourne-shell") { + } else if matches.is_present(options::SH) || matches.is_present(options::BOURNE_SHELL) { out_format = OutputFmt::Shell; } @@ -113,24 +177,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } let result; - if matches.free.is_empty() { + if files.is_empty() { result = parse(INTERNAL_DB.lines(), out_format, "") } else { - if matches.free.len() > 1 { - show_usage_error!("extra operand ‘{}’", matches.free[1]); + if files.len() > 1 { + show_usage_error!("extra operand ‘{}’", files[1]); return 1; } - match File::open(matches.free[0].as_str()) { + match File::open(files[0]) { Ok(f) => { let fin = BufReader::new(f); result = parse( fin.lines().filter_map(Result::ok), out_format, - matches.free[0].as_str(), + files[0], ) } Err(e) => { - show_error!("{}: {}", matches.free[0], e); + show_error!("{}: {}", files[0], e); return 1; } } From 850a56cceaa758b4122e5b644b28d9bf8aa3bb70 Mon Sep 17 00:00:00 2001 From: Mitchell Mebane Date: Tue, 1 Jun 2021 19:13:20 -0500 Subject: [PATCH 013/320] dircolors: rustfmt --- src/uu/dircolors/src/dircolors.rs | 80 ++++++++++++++----------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 8e13e281c..078270791 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -16,7 +16,7 @@ use std::env; use std::fs::File; use std::io::{BufRead, BufReader}; -use clap::{App, Arg, crate_version}; +use clap::{crate_version, App, Arg}; mod options { pub const SH: &str = "sh"; @@ -65,11 +65,7 @@ pub fn guess_syntax() -> OutputFmt { } fn get_usage() -> String { - format!( - "{0} {1}", - executable!(), - SYNTAX - ) + format!("{0} {1}", executable!(), SYNTAX) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -93,50 +89,52 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .about(SUMMARY) .usage(&usage[..]) .after_help(LONG_HELP) - .arg(Arg::with_name(options::SH) - .long("sh") - .short("b") - .help("output Bourne shell code to set LS_COLORS") - .display_order(1) + .arg( + Arg::with_name(options::SH) + .long("sh") + .short("b") + .help("output Bourne shell code to set LS_COLORS") + .display_order(1), ) - .arg(Arg::with_name(options::BOURNE_SHELL) - .long("bourne-shell") - .help("output Bourne shell code to set LS_COLORS") - .display_order(2) + .arg( + Arg::with_name(options::BOURNE_SHELL) + .long("bourne-shell") + .help("output Bourne shell code to set LS_COLORS") + .display_order(2), ) - .arg(Arg::with_name(options::CSH) - .long("csh") - .short("c") - .help("output C shell code to set LS_COLORS") - .display_order(3) + .arg( + Arg::with_name(options::CSH) + .long("csh") + .short("c") + .help("output C shell code to set LS_COLORS") + .display_order(3), ) - .arg(Arg::with_name(options::C_SHELL) - .long("c-shell") - .help("output C shell code to set LS_COLORS") - .display_order(4) + .arg( + Arg::with_name(options::C_SHELL) + .long("c-shell") + .help("output C shell code to set LS_COLORS") + .display_order(4), ) - .arg(Arg::with_name(options::PRINT_DATABASE) - .long("print-database") - .short("p") - .help("print the byte counts") - .display_order(5) - ) - .arg(Arg::with_name(options::FILE) - .hidden(true) - .multiple(true) + .arg( + Arg::with_name(options::PRINT_DATABASE) + .long("print-database") + .short("p") + .help("print the byte counts") + .display_order(5), ) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) .get_matches_from(&args); - let files = matches.values_of(options::FILE) + let files = matches + .values_of(options::FILE) .map_or(vec![], |file_values| file_values.collect()); // clap provides .conflicts_with / .conflicts_with_all, but we want to // manually handle conflicts so we can match the output of GNU coreutils if (matches.is_present(options::CSH) - || matches.is_present(options::C_SHELL) - || matches.is_present(options::SH) - || matches.is_present(options::BOURNE_SHELL) - ) + || matches.is_present(options::C_SHELL) + || matches.is_present(options::SH) + || matches.is_present(options::BOURNE_SHELL)) && matches.is_present(options::PRINT_DATABASE) { show_usage_error!( @@ -187,11 +185,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match File::open(files[0]) { Ok(f) => { let fin = BufReader::new(f); - result = parse( - fin.lines().filter_map(Result::ok), - out_format, - files[0], - ) + result = parse(fin.lines().filter_map(Result::ok), out_format, files[0]) } Err(e) => { show_error!("{}: {}", files[0], e); From 6b8de1dd8bfd3935467c5c2679c3b2bf0a6478cf Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 2 Jun 2021 04:16:41 +0200 Subject: [PATCH 014/320] sort: use "parse_size" from uucore * make parsing of SIZE argument consistent with GNU's behavior * add error handling * add tests --- src/uu/sort/src/sort.rs | 100 +++++++++++++++++++++++++++---------- tests/by-util/test_sort.rs | 45 +++++++++++++++-- 2 files changed, 115 insertions(+), 30 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index ab3b06451..208010d09 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -43,6 +43,7 @@ use std::ops::Range; use std::path::Path; use std::path::PathBuf; use unicode_width::UnicodeWidthStr; +use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::InvalidEncodingHandling; static NAME: &str = "sort"; @@ -159,32 +160,31 @@ pub struct GlobalSettings { } impl GlobalSettings { - /// Interpret this `&str` as a number with an optional trailing si unit. - /// - /// If there is no trailing si unit, the implicit unit is K. - /// The suffix B causes the number to be interpreted as a byte count. - fn parse_byte_count(input: &str) -> usize { - const SI_UNITS: &[char] = &['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; + /// Parse a SIZE string into a number of bytes. + /// A size string comprises an integer and an optional unit. + /// The unit may be k, K, m, M, g, G, t, T, P, E, Z, Y (powers of 1024), or b which is 1. + /// Default is K. + fn parse_byte_count(input: &str) -> Result { + // GNU sort (8.32) valid: 1b, k, K, m, M, g, G, t, T, P, E, Z, Y + // GNU sort (8.32) invalid: b, B, 1B, p, e, z, y + const ALLOW_LIST: &[char] = &[ + 'b', 'k', 'K', 'm', 'M', 'g', 'G', 't', 'T', 'P', 'E', 'Z', 'Y', + ]; + let mut size_string = input.trim().to_string(); - let input = input.trim(); - - let (num_str, si_unit) = - if input.ends_with(|c: char| SI_UNITS.contains(&c.to_ascii_uppercase())) { - let mut chars = input.chars(); - let si_suffix = chars.next_back().unwrap().to_ascii_uppercase(); - let si_unit = SI_UNITS.iter().position(|&c| c == si_suffix).unwrap(); - let num_str = chars.as_str(); - (num_str, si_unit) - } else { - (input, 1) - }; - - let num_usize: usize = num_str - .trim() - .parse() - .unwrap_or_else(|e| crash!(1, "failed to parse buffer size `{}`: {}", num_str, e)); - - num_usize.saturating_mul(1000usize.saturating_pow(si_unit as u32)) + if size_string.ends_with(|c: char| ALLOW_LIST.contains(&c)) + || size_string.ends_with(|c: char| c.is_digit(10)) + { + // b 1, K 1024 (default) + if size_string.ends_with(|c: char| c.is_digit(10)) { + size_string.push('K'); + } else if size_string.ends_with('b') { + size_string.pop(); + } + parse_size(&size_string) + } else { + Err(ParseSizeError::ParseFailure("invalid suffix".to_string())) + } } fn out_writer(&self) -> BufWriter> { @@ -1148,7 +1148,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.buffer_size = matches .value_of(OPT_BUF_SIZE) - .map(GlobalSettings::parse_byte_count) + .map(|v| match GlobalSettings::parse_byte_count(v) { + Ok(n) => n, + Err(ParseSizeError::ParseFailure(_)) => crash!(2, "invalid -S argument '{}'", v), + Err(ParseSizeError::SizeTooBig(_)) => crash!(2, "-S argument '{}' too large", v), + }) .unwrap_or(DEFAULT_BUF_SIZE); settings.tmp_dir = matches @@ -1640,4 +1644,48 @@ mod tests { // How big is a selection? Constant cost all lines pay when we need selections. assert_eq!(std::mem::size_of::(), 24); } + + #[test] + fn test_parse_byte_count() { + let valid_input = [ + ("0", 0), + ("50K", 50 * 1024), + ("50k", 50 * 1024), + ("1M", 1024 * 1024), + ("100M", 100 * 1024 * 1024), + #[cfg(not(target_pointer_width = "32"))] + ("1000G", 1000 * 1024 * 1024 * 1024), + #[cfg(not(target_pointer_width = "32"))] + ("10T", 10 * 1024 * 1024 * 1024 * 1024), + ("1b", 1), + ("1024b", 1024), + ("1024Mb", 1024 * 1024 * 1024), // TODO: This might not be what GNU `sort` does? + ("1", 1024), // K is default + ("50", 50 * 1024), + ("K", 1024), + ("k", 1024), + ("m", 1024 * 1024), + #[cfg(not(target_pointer_width = "32"))] + ("E", 1024 * 1024 * 1024 * 1024 * 1024 * 1024), + ]; + for (input, expected_output) in &valid_input { + assert_eq!( + GlobalSettings::parse_byte_count(input), + Ok(*expected_output) + ); + } + + // SizeTooBig + let invalid_input = ["500E", "1Y"]; + for input in &invalid_input { + #[cfg(not(target_pointer_width = "128"))] + assert!(GlobalSettings::parse_byte_count(input).is_err()); + } + + // ParseFailure + let invalid_input = ["nonsense", "1B", "B", "b", "p", "e", "z", "y"]; + for input in &invalid_input { + assert!(GlobalSettings::parse_byte_count(input).is_err()); + } + } } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index d2b447925..054789edf 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -21,9 +21,7 @@ fn test_helper(file_name: &str, possible_args: &[&str]) { #[test] fn test_buffer_sizes() { - let buffer_sizes = [ - "0", "50K", "50k", "1M", "100M", "1000G", "10T", "500E", "1Y", - ]; + let buffer_sizes = ["0", "50K", "50k", "1M", "100M"]; for buffer_size in &buffer_sizes { new_ucmd!() .arg("-n") @@ -32,6 +30,20 @@ fn test_buffer_sizes() { .arg("ext_sort.txt") .succeeds() .stdout_is_fixture("ext_sort.expected"); + + #[cfg(not(target_pointer_width = "32"))] + { + let buffer_sizes = ["1000G", "10T"]; + for buffer_size in &buffer_sizes { + new_ucmd!() + .arg("-n") + .arg("-S") + .arg(buffer_size) + .arg("ext_sort.txt") + .succeeds() + .stdout_is_fixture("ext_sort.expected"); + } + } } } @@ -43,11 +55,36 @@ fn test_invalid_buffer_size() { .arg("-S") .arg(invalid_buffer_size) .fails() + .code_is(2) .stderr_only(format!( - "sort: failed to parse buffer size `{}`: invalid digit found in string", + "sort: invalid -S argument '{}'", invalid_buffer_size )); } + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .arg("-n") + .arg("-S") + .arg("1Y") + .arg("ext_sort.txt") + .fails() + .code_is(2) + .stderr_only("sort: -S argument '1Y' too large"); + + #[cfg(target_pointer_width = "32")] + { + let buffer_sizes = ["1000G", "10T"]; + for buffer_size in &buffer_sizes { + new_ucmd!() + .arg("-n") + .arg("-S") + .arg(buffer_size) + .arg("ext_sort.txt") + .fails() + .code_is(2) + .stderr_only(format!("sort: -S argument '{}' too large", buffer_size)); + } + } } #[test] From efa89de4636220d94ba9d077a4f2f0d2f2e64029 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 2 Jun 2021 19:58:29 +0200 Subject: [PATCH 015/320] ln: fix LINK_NAME in help output --- src/uu/ln/src/ln.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 2a14f3c9c..950270872 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -54,7 +54,7 @@ pub enum BackupMode { fn get_usage() -> String { format!( - "{0} [OPTION]... [-T] TARGET LINK_executable!() (1st form) + "{0} [OPTION]... [-T] TARGET LINK_NAME (1st form) {0} [OPTION]... TARGET (2nd form) {0} [OPTION]... TARGET... DIRECTORY (3rd form) {0} [OPTION]... -t DIRECTORY TARGET... (4th form)", @@ -64,7 +64,7 @@ fn get_usage() -> String { fn get_long_usage() -> String { String::from( - " In the 1st form, create a link to TARGET with the name LINK_executable!(). + " In the 1st form, create a link to TARGET with the name LINK_NAME. In the 2nd form, create a link to TARGET in the current directory. In the 3rd and 4th forms, create links to each TARGET in DIRECTORY. Create hard links by default, symbolic links with --symbolic. @@ -144,7 +144,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .short("n") .long(OPT_NO_DEREFERENCE) .help( - "treat LINK_executable!() as a normal file if it is a \ + "treat LINK_NAME as a normal file if it is a \ symbolic link to a directory", ), ) @@ -179,7 +179,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(OPT_NO_TARGET_DIRECTORY) .short("T") .long(OPT_NO_TARGET_DIRECTORY) - .help("treat LINK_executable!() as a normal file always"), + .help("treat LINK_NAME as a normal file always"), ) .arg( Arg::with_name(OPT_RELATIVE) From 87570bbc1041b211b30b1f72fe661d4b497b8da2 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 2 Jun 2021 20:56:37 +0200 Subject: [PATCH 016/320] ln: remove redundant `force` flag This information is already encoded in the `OverwriteMode` enum. --- src/uu/ln/src/ln.rs | 82 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 950270872..0c60405f6 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -27,7 +27,6 @@ use uucore::fs::{canonicalize, CanonicalizeMode}; pub struct Settings { overwrite: OverwriteMode, backup: BackupMode, - force: bool, suffix: String, symbolic: bool, relative: bool, @@ -244,7 +243,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let settings = Settings { overwrite: overwrite_mode, backup: backup_mode, - force: matches.is_present(OPT_FORCE), suffix: backup_suffix.to_string(), symbolic: matches.is_present(OPT_SYMBOLIC), relative: matches.is_present(OPT_RELATIVE), @@ -311,47 +309,48 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) let mut all_successful = true; for srcpath in files.iter() { - let targetpath = if settings.no_dereference && settings.force { - // In that case, we don't want to do link resolution - // We need to clean the target - if is_symlink(target_dir) { - if target_dir.is_file() { - if let Err(e) = fs::remove_file(target_dir) { - show_error!("Could not update {}: {}", target_dir.display(), e) - }; - } - if target_dir.is_dir() { - // Not sure why but on Windows, the symlink can be - // considered as a dir - // See test_ln::test_symlink_no_deref_dir - if let Err(e) = fs::remove_dir(target_dir) { - show_error!("Could not update {}: {}", target_dir.display(), e) - }; - } - } - target_dir.to_path_buf() - } else { - match srcpath.as_os_str().to_str() { - Some(name) => { - match Path::new(name).file_name() { - Some(basename) => target_dir.join(basename), - // This can be None only for "." or "..". Trying - // to create a link with such name will fail with - // EEXIST, which agrees with the behavior of GNU - // coreutils. - None => target_dir.join(name), + let targetpath = + if settings.no_dereference && matches!(settings.overwrite, OverwriteMode::Force) { + // In that case, we don't want to do link resolution + // We need to clean the target + if is_symlink(target_dir) { + if target_dir.is_file() { + if let Err(e) = fs::remove_file(target_dir) { + show_error!("Could not update {}: {}", target_dir.display(), e) + }; + } + if target_dir.is_dir() { + // Not sure why but on Windows, the symlink can be + // considered as a dir + // See test_ln::test_symlink_no_deref_dir + if let Err(e) = fs::remove_dir(target_dir) { + show_error!("Could not update {}: {}", target_dir.display(), e) + }; } } - None => { - show_error!( - "cannot stat '{}': No such file or directory", - srcpath.display() - ); - all_successful = false; - continue; + target_dir.to_path_buf() + } else { + match srcpath.as_os_str().to_str() { + Some(name) => { + match Path::new(name).file_name() { + Some(basename) => target_dir.join(basename), + // This can be None only for "." or "..". Trying + // to create a link with such name will fail with + // EEXIST, which agrees with the behavior of GNU + // coreutils. + None => target_dir.join(name), + } + } + None => { + show_error!( + "cannot stat '{}': No such file or directory", + srcpath.display() + ); + all_successful = false; + continue; + } } - } - }; + }; if let Err(e) = link(srcpath, &targetpath, settings) { show_error!( @@ -422,7 +421,8 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> { } } - if settings.no_dereference && settings.force && dst.exists() { + if settings.no_dereference && matches!(settings.overwrite, OverwriteMode::Force) && dst.exists() + { fs::remove_file(dst)?; } From ed69d797b526bf4a01f676d591a688664cb1b281 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 2 Jun 2021 21:02:12 +0200 Subject: [PATCH 017/320] ln: reject --relative without --symbolic --- src/uu/ln/src/ln.rs | 3 ++- tests/by-util/test_ln.rs | 19 +++++-------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 0c60405f6..4bd3310cc 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -184,7 +184,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(OPT_RELATIVE) .short("r") .long(OPT_RELATIVE) - .help("create symbolic links relative to link location"), + .help("create symbolic links relative to link location") + .requires(OPT_SYMBOLIC), ) .arg( Arg::with_name(OPT_VERBOSE) diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index e475e3608..00ea85ecd 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -428,20 +428,6 @@ fn test_symlink_relative() { assert_eq!(at.resolve_link(link), file_a); } -#[test] -fn test_hardlink_relative() { - let (at, mut ucmd) = at_and_ucmd!(); - let file_a = "test_hardlink_relative_a"; - let link = "test_hardlink_relative_link"; - - at.touch(file_a); - - // relative hardlink - ucmd.args(&["-r", "-v", file_a, link]) - .succeeds() - .stdout_only(format!("'{}' -> '{}'\n", link, file_a)); -} - #[test] fn test_symlink_relative_path() { let (at, mut ucmd) = at_and_ucmd!(); @@ -571,3 +557,8 @@ fn test_symlink_no_deref_file() { assert!(at.is_symlink(link)); assert_eq!(at.resolve_link(link), file1); } + +#[test] +fn test_relative_requires_symbolic() { + new_ucmd!().args(&["-r", "foo", "bar"]).fails(); +} From 2f5f7c6fa1f79a3917300e62555e812be3faa87b Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 2 Jun 2021 18:37:21 +0200 Subject: [PATCH 018/320] split: use "parse_size" from uucore * make stderr of parsing SIZE/NUMBER argument consistent with GNU's behavior * add error handling * add tests --- src/uu/split/src/split.rs | 56 +++++++++---------------- src/uucore/src/lib/parser/parse_size.rs | 4 +- tests/by-util/test_split.rs | 46 ++++++++++++++++++++ 3 files changed, 69 insertions(+), 37 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 85ed5f183..1fe35795e 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -13,11 +13,13 @@ extern crate uucore; mod platform; use clap::{App, Arg}; +use std::convert::TryFrom; use std::env; use std::fs::File; use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write}; use std::path::Path; use std::{char, fs::remove_file}; +use uucore::parse_size::{parse_size, ParseSizeError}; static NAME: &str = "split"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -232,10 +234,9 @@ struct LineSplitter { impl LineSplitter { fn new(settings: &Settings) -> LineSplitter { LineSplitter { - lines_per_split: settings - .strategy_param - .parse() - .unwrap_or_else(|e| crash!(1, "invalid number of lines: {}", e)), + lines_per_split: settings.strategy_param.parse().unwrap_or_else(|_| { + crash!(1, "invalid number of lines: ‘{}’", settings.strategy_param) + }), } } } @@ -277,40 +278,23 @@ struct ByteSplitter { impl ByteSplitter { fn new(settings: &Settings) -> ByteSplitter { - // These multipliers are the same as supported by GNU coreutils. - let modifiers: Vec<(&str, u128)> = vec![ - ("K", 1024u128), - ("M", 1024 * 1024), - ("G", 1024 * 1024 * 1024), - ("T", 1024 * 1024 * 1024 * 1024), - ("P", 1024 * 1024 * 1024 * 1024 * 1024), - ("E", 1024 * 1024 * 1024 * 1024 * 1024 * 1024), - ("Z", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024), - ("Y", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024), - ("KB", 1000), - ("MB", 1000 * 1000), - ("GB", 1000 * 1000 * 1000), - ("TB", 1000 * 1000 * 1000 * 1000), - ("PB", 1000 * 1000 * 1000 * 1000 * 1000), - ("EB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000), - ("ZB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000), - ("YB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000), - ]; - - // This sequential find is acceptable since none of the modifiers are - // suffixes of any other modifiers, a la Huffman codes. - let (suffix, multiplier) = modifiers - .iter() - .find(|(suffix, _)| settings.strategy_param.ends_with(suffix)) - .unwrap_or(&("", 1)); - - // Try to parse the actual numeral. - let n = &settings.strategy_param[0..(settings.strategy_param.len() - suffix.len())] - .parse::() - .unwrap_or_else(|e| crash!(1, "invalid number of bytes: {}", e)); + let size_string = &settings.strategy_param; + let size_num = match parse_size(&size_string) { + Ok(n) => n, + Err(e) => match e { + ParseSizeError::ParseFailure(_) => { + crash!(1, "invalid number of bytes: {}", e.to_string()) + } + ParseSizeError::SizeTooBig(_) => crash!( + 1, + "invalid number of bytes: ‘{}’: Value too large for defined data type", + size_string + ), + }, + }; ByteSplitter { - bytes_per_split: n * multiplier, + bytes_per_split: u128::try_from(size_num).unwrap(), } } } diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 8b3e0bf03..4a31d753a 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -117,7 +117,9 @@ impl ParseSizeError { fn size_too_big(s: &str) -> ParseSizeError { // has to be handled in the respective uutils because strings differ, e.g. // truncate: Invalid number: ‘1Y’: Value too large to be stored in data type - // tail: invalid number of bytes: ‘1Y’: Value too large to be stored in data type + // tail: invalid number of bytes: ‘1Y’: Value too large to be stored in data type + // split: invalid number of bytes: ‘1Y’: Value too large for defined data type + // etc. ParseSizeError::SizeTooBig(format!( "‘{}’: Value too large to be stored in data type", s diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 85b28326b..1727072ca 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -285,3 +285,49 @@ fn test_filter_command_fails() { ucmd.args(&["--filter=/a/path/that/totally/does/not/exist", name]) .fails(); } + +#[test] +fn test_split_lines_number() { + // Test if stdout/stderr for '--lines' option is correct + new_ucmd!() + .args(&["--lines", "2"]) + .pipe_in("abcde") + .succeeds() + .no_stderr() + .no_stdout(); + new_ucmd!() + .args(&["--lines", "2fb"]) + .pipe_in("abcde") + .fails() + .code_is(1) + .stderr_only("split: invalid number of lines: ‘2fb’"); +} + +#[test] +fn test_split_invalid_bytes_size() { + new_ucmd!() + .args(&["-b", "1024R"]) + .fails() + .code_is(1) + .stderr_only("split: invalid number of bytes: ‘1024R’"); + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .args(&["-b", "1Y"]) + .fails() + .code_is(1) + .stderr_only("split: invalid number of bytes: ‘1Y’: Value too large for defined data type"); + #[cfg(target_pointer_width = "32")] + { + let sizes = ["1000G", "10T"]; + for size in &sizes { + new_ucmd!() + .args(&["-b", size]) + .fails() + .code_is(1) + .stderr_only(format!( + "split: invalid number of bytes: ‘{}’: Value too large for defined data type", + size + )); + } + } +} From 5898b99627ebb5fac6e14d381abc907879d33ee2 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 2 Jun 2021 22:08:42 +0200 Subject: [PATCH 019/320] truncate: add error handling for SIZE argument * add tests for SIZE argument * fix clap argument handling for --size and --reference --- src/uu/truncate/src/truncate.rs | 22 +++++++-------- tests/by-util/test_truncate.rs | 48 ++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 79aa4f3f1..57cc280c3 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -61,10 +61,9 @@ pub mod options { pub static NO_CREATE: &str = "no-create"; pub static REFERENCE: &str = "reference"; pub static SIZE: &str = "size"; + pub static ARG_FILES: &str = "files"; } -static ARG_FILES: &str = "files"; - fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } @@ -116,21 +115,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::REFERENCE) .short("r") .long(options::REFERENCE) + .required_unless(options::SIZE) .help("base the size of each file on the size of RFILE") .value_name("RFILE") ) .arg( Arg::with_name(options::SIZE) .short("s") - .long("size") + .long(options::SIZE) + .required_unless(options::REFERENCE) .help("set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified") .value_name("SIZE") ) - .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1)) + .arg(Arg::with_name(options::ARG_FILES).value_name("FILE").multiple(true).takes_value(true).required(true).min_values(1)) .get_matches_from(args); let files: Vec = matches - .values_of(ARG_FILES) + .values_of(options::ARG_FILES) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); @@ -152,8 +153,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!( 1, "cannot stat '{}': No such file or directory", - reference.unwrap() - ); + reference.unwrap_or("".to_string()) + ); // TODO: fix '--no-create' see test_reference and test_truncate_bytes_size } _ => crash!(1, "{}", e.to_string()), } @@ -209,8 +210,7 @@ fn truncate_reference_and_size( } _ => m, }, - // TODO: handle errors ParseFailure(String)/SizeTooBig(String) - Err(_) => crash!(1, "Invalid number: ‘{}’", size_string), + Err(e) => crash!(1, "Invalid number: {}", e.to_string()), }; let fsize = usize::try_from(metadata(rfilename)?.len()).unwrap(); let tsize = mode.to_size(fsize); @@ -265,7 +265,7 @@ fn truncate_size_only( ) -> std::io::Result<()> { let mode = match parse_mode_and_size(size_string) { Ok(m) => m, - Err(_) => crash!(1, "Invalid number: ‘{}’", size_string), + Err(e) => crash!(1, "Invalid number: {}", e.to_string()), }; for filename in &filenames { let fsize = usize::try_from(metadata(filename)?.len()).unwrap(); @@ -294,7 +294,7 @@ fn truncate( } (Some(rfilename), None) => truncate_reference_file_only(&rfilename, filenames, create), (None, Some(size_string)) => truncate_size_only(&size_string, filenames, create), - (None, None) => crash!(1, "you must specify either --reference or --size"), + (None, None) => crash!(1, "you must specify either --reference or --size"), // this case cannot happen anymore because it's handled by clap } } diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 5c3f169a1..72f7f780b 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -48,7 +48,7 @@ fn test_reference() { // manpage: "A FILE argument that does not exist is created." // TODO: 'truncate' does not create the file in this case, // but should because '--no-create' wasn't specified. - at.touch(FILE1); // TODO: remove this when fixed + at.touch(FILE1); // TODO: remove this when 'no-create' is fixed scene.ucmd().arg("-s").arg("+5KB").arg(FILE1).succeeds(); scene @@ -240,11 +240,18 @@ fn test_size_and_reference() { ); } +#[test] +fn test_error_filename_only() { + // truncate: you must specify either ‘--size’ or ‘--reference’ + new_ucmd!().args(&["file"]).fails().stderr_contains( + "error: The following required arguments were not provided: + --reference + --size ", + ); +} + #[test] fn test_invalid_numbers() { - // TODO For compatibility with GNU, `truncate -s 0X` should cause - // the same error as `truncate -s 0X file`, but currently it returns - // a different error. new_ucmd!() .args(&["-s", "0X", "file"]) .fails() @@ -274,3 +281,36 @@ fn test_reference_with_size_file_not_found() { .fails() .stderr_contains("cannot stat 'a': No such file or directory"); } + +#[test] +fn test_truncate_bytes_size() { + // TODO: this should succeed without error, uncomment when '--no-create' is fixed + // new_ucmd!() + // .args(&["--no-create", "--size", "K", "file"]) + // .succeeds(); + new_ucmd!() + .args(&["--size", "1024R", "file"]) + .fails() + .code_is(1) + .stderr_only("truncate: Invalid number: ‘1024R’"); + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .args(&["--size", "1Y", "file"]) + .fails() + .code_is(1) + .stderr_only("truncate: Invalid number: ‘1Y’: Value too large for defined data type"); + #[cfg(target_pointer_width = "32")] + { + let sizes = ["1000G", "10T"]; + for size in &sizes { + new_ucmd!() + .args(&["--size", size, "file"]) + .fails() + .code_is(1) + .stderr_only(format!( + "truncate: Invalid number: ‘{}’: Value too large for defined data type", + size + )); + } + } +} From af8f47ea6a6c21836f88032217932cb15fba5ecb Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 3 Jun 2021 15:05:20 +0200 Subject: [PATCH 020/320] ln: remove redundant check if `dst.exists()` and `settings.overwrite` is `OverwriteMode::Force`, we already delete the file in the match above. --- src/uu/ln/src/ln.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 4bd3310cc..4087716bd 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -422,11 +422,6 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> { } } - if settings.no_dereference && matches!(settings.overwrite, OverwriteMode::Force) && dst.exists() - { - fs::remove_file(dst)?; - } - if settings.symbolic { symlink(&source, dst)?; } else { From 6c46d09397759118343a3e7fdd21310aa99debac Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 2 Jun 2021 21:27:08 +0200 Subject: [PATCH 021/320] ln: canonicalize the parent directory of dst, not dst dst may or may not exist. In case it exists it might already be a symlink. In neither case we should try to canonicalize dst, only its parent directory. https://www.gnu.org/software/coreutils/manual/html_node/ln-invocation.html > Relative symbolic links are generated based on their canonicalized > **containing directory**, and canonicalized targets. --- src/uu/ln/src/ln.rs | 3 ++- tests/by-util/test_ln.rs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 4087716bd..33ec5f449 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -372,7 +372,8 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) fn relative_path<'a>(src: &Path, dst: &Path) -> Result> { let src_abs = canonicalize(src, CanonicalizeMode::Normal)?; - let dst_abs = canonicalize(dst, CanonicalizeMode::Normal)?; + let mut dst_abs = canonicalize(dst.parent().unwrap(), CanonicalizeMode::Normal)?; + dst_abs.push(dst.components().last().unwrap()); let suffix_pos = src_abs .components() .zip(dst_abs.components()) diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index 00ea85ecd..fc97ff779 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -562,3 +562,21 @@ fn test_symlink_no_deref_file() { fn test_relative_requires_symbolic() { new_ucmd!().args(&["-r", "foo", "bar"]).fails(); } + +#[test] +fn test_relative_dst_already_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file1"); + at.symlink_file("file1", "file2"); + ucmd.arg("-srf").arg("file1").arg("file2").succeeds(); + at.is_symlink("file2"); +} + +#[test] +fn test_relative_src_already_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file1"); + at.symlink_file("file1", "file2"); + ucmd.arg("-sr").arg("file2").arg("file3").succeeds(); + assert!(at.resolve_link("file3").ends_with("file1")); +} From ad26b7a042cabaf50b7b2ba93e980b050dc0bb59 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 3 Jun 2021 20:37:29 +0200 Subject: [PATCH 022/320] head/tail/split: make error handling of NUM/SIZE arguments more consistent * add tests for each flag that takes NUM/SIZE arguments * fix bug in tail where 'quiet' and 'verbose' flags did not override each other POSIX style --- src/uu/head/src/head.rs | 2 +- src/uu/head/src/parse.rs | 5 +- src/uu/split/src/split.rs | 13 +---- src/uu/tail/src/tail.rs | 18 +++---- src/uucore/src/lib/parser/parse_size.rs | 63 +++++++++++++++++++------ tests/by-util/test_head.rs | 23 ++++++--- tests/by-util/test_tail.rs | 25 ++++++---- 7 files changed, 93 insertions(+), 56 deletions(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index d67cc5b07..80f816d0f 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -76,7 +76,7 @@ fn app<'a>() -> App<'a, 'a> { .arg( Arg::with_name(options::QUIET_NAME) .short("q") - .long("--quiet") + .long("quiet") .visible_alias("silent") .help("never print headers giving file names") .overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]), diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 50cdfc439..6631dba0e 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -108,10 +108,7 @@ pub fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> { return Err(ParseSizeError::ParseFailure(src.to_string())); } - match parse_size(&size_string) { - Ok(n) => Ok((n, all_but_last)), - Err(e) => Err(e), - } + parse_size(&size_string).map(|n| (n, all_but_last)) } #[cfg(test)] diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 1fe35795e..ca9c68c74 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -19,7 +19,7 @@ use std::fs::File; use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write}; use std::path::Path; use std::{char, fs::remove_file}; -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::parse_size; static NAME: &str = "split"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -281,16 +281,7 @@ impl ByteSplitter { let size_string = &settings.strategy_param; let size_num = match parse_size(&size_string) { Ok(n) => n, - Err(e) => match e { - ParseSizeError::ParseFailure(_) => { - crash!(1, "invalid number of bytes: {}", e.to_string()) - } - ParseSizeError::SizeTooBig(_) => crash!( - 1, - "invalid number of bytes: ‘{}’: Value too large for defined data type", - size_string - ), - }, + Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()), }; ByteSplitter { diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index acaad8c30..76c799621 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -33,7 +33,6 @@ use uucore::ringbuffer::RingBuffer; pub mod options { pub mod verbosity { pub static QUIET: &str = "quiet"; - pub static SILENT: &str = "silent"; pub static VERBOSE: &str = "verbose"; } pub static BYTES: &str = "bytes"; @@ -77,6 +76,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let app = App::new(executable!()) .version(crate_version!()) .about("output the last part of files") + // TODO: add usage .arg( Arg::with_name(options::BYTES) .short("c") @@ -111,13 +111,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::verbosity::QUIET) .short("q") .long(options::verbosity::QUIET) + .visible_alias("silent") + .overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE]) .help("never output headers giving file names"), ) - .arg( - Arg::with_name(options::verbosity::SILENT) - .long(options::verbosity::SILENT) - .help("synonym of --quiet"), - ) .arg( Arg::with_name(options::SLEEP_INT) .short("s") @@ -129,6 +126,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::verbosity::VERBOSE) .short("v") .long(options::verbosity::VERBOSE) + .overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE]) .help("always output headers giving file names"), ) .arg( @@ -195,8 +193,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } let verbose = matches.is_present(options::verbosity::VERBOSE); - let quiet = matches.is_present(options::verbosity::QUIET) - || matches.is_present(options::verbosity::SILENT); + let quiet = matches.is_present(options::verbosity::QUIET); let files: Vec = matches .values_of(options::ARG_FILES) @@ -423,8 +420,5 @@ fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> { return Err(ParseSizeError::ParseFailure(src.to_string())); } - match parse_size(&size_string) { - Ok(n) => Ok((n, starting_with)), - Err(e) => Err(e), - } + parse_size(&size_string).map(|n| (n, starting_with)) } diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 4a31d753a..c7825d4e3 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -75,10 +75,9 @@ pub fn parse_size(size: &str) -> Result { Ok(n) => n, Err(_) => return Err(ParseSizeError::size_too_big(size)), }; - match number.checked_mul(factor) { - Some(n) => Ok(n), - None => Err(ParseSizeError::size_too_big(size)), - } + number + .checked_mul(factor) + .ok_or(ParseSizeError::size_too_big(size)) } #[derive(Debug, PartialEq, Eq)] @@ -108,22 +107,58 @@ impl fmt::Display for ParseSizeError { impl ParseSizeError { fn parse_failure(s: &str) -> ParseSizeError { - // has to be handled in the respective uutils because strings differ, e.g. - // truncate: Invalid number: ‘fb’ - // tail: invalid number of bytes: ‘fb’ + // stderr on linux (GNU coreutils 8.32) + // has to be handled in the respective uutils because strings differ, e.g.: + // + // `NUM` + // head: invalid number of bytes: ‘1fb’ + // tail: invalid number of bytes: ‘1fb’ + // + // `SIZE` + // split: invalid number of bytes: ‘1fb’ + // truncate: Invalid number: ‘1fb’ + // + // `MODE` + // stdbuf: invalid mode ‘1fb’ + // + // `SIZE` + // sort: invalid suffix in --buffer-size argument '1fb' + // sort: invalid --buffer-size argument 'fb' + // + // `SIZE` + // du: invalid suffix in --buffer-size argument '1fb' + // du: invalid suffix in --threshold argument '1fb' + // du: invalid --buffer-size argument 'fb' + // du: invalid --threshold argument 'fb' + // + // `BYTES` + // od: invalid suffix in --read-bytes argument '1fb' + // od: invalid --read-bytes argument argument 'fb' + // --skip-bytes + // --width + // --strings + // etc. ParseSizeError::ParseFailure(format!("‘{}’", s)) } fn size_too_big(s: &str) -> ParseSizeError { - // has to be handled in the respective uutils because strings differ, e.g. - // truncate: Invalid number: ‘1Y’: Value too large to be stored in data type - // tail: invalid number of bytes: ‘1Y’: Value too large to be stored in data type + // stderr on linux (GNU coreutils 8.32) + // has to be handled in the respective uutils because strings differ, e.g.: + // + // head: invalid number of bytes: ‘1Y’: Value too large for defined data type + // tail: invalid number of bytes: ‘1Y’: Value too large for defined data type // split: invalid number of bytes: ‘1Y’: Value too large for defined data type + // truncate: Invalid number: ‘1Y’: Value too large for defined data type + // stdbuf: invalid mode ‘1Y’: Value too large for defined data type + // sort: -S argument '1Y' too large + // du: -B argument '1Y' too large + // od: -N argument '1Y' too large // etc. - ParseSizeError::SizeTooBig(format!( - "‘{}’: Value too large to be stored in data type", - s - )) + // + // stderr on macos (brew - GNU coreutils 8.32) also differs for the same version, e.g.: + // ghead: invalid number of bytes: ‘1Y’: Value too large to be stored in data type + // gtail: invalid number of bytes: ‘1Y’: Value too large to be stored in data type + ParseSizeError::SizeTooBig(format!("‘{}’: Value too large for defined data type", s)) } } diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 99e6518fa..6a2cdf1cd 100755 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -244,6 +244,7 @@ hello ", ); } + #[test] fn test_head_invalid_num() { new_ucmd!() @@ -258,16 +259,26 @@ fn test_head_invalid_num() { new_ucmd!() .args(&["-c", "1Y", "emptyfile.txt"]) .fails() - .stderr_is( - "head: invalid number of bytes: ‘1Y’: Value too large to be stored in data type", - ); + .stderr_is("head: invalid number of bytes: ‘1Y’: Value too large for defined data type"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["-n", "1Y", "emptyfile.txt"]) .fails() - .stderr_is( - "head: invalid number of lines: ‘1Y’: Value too large to be stored in data type", - ); + .stderr_is("head: invalid number of lines: ‘1Y’: Value too large for defined data type"); + #[cfg(target_pointer_width = "32")] + { + let sizes = ["1000G", "10T"]; + for size in &sizes { + new_ucmd!() + .args(&["-c", size]) + .fails() + .code_is(1) + .stderr_only(format!( + "head: invalid number of bytes: ‘{}’: Value too large for defined data type", + size + )); + } + } } #[test] diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 43e4aaa0c..c296e2763 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -284,12 +284,11 @@ fn test_multiple_input_files_with_suppressed_headers() { #[test] fn test_multiple_input_quiet_flag_overrides_verbose_flag_for_suppressing_headers() { - // TODO: actually the later one should win, i.e. -qv should lead to headers being printed, -vq to them being suppressed new_ucmd!() .arg(FOOBAR_TXT) .arg(FOOBAR_2_TXT) - .arg("-q") .arg("-v") + .arg("-q") .run() .stdout_is_fixture("foobar_multiple_quiet.expected"); } @@ -367,16 +366,26 @@ fn test_tail_invalid_num() { new_ucmd!() .args(&["-c", "1Y", "emptyfile.txt"]) .fails() - .stderr_is( - "tail: invalid number of bytes: ‘1Y’: Value too large to be stored in data type", - ); + .stderr_is("tail: invalid number of bytes: ‘1Y’: Value too large for defined data type"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["-n", "1Y", "emptyfile.txt"]) .fails() - .stderr_is( - "tail: invalid number of lines: ‘1Y’: Value too large to be stored in data type", - ); + .stderr_is("tail: invalid number of lines: ‘1Y’: Value too large for defined data type"); + #[cfg(target_pointer_width = "32")] + { + let sizes = ["1000G", "10T"]; + for size in &sizes { + new_ucmd!() + .args(&["-c", size]) + .fails() + .code_is(1) + .stderr_only(format!( + "tail: invalid number of bytes: ‘{}’: Value too large for defined data type", + size + )); + } + } } #[test] From db3ee61742138e7bc1dd14bb3a1bd67db418f5a5 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 3 Jun 2021 21:00:03 +0200 Subject: [PATCH 023/320] du/sort/od/stdbuf: make error handling of SIZE/BYTES/MODE arguments more consistent * od: add stderr info for not yet implemented '--strings' flag --- src/uu/du/src/du.rs | 31 ++++++++------- src/uu/od/src/od.rs | 65 +++++++++++++++++++------------- src/uu/od/src/parse_nrofbytes.rs | 45 +++++++++++----------- src/uu/sort/src/sort.rs | 24 ++++++++---- src/uu/stdbuf/src/stdbuf.rs | 11 ++---- tests/by-util/test_du.rs | 2 +- tests/by-util/test_od.rs | 34 +++++++++++++++++ tests/by-util/test_sort.rs | 9 +++-- tests/by-util/test_stdbuf.rs | 25 ++++++------ 9 files changed, 153 insertions(+), 93 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 5dfb41d82..170e057b5 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -42,7 +42,7 @@ mod options { pub const NULL: &str = "0"; pub const ALL: &str = "all"; pub const APPARENT_SIZE: &str = "apparent-size"; - pub const BLOCK_SIZE: &str = "B"; + pub const BLOCK_SIZE: &str = "block-size"; pub const BYTES: &str = "b"; pub const TOTAL: &str = "c"; pub const MAX_DEPTH: &str = "d"; @@ -211,18 +211,11 @@ fn get_file_info(path: &PathBuf) -> Option { } fn read_block_size(s: Option<&str>) -> usize { - if let Some(size_arg) = s { - match parse_size(size_arg) { - Ok(v) => v, - Err(e) => match e { - ParseSizeError::ParseFailure(_) => { - crash!(1, "invalid suffix in --block-size argument '{}'", size_arg) - } - ParseSizeError::SizeTooBig(_) => { - crash!(1, "--block-size argument '{}' too large", size_arg) - } - }, - } + if let Some(s) = s { + parse_size(s).map_or_else( + |e| crash!(1, "{}", format_error_message(e, s, options::BLOCK_SIZE)), + |n| n, + ) } else { for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { if let Ok(env_size) = env::var(env_var) { @@ -389,7 +382,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(options::BLOCK_SIZE) .short("B") - .long("block-size") + .long(options::BLOCK_SIZE) .value_name("SIZE") .help( "scale sizes by SIZE before printing them. \ @@ -694,6 +687,16 @@ Try '{} --help' for more information.", 0 } +fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String { + // NOTE: + // GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection + // GNU's du distinguishs between "invalid (suffix in) argument" + match error { + ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s), + ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s), + } +} + #[cfg(test)] mod test_du { #[allow(unused_imports)] diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 7359047b2..9b98fd515 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -43,6 +43,7 @@ use crate::partialreader::*; use crate::peekreader::*; use crate::prn_char::format_ascii_dump; use clap::{self, AppSettings, Arg, ArgMatches}; +use uucore::parse_size::ParseSizeError; use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -130,15 +131,16 @@ impl OdOptions { } }; - let mut skip_bytes = match matches.value_of(options::SKIP_BYTES) { - None => 0, - Some(s) => match parse_number_of_bytes(&s) { - Ok(i) => i, - Err(_) => { - return Err(format!("Invalid argument --skip-bytes={}", s)); - } - }, - }; + if matches.occurrences_of(options::STRINGS) > 0 { + crash!(1, "Option '{}' not yet implemented.", options::STRINGS); + } + + let mut skip_bytes = matches.value_of(options::SKIP_BYTES).map_or(0, |s| { + parse_number_of_bytes(s).map_or_else( + |e| crash!(1, "{}", format_error_message(e, s, options::SKIP_BYTES)), + |n| n, + ) + }); let mut label: Option = None; @@ -161,11 +163,16 @@ impl OdOptions { } }; - let mut line_bytes = match matches.value_of(options::WIDTH) { - None => 16, - Some(_) if matches.occurrences_of(options::WIDTH) == 0 => 16, - Some(s) => s.parse::().unwrap_or(0), - }; + let mut line_bytes = matches.value_of(options::WIDTH).map_or(16, |s| { + if matches.occurrences_of(options::WIDTH) == 0 { + return 16; + }; + parse_number_of_bytes(s).map_or_else( + |e| crash!(1, "{}", format_error_message(e, s, options::WIDTH)), + |n| n, + ) + }); + let min_bytes = formats.iter().fold(1, |max, next| { cmp::max(max, next.formatter_item_info.byte_size) }); @@ -176,15 +183,12 @@ impl OdOptions { let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES); - let read_bytes = match matches.value_of(options::READ_BYTES) { - None => None, - Some(s) => match parse_number_of_bytes(&s) { - Ok(i) => Some(i), - Err(_) => { - return Err(format!("Invalid argument --read-bytes={}", s)); - } - }, - }; + let read_bytes = matches.value_of(options::READ_BYTES).map(|s| { + parse_number_of_bytes(s).map_or_else( + |e| crash!(1, "{}", format_error_message(e, s, options::READ_BYTES)), + |n| n, + ) + }); let radix = match matches.value_of(options::ADDRESS_RADIX) { None => Radix::Octal, @@ -265,7 +269,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .short("S") .long(options::STRINGS) .help( - "output strings of at least BYTES graphic chars. 3 is assumed when \ + "NotImplemented: output strings of at least BYTES graphic chars. 3 is assumed when \ BYTES is not specified.", ) .default_value("3") @@ -466,8 +470,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let od_options = match OdOptions::new(clap_matches, args) { Err(s) => { - show_usage_error!("{}", s); - return 1; + crash!(1, "{}", s); } Ok(o) => o, }; @@ -649,3 +652,13 @@ fn open_input_peek_reader( let pr = PartialReader::new(mf, skip_bytes, read_bytes); PeekReader::new(pr) } + +fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String { + // NOTE: + // GNU's od echos affected flag, -N or --read-bytes (-j or --skip-bytes, etc.), depending user's selection + // GNU's od distinguishs between "invalid (suffix in) argument" + match error { + ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s), + ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s), + } +} diff --git a/src/uu/od/src/parse_nrofbytes.rs b/src/uu/od/src/parse_nrofbytes.rs index 9223d7e53..e51e15d39 100644 --- a/src/uu/od/src/parse_nrofbytes.rs +++ b/src/uu/od/src/parse_nrofbytes.rs @@ -1,4 +1,6 @@ -pub fn parse_number_of_bytes(s: &str) -> Result { +use uucore::parse_size::{parse_size, ParseSizeError}; + +pub fn parse_number_of_bytes(s: &str) -> Result { let mut start = 0; let mut len = s.len(); let mut radix = 16; @@ -9,10 +11,7 @@ pub fn parse_number_of_bytes(s: &str) -> Result { } else if s.starts_with('0') { radix = 8; } else { - return match uucore::parse_size::parse_size(&s[start..]) { - Ok(n) => Ok(n), - Err(_) => Err("parse failed"), - }; + return parse_size(&s[start..]); } let mut ends_with = s.chars().rev(); @@ -60,35 +59,33 @@ pub fn parse_number_of_bytes(s: &str) -> Result { Some('P') => 1000 * 1000 * 1000 * 1000 * 1000, #[cfg(target_pointer_width = "64")] Some('E') => 1000 * 1000 * 1000 * 1000 * 1000 * 1000, - _ => return Err("parse failed"), + _ => return Err(ParseSizeError::ParseFailure(s.to_string())), } } _ => {} } - match usize::from_str_radix(&s[start..len], radix) { - Ok(i) => Ok(i * multiply), - Err(_) => Err("parse failed"), - } -} - -#[allow(dead_code)] -fn parse_number_of_bytes_str(s: &str) -> Result { - parse_number_of_bytes(&String::from(s)) + let factor = match usize::from_str_radix(&s[start..len], radix) { + Ok(f) => f, + Err(e) => return Err(ParseSizeError::ParseFailure(e.to_string())), + }; + factor + .checked_mul(multiply) + .ok_or(ParseSizeError::SizeTooBig(s.to_string())) } #[test] fn test_parse_number_of_bytes() { // octal input - assert_eq!(8, parse_number_of_bytes_str("010").unwrap()); - assert_eq!(8 * 512, parse_number_of_bytes_str("010b").unwrap()); - assert_eq!(8 * 1024, parse_number_of_bytes_str("010k").unwrap()); - assert_eq!(8 * 1048576, parse_number_of_bytes_str("010m").unwrap()); + assert_eq!(8, parse_number_of_bytes("010").unwrap()); + assert_eq!(8 * 512, parse_number_of_bytes("010b").unwrap()); + assert_eq!(8 * 1024, parse_number_of_bytes("010k").unwrap()); + assert_eq!(8 * 1048576, parse_number_of_bytes("010m").unwrap()); // hex input - assert_eq!(15, parse_number_of_bytes_str("0xf").unwrap()); - assert_eq!(15, parse_number_of_bytes_str("0XF").unwrap()); - assert_eq!(27, parse_number_of_bytes_str("0x1b").unwrap()); - assert_eq!(16 * 1024, parse_number_of_bytes_str("0x10k").unwrap()); - assert_eq!(16 * 1048576, parse_number_of_bytes_str("0x10m").unwrap()); + assert_eq!(15, parse_number_of_bytes("0xf").unwrap()); + assert_eq!(15, parse_number_of_bytes("0XF").unwrap()); + assert_eq!(27, parse_number_of_bytes("0x1b").unwrap()); + assert_eq!(16 * 1024, parse_number_of_bytes("0x10k").unwrap()); + assert_eq!(16 * 1048576, parse_number_of_bytes("0x10m").unwrap()); } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 208010d09..c148d06c7 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1148,12 +1148,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.buffer_size = matches .value_of(OPT_BUF_SIZE) - .map(|v| match GlobalSettings::parse_byte_count(v) { - Ok(n) => n, - Err(ParseSizeError::ParseFailure(_)) => crash!(2, "invalid -S argument '{}'", v), - Err(ParseSizeError::SizeTooBig(_)) => crash!(2, "-S argument '{}' too large", v), - }) - .unwrap_or(DEFAULT_BUF_SIZE); + .map_or(DEFAULT_BUF_SIZE, |s| { + GlobalSettings::parse_byte_count(s).map_or_else( + |e| crash!(2, "{}", format_error_message(e, s, OPT_BUF_SIZE)), + |n| n, + ) + }); settings.tmp_dir = matches .value_of(OPT_TMP_DIR) @@ -1555,6 +1555,16 @@ fn open(path: impl AsRef) -> Box { } } +fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String { + // NOTE: + // GNU's sort echos affected flag, -S or --buffer-size, depending user's selection + // GNU's sort distinguishs between "invalid (suffix in) argument" + match error { + ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s), + ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s), + } +} + #[cfg(test)] mod tests { @@ -1659,7 +1669,7 @@ mod tests { ("10T", 10 * 1024 * 1024 * 1024 * 1024), ("1b", 1), ("1024b", 1024), - ("1024Mb", 1024 * 1024 * 1024), // TODO: This might not be what GNU `sort` does? + ("1024Mb", 1024 * 1024 * 1024), // NOTE: This might not be how GNU `sort` behaves for 'Mb' ("1", 1024), // K is default ("50", 50 * 1024), ("K", 1024), diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index e39af3816..d073c1bd1 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -118,13 +118,10 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result { - let size = match parse_size(x) { - Ok(m) => m, - Err(e) => return Err(ProgramOptionsError(format!("invalid mode {}", e))), - }; - Ok(BufferType::Size(size)) - } + x => parse_size(x).map_or_else( + |e| crash!(125, "invalid mode {}", e), + |m| Ok(BufferType::Size(m)), + ), }, None => Ok(BufferType::Default), } diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 8ec0a9c0c..066ab5c9b 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -80,7 +80,7 @@ fn test_du_invalid_size() { .arg("/tmp") .fails() .code_is(1) - .stderr_only("du: invalid suffix in --block-size argument '1fb4t'"); + .stderr_only("du: invalid --block-size argument '1fb4t'"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .arg("--block-size=1Y") diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index c21c683dc..3d34e47a1 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -804,3 +804,37 @@ fn test_traditional_only_label() { ", )); } + +#[test] +fn test_od_invalid_bytes() { + const INVALID_SIZE: &str = "1fb4t"; + const BIG_SIZE: &str = "1Y"; + + let input: [u8; 8] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + let options = [ + "--read-bytes", + "--skip-bytes", + "--width", + // "--strings", // TODO: consider testing here once '--strings' is implemented + ]; + for option in &options { + new_ucmd!() + .arg(format!("{}={}", option, INVALID_SIZE)) + .run_piped_stdin(&input[..]) + .failure() + .code_is(1) + .stderr_only(format!( + "od: invalid {} argument '{}'", + option, INVALID_SIZE + )); + + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .arg(format!("{}={}", option, BIG_SIZE)) + .run_piped_stdin(&input[..]) + .failure() + .code_is(1) + .stderr_only(format!("od: {} argument '{}' too large", option, BIG_SIZE)); + } +} diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 054789edf..eab256980 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -57,7 +57,7 @@ fn test_invalid_buffer_size() { .fails() .code_is(2) .stderr_only(format!( - "sort: invalid -S argument '{}'", + "sort: invalid --buffer-size argument '{}'", invalid_buffer_size )); } @@ -69,7 +69,7 @@ fn test_invalid_buffer_size() { .arg("ext_sort.txt") .fails() .code_is(2) - .stderr_only("sort: -S argument '1Y' too large"); + .stderr_only("sort: --buffer-size argument '1Y' too large"); #[cfg(target_pointer_width = "32")] { @@ -82,7 +82,10 @@ fn test_invalid_buffer_size() { .arg("ext_sort.txt") .fails() .code_is(2) - .stderr_only(format!("sort: -S argument '{}' too large", buffer_size)); + .stderr_only(format!( + "sort: --buffer-size argument '{}' too large", + buffer_size + )); } } } diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index e5d784edb..fc1b9324a 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -57,15 +57,18 @@ fn test_stdbuf_line_buffering_stdin_fails() { #[cfg(not(target_os = "windows"))] #[test] fn test_stdbuf_invalid_mode_fails() { - // TODO: GNU's `stdbuf` (8.32) does not return "\nTry 'stdbuf --help' for more information." - // for invalid modes. - new_ucmd!() - .args(&["-i", "1024R", "head"]) - .fails() - .stderr_contains("stdbuf: invalid mode ‘1024R’"); - #[cfg(not(target_pointer_width = "128"))] - new_ucmd!() - .args(&["--error", "1Y", "head"]) - .fails() - .stderr_contains("stdbuf: invalid mode ‘1Y’: Value too large to be stored in data type"); + let options = ["--input", "--output", "--error"]; + for option in &options { + new_ucmd!() + .args(&[*option, "1024R", "head"]) + .fails() + .code_is(125) + .stderr_only("stdbuf: invalid mode ‘1024R’"); + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .args(&[*option, "1Y", "head"]) + .fails() + .code_is(125) + .stderr_contains("stdbuf: invalid mode ‘1Y’: Value too large for defined data type"); + } } From 84330ca938bdd441f341d28a44e480f8352f43ed Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 3 Jun 2021 22:15:57 +0200 Subject: [PATCH 024/320] gnu/test: rm: update one of the test to match what we do --- util/build-gnu.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 64329bd0c..c575e84f6 100644 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -98,4 +98,13 @@ sed -i 's|seq |/usr/bin/seq |' tests/misc/sort-discrim.sh sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh sed -i 's|cat |/usr/bin/timeout 0.1 cat |' tests/misc/cat-self.sh + + +#### Adjust tests to make them work with Rust/coreutils +# in some cases, what we are doing in rust/coreutils is good (or better) +# we should not regress our project just to match what GNU is going. +# So, do some changes on the fly + +sed -i -e "s|rm: cannot remove 'e/slink'|rm: cannot remove 'e'|g" tests/rm/fail-eacces.sh + test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" From 6a8d15f92eee778fe583d8680a9af7bcc9e8a46e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 3 Jun 2021 22:19:14 +0200 Subject: [PATCH 025/320] gnu/rm: match gnu's output --- src/uu/rm/src/rm.rs | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 43a4f4780..ba764034a 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -255,7 +255,18 @@ fn handle_dir(path: &Path, options: &Options) -> bool { // correctly on Windows if let Err(e) = remove_dir_all(path) { had_err = true; - show_error!("could not remove '{}': {}", path.display(), e); + if e.kind() == std::io::ErrorKind::PermissionDenied { + // GNU compatibility (rm/fail-eacces.sh) + // here, GNU doesn't use some kind of remove_dir_all + // It will show directory+file + show_error!( + "cannot remove '{}': {}", + path.display(), + "Permission denied" + ); + } else { + show_error!("cannot remove '{}': {}", path.display(), e); + } } } else { let mut dirs: VecDeque = VecDeque::new(); @@ -314,7 +325,16 @@ fn remove_dir(path: &Path, options: &Options) -> bool { } } Err(e) => { - show_error!("cannot remove '{}': {}", path.display(), e); + if e.kind() == std::io::ErrorKind::PermissionDenied { + // GNU compatibility (rm/fail-eacces.sh) + show_error!( + "cannot remove '{}': {}", + path.display(), + "Permission denied" + ); + } else { + show_error!("cannot remove '{}': {}", path.display(), e); + } return true; } } @@ -352,7 +372,16 @@ fn remove_file(path: &Path, options: &Options) -> bool { } } Err(e) => { - show_error!("removing '{}': {}", path.display(), e); + if e.kind() == std::io::ErrorKind::PermissionDenied { + // GNU compatibility (rm/fail-eacces.sh) + show_error!( + "cannot remove '{}': {}", + path.display(), + "Permission denied" + ); + } else { + show_error!("cannot remove '{}': {}", path.display(), e); + } return true; } } From 754082886c602414846f59bf9c3f9a9d04343a6b Mon Sep 17 00:00:00 2001 From: Mitchell Mebane Date: Thu, 3 Jun 2021 20:49:25 -0500 Subject: [PATCH 026/320] dircolors: Address code review comments --- src/uu/dircolors/src/dircolors.rs | 42 +++++++------------------------ 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 078270791..2fa2e8b91 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -19,9 +19,7 @@ use std::io::{BufRead, BufReader}; use clap::{crate_version, App, Arg}; mod options { - pub const SH: &str = "sh"; pub const BOURNE_SHELL: &str = "bourne-shell"; - pub const CSH: &str = "csh"; pub const C_SHELL: &str = "c-shell"; pub const PRINT_DATABASE: &str = "print-database"; pub const FILE: &str = "FILE"; @@ -75,52 +73,33 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - /* Clap has .visible_alias, but it generates help like this - * -b, --sh output Bourne shell code to set LS_COLORS [aliases: bourne-shell] - * whereas we want help like this - * -b, --sh output Bourne shell code to set LS_COLORS - * --bourne-shell output Bourne shell code to set LS_COLORS - * (or preferably like the original, but that doesn't seem possible with clap:) - * -b, --sh, --bourne-shell output Bourne shell code to set LS_COLORS - * therefore, command aliases are defined manually as multiple commands - */ let matches = App::new(executable!()) .version(crate_version!()) .about(SUMMARY) .usage(&usage[..]) .after_help(LONG_HELP) .arg( - Arg::with_name(options::SH) + Arg::with_name(options::BOURNE_SHELL) .long("sh") .short("b") + .visible_alias("bourne-shell") .help("output Bourne shell code to set LS_COLORS") .display_order(1), ) .arg( - Arg::with_name(options::BOURNE_SHELL) - .long("bourne-shell") - .help("output Bourne shell code to set LS_COLORS") - .display_order(2), - ) - .arg( - Arg::with_name(options::CSH) + Arg::with_name(options::C_SHELL) .long("csh") .short("c") + .visible_alias("c-shell") .help("output C shell code to set LS_COLORS") - .display_order(3), - ) - .arg( - Arg::with_name(options::C_SHELL) - .long("c-shell") - .help("output C shell code to set LS_COLORS") - .display_order(4), + .display_order(2), ) .arg( Arg::with_name(options::PRINT_DATABASE) .long("print-database") .short("p") .help("print the byte counts") - .display_order(5), + .display_order(3), ) .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) .get_matches_from(&args); @@ -131,10 +110,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // clap provides .conflicts_with / .conflicts_with_all, but we want to // manually handle conflicts so we can match the output of GNU coreutils - if (matches.is_present(options::CSH) - || matches.is_present(options::C_SHELL) - || matches.is_present(options::SH) - || matches.is_present(options::BOURNE_SHELL)) + if (matches.is_present(options::C_SHELL) || matches.is_present(options::BOURNE_SHELL)) && matches.is_present(options::PRINT_DATABASE) { show_usage_error!( @@ -158,9 +134,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } let mut out_format = OutputFmt::Unknown; - if matches.is_present(options::CSH) || matches.is_present(options::C_SHELL) { + if matches.is_present(options::C_SHELL) { out_format = OutputFmt::CShell; - } else if matches.is_present(options::SH) || matches.is_present(options::BOURNE_SHELL) { + } else if matches.is_present(options::BOURNE_SHELL) { out_format = OutputFmt::Shell; } From a85ee4386ad29c60a185af8a7861a9c2b9404d29 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 3 Jun 2021 22:49:47 +0200 Subject: [PATCH 027/320] gnu/rm: make the code reentrant --- util/build-gnu.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index c575e84f6..cd0f7dc39 100644 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -59,7 +59,7 @@ do done -grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||' +grep -rl 'path_prepend_' tests/* | xargs sed -i 's| path_prepend_ ./src||' sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh # Remove tests checking for --version & --help @@ -94,11 +94,14 @@ sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh sed -i 's|seq |/usr/bin/seq |' tests/misc/sort-discrim.sh -#Add specific timeout to tests that currently hang to limit time spent waiting +# Add specific timeout to tests that currently hang to limit time spent waiting sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh sed -i 's|cat |/usr/bin/timeout 0.1 cat |' tests/misc/cat-self.sh +# Remove dup of /usr/bin/ when executed several times +grep -rl '/usr/bin//usr/bin/' tests/* | xargs --no-run-if-empty sed -i 's|/usr/bin//usr/bin/|/usr/bin/|g' + #### Adjust tests to make them work with Rust/coreutils # in some cases, what we are doing in rust/coreutils is good (or better) From 9cf3ab894f0a887d5085fdf08abb52a13d3acb07 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 3 Jun 2021 23:00:51 +0200 Subject: [PATCH 028/320] gnu/ci: build in parallel --- util/build-gnu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index cd0f7dc39..0c511ffab 100644 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -44,7 +44,7 @@ sed -i 's|"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver # Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile sed -i 's| tr | /usr/bin/tr |' tests/init.sh -make +make -j "$(nproc)" # Generate the factor tests, so they can be fixed # Used to be 36. Reduced to 20 to decrease the log size for i in {00..20} From aabef14404274a7071dad8725ba7b51019b31c3b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 3 Jun 2021 23:07:51 +0200 Subject: [PATCH 029/320] gnu/rm: fix tests/rm/cycle.sh --- util/build-gnu.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 0c511ffab..691598881 100644 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -110,4 +110,6 @@ grep -rl '/usr/bin//usr/bin/' tests/* | xargs --no-run-if-empty sed -i 's|/usr/b sed -i -e "s|rm: cannot remove 'e/slink'|rm: cannot remove 'e'|g" tests/rm/fail-eacces.sh +sed -i -e "s|rm: cannot remove 'a/b/file'|rm: cannot remove 'a'|g" tests/rm/cycle.sh + test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" From f421e51ad3df3b796ebf2d03ff8e099a1919ac93 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 3 Jun 2021 23:30:33 +0200 Subject: [PATCH 030/320] gnu/rm: fix tests/rm/rm{1,2}.sh --- util/build-gnu.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 691598881..83136e733 100644 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -112,4 +112,8 @@ sed -i -e "s|rm: cannot remove 'e/slink'|rm: cannot remove 'e'|g" tests/rm/fail- sed -i -e "s|rm: cannot remove 'a/b/file'|rm: cannot remove 'a'|g" tests/rm/cycle.sh +sed -i -e "s|rm: cannot remove directory 'b/a/p'|rm: cannot remove 'b'|g" tests/rm/rm1.sh + +sed -i -e "s|rm: cannot remove 'a/1'|rm: cannot remove 'a'|g" tests/rm/rm2.sh + test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" From f8e96150f81d20848b5285acd6b4ef95045a31d4 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 4 Jun 2021 00:49:06 +0200 Subject: [PATCH 031/320] fix clippy warnings and spelling * add some missing LICENSE headers --- src/uu/du/src/du.rs | 20 +++++++++++--------- src/uu/head/src/head.rs | 5 +++++ src/uu/head/src/parse.rs | 5 +++++ src/uu/od/src/od.rs | 2 +- src/uu/od/src/parse_nrofbytes.rs | 6 +++--- src/uu/sort/src/sort.rs | 2 +- src/uu/tail/src/tail.rs | 1 - src/uu/truncate/src/truncate.rs | 2 +- src/uucore/src/lib/lib.rs | 2 +- src/uucore/src/lib/parser/parse_size.rs | 4 +++- tests/by-util/test_du.rs | 5 +++++ tests/by-util/test_head.rs | 7 ++++++- tests/by-util/test_od.rs | 5 +++++ tests/by-util/test_sort.rs | 5 +++++ tests/by-util/test_split.rs | 5 +++++ tests/by-util/test_tail.rs | 7 +++++++ tests/by-util/test_truncate.rs | 7 +++++++ 17 files changed, 71 insertions(+), 19 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 25ef47af3..c2d764ebf 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -1,9 +1,9 @@ -// This file is part of the uutils coreutils package. -// -// (c) Derek Chiang -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. +// * This file is part of the uutils coreutils package. +// * +// * (c) Derek Chiang +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. #[macro_use] extern crate uucore; @@ -25,6 +25,8 @@ use std::os::unix::fs::MetadataExt; use std::os::windows::fs::MetadataExt; #[cfg(windows)] use std::os::windows::io::AsRawHandle; +#[cfg(windows)] +use std::path::Path; use std::path::PathBuf; use std::time::{Duration, UNIX_EPOCH}; use uucore::parse_size::{parse_size, ParseSizeError}; @@ -161,7 +163,7 @@ fn birth_u64(meta: &Metadata) -> Option { } #[cfg(windows)] -fn get_size_on_disk(path: &PathBuf) -> u64 { +fn get_size_on_disk(path: &Path) -> u64 { let mut size_on_disk = 0; // bind file so it stays in scope until end of function @@ -193,7 +195,7 @@ fn get_size_on_disk(path: &PathBuf) -> u64 { } #[cfg(windows)] -fn get_file_info(path: &PathBuf) -> Option { +fn get_file_info(path: &Path) -> Option { let mut result = None; let file = match fs::File::open(path) { @@ -709,7 +711,7 @@ Try '{} --help' for more information.", fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String { // NOTE: // GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection - // GNU's du distinguishs between "invalid (suffix in) argument" + // GNU's du does distinguish between "invalid (suffix in) argument" match error { ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s), ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s), diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index d671ed665..2c13ed37d 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -1,3 +1,8 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + // spell-checker:ignore (vars) zlines use clap::{crate_version, App, Arg}; diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 6631dba0e..7e36594b5 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -1,3 +1,8 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + use std::ffi::OsString; use uucore::parse_size::{parse_size, ParseSizeError}; diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 080630b71..1a592f622 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -643,7 +643,7 @@ fn open_input_peek_reader( fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String { // NOTE: // GNU's od echos affected flag, -N or --read-bytes (-j or --skip-bytes, etc.), depending user's selection - // GNU's od distinguishs between "invalid (suffix in) argument" + // GNU's od does distinguish between "invalid (suffix in) argument" match error { ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s), ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s), diff --git a/src/uu/od/src/parse_nrofbytes.rs b/src/uu/od/src/parse_nrofbytes.rs index e51e15d39..d6329c60a 100644 --- a/src/uu/od/src/parse_nrofbytes.rs +++ b/src/uu/od/src/parse_nrofbytes.rs @@ -71,7 +71,7 @@ pub fn parse_number_of_bytes(s: &str) -> Result { }; factor .checked_mul(multiply) - .ok_or(ParseSizeError::SizeTooBig(s.to_string())) + .ok_or_else(|| ParseSizeError::SizeTooBig(s.to_string())) } #[test] @@ -80,12 +80,12 @@ fn test_parse_number_of_bytes() { assert_eq!(8, parse_number_of_bytes("010").unwrap()); assert_eq!(8 * 512, parse_number_of_bytes("010b").unwrap()); assert_eq!(8 * 1024, parse_number_of_bytes("010k").unwrap()); - assert_eq!(8 * 1048576, parse_number_of_bytes("010m").unwrap()); + assert_eq!(8 * 1_048_576, parse_number_of_bytes("010m").unwrap()); // hex input assert_eq!(15, parse_number_of_bytes("0xf").unwrap()); assert_eq!(15, parse_number_of_bytes("0XF").unwrap()); assert_eq!(27, parse_number_of_bytes("0x1b").unwrap()); assert_eq!(16 * 1024, parse_number_of_bytes("0x10k").unwrap()); - assert_eq!(16 * 1048576, parse_number_of_bytes("0x10m").unwrap()); + assert_eq!(16 * 1_048_576, parse_number_of_bytes("0x10m").unwrap()); } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index fca974dbd..9ec90519f 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1567,7 +1567,7 @@ fn open(path: impl AsRef) -> Box { fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String { // NOTE: // GNU's sort echos affected flag, -S or --buffer-size, depending user's selection - // GNU's sort distinguishs between "invalid (suffix in) argument" + // GNU's sort does distinguish between "invalid (suffix in) argument" match error { ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s), ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s), diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 76c799621..75cc43db1 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -5,7 +5,6 @@ // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// * // spell-checker:ignore (ToDO) seekable seek'd tail'ing ringbuffer ringbuf diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 78167ed77..8f02be874 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -152,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!( 1, "cannot stat '{}': No such file or directory", - reference.unwrap_or("".to_string()) + reference.unwrap_or_else(|| "".to_string()) ); // TODO: fix '--no-create' see test_reference and test_truncate_bytes_size } _ => crash!(1, "{}", e.to_string()), diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 84adeeb34..f765b7b3e 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -22,7 +22,7 @@ pub extern crate winapi; mod features; // feature-gated code modules mod macros; // crate macros (macro_rules-type; exported to `crate::...`) mod mods; // core cross-platform modules -mod parser; // string parsing moduls +mod parser; // string parsing modules // * cross-platform modules pub use crate::mods::backup_control; diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 12c410e57..58213adef 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -3,6 +3,8 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +// spell-checker:ignore (ToDO) hdsf ghead gtail + use std::convert::TryFrom; use std::error::Error; use std::fmt; @@ -77,7 +79,7 @@ pub fn parse_size(size: &str) -> Result { }; number .checked_mul(factor) - .ok_or(ParseSizeError::size_too_big(size)) + .ok_or_else(|| ParseSizeError::size_too_big(size)) } #[derive(Debug, PartialEq, Eq)] diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 2c656ced5..e4dd95ae1 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -1,3 +1,8 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + // spell-checker:ignore (paths) sublink subwords use crate::common::util::*; diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 6a2cdf1cd..2c4b66696 100755 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -1,4 +1,9 @@ -// spell-checker:ignore (words) bogusfile emptyfile +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + +// spell-checker:ignore (words) bogusfile emptyfile abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstu use crate::common::util::*; diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index 3d34e47a1..53b471dba 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -1,3 +1,8 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + extern crate unindent; use self::unindent::*; diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 204145719..3d1cfe120 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1,3 +1,8 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + // spell-checker:ignore (words) ints use crate::common::util::*; diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 53b545200..a1350534f 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -1,3 +1,8 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + extern crate rand; extern crate regex; diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index c296e2763..8478944e2 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -1,3 +1,10 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + +// spell-checker:ignore (ToDO) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile + extern crate tail; use crate::common::util::*; diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 72f7f780b..2da59035e 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -1,3 +1,10 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + +// spell-checker:ignore (words) RFILE + use crate::common::util::*; use std::io::{Seek, SeekFrom, Write}; From acd290d11f3351a342e67e351d38792ffd725286 Mon Sep 17 00:00:00 2001 From: Dean Li Date: Wed, 2 Jun 2021 21:40:47 +0800 Subject: [PATCH 032/320] more: fix unicode bug for breakline - Use `unicode_segmentation` and `unicode_width` to determine proper `break_line` position. - Keep track of total_width as suggested by @tertsdiepraam. - Add unittest for ZWJ unicode case Related to #2319. --- Cargo.lock | 2 ++ src/uu/more/Cargo.toml | 2 ++ src/uu/more/src/more.rs | 62 ++++++++++++++++++++++++++++++----------- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17fa9e2b7..3778db34c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2204,6 +2204,8 @@ dependencies = [ "nix 0.13.1", "redox_syscall 0.1.57", "redox_termios", + "unicode-segmentation", + "unicode-width", "uucore", "uucore_procs", ] diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index 9b1a3d7b6..b3b97e6dd 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -20,6 +20,8 @@ uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" } uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" } crossterm = ">=0.19" atty = "0.2.14" +unicode-width = "0.1.7" +unicode-segmentation = "1.7.1" [target.'cfg(target_os = "redox")'.dependencies] redox_termios = "0.1" diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 482c5491d..2e6771705 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -29,6 +29,9 @@ use crossterm::{ terminal, }; +use unicode_segmentation::UnicodeSegmentation; +use unicode_width::UnicodeWidthStr; + pub mod options { pub const SILENT: &str = "silent"; pub const LOGICAL: &str = "logical"; @@ -313,23 +316,30 @@ fn break_buff(buff: &str, cols: usize) -> Vec { lines } -fn break_line(mut line: &str, cols: usize) -> Vec { - let breaks = (line.len() / cols).saturating_add(1); - let mut lines = Vec::with_capacity(breaks); - // TODO: Use unicode width instead of the length in bytes. - if line.len() < cols { +fn break_line(line: &str, cols: usize) -> Vec { + let width = UnicodeWidthStr::width(line); + let mut lines = Vec::new(); + if width < cols { lines.push(line.to_string()); return lines; } - for _ in 1..=breaks { - let (line1, line2) = line.split_at(cols); - lines.push(line1.to_string()); - if line2.len() < cols { - lines.push(line2.to_string()); - break; + let gr_idx = UnicodeSegmentation::grapheme_indices(line, true); + let mut last_index = 0; + let mut total_width = 0; + for (index, grapheme) in gr_idx { + let width = UnicodeWidthStr::width(grapheme); + total_width += width; + + if total_width > cols { + lines.push(line[last_index..index].to_string()); + last_index = index; + total_width = width; } - line = line2; + } + + if last_index != line.len() { + lines.push(line[last_index..].to_string()); } lines } @@ -363,6 +373,7 @@ fn make_prompt_and_flush(stdout: &mut Stdout, lower_mark: u16, lc: u16) { #[cfg(test)] mod tests { use super::{break_line, calc_range}; + use unicode_width::UnicodeWidthStr; // It is good to test the above functions #[test] @@ -379,11 +390,12 @@ mod tests { } let lines = break_line(&test_string, 80); + let widths: Vec = lines + .iter() + .map(|s| UnicodeWidthStr::width(&s[..])) + .collect(); - assert_eq!( - (80, 80, 40), - (lines[0].len(), lines[1].len(), lines[2].len()) - ); + assert_eq!((80, 80, 40), (widths[0], widths[1], widths[2])); } #[test] @@ -397,4 +409,22 @@ mod tests { assert_eq!(20, lines[0].len()); } + + #[test] + fn test_break_line_zwj() { + let mut test_string = String::with_capacity(1100); + for _ in 0..20 { + test_string.push_str("👩🏻‍🔬"); + } + + let lines = break_line(&test_string, 80); + + let widths: Vec = lines + .iter() + .map(|s| UnicodeWidthStr::width(&s[..])) + .collect(); + + // Each 👩🏻‍🔬 is 6 character width it break line to the closest number to 80 => 6 * 13 = 78 + assert_eq!((78, 42), (widths[0], widths[1])); + } } From b7061d1817e55db3322a5a8a52f2010667288543 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Fri, 4 Jun 2021 16:29:32 +0200 Subject: [PATCH 033/320] README: Cleanup utility list In PR #2300 an old commit got merged putting back utilities that were already implemented into "To Do". This commit reverts this. In addition it moves `numfmt` to Semi-Done and sorts the Semi-Done column alphabetically. This should now be the up-to-date list of utilities. There are 96 utilities in Done or Semi-Done and `ls -1 src/uu | wc -l` also outputs 96. --- README.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index fde01d64a..fd8709b64 100644 --- a/README.md +++ b/README.md @@ -342,22 +342,22 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). | Done | Semi-Done | To Do | |-----------|-----------|--------| | arch | cp | chcon | -| base32 | expr | csplit | -| base64 | install | dd | -| basename | ls | df | -| cat | more | numfmt | -| chgrp | od (`--strings` and 128-bit data types missing) | runcon | -| chmod | printf | stty | -| chown | sort | | -| chroot | split | | -| cksum | tail | | -| comm | test | | -| csplit | date | | -| cut | join | | -| dircolors | df | | +| base32 | date | dd | +| base64 | df | runcon | +| basename | expr | stty | +| cat | install | | +| chgrp | join | | +| chmod | ls | | +| chown | more | | +| chroot | numfmt | | +| cksum | od (`--strings` and 128-bit data types missing) | | +| comm | pr | | +| csplit | printf | | +| cut | sort | | +| dircolors | split | | | dirname | tac | | -| du | pr | | -| echo | | | +| du | tail | | +| echo | test | | | env | | | | expand | | | | factor | | | @@ -374,12 +374,12 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). | link | | | | ln | | | | logname | | | -| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | -| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | -| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | -| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | -| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | -| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | +| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | | mkdir | | | | mkfifo | | | | mknod | | | From 81e07a6a4dc829bd57b2527b50048cc3f8a5e61c Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 4 Jun 2021 17:22:45 +0200 Subject: [PATCH 034/320] od: replace 'piped_stdin' to make test stable --- tests/by-util/test_od.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index 53b471dba..33d7d4dc4 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -815,7 +815,10 @@ fn test_od_invalid_bytes() { const INVALID_SIZE: &str = "1fb4t"; const BIG_SIZE: &str = "1Y"; - let input: [u8; 8] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + // NOTE: + // GNU's od (8.32) with option '--width' does not accept 'Y' as valid suffix. + // According to the man page it should be valid in the same way it is valid for + // '--read-bytes' and '--skip-bytes'. let options = [ "--read-bytes", @@ -826,8 +829,8 @@ fn test_od_invalid_bytes() { for option in &options { new_ucmd!() .arg(format!("{}={}", option, INVALID_SIZE)) - .run_piped_stdin(&input[..]) - .failure() + .arg("file") + .fails() .code_is(1) .stderr_only(format!( "od: invalid {} argument '{}'", @@ -837,8 +840,8 @@ fn test_od_invalid_bytes() { #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .arg(format!("{}={}", option, BIG_SIZE)) - .run_piped_stdin(&input[..]) - .failure() + .arg("file") + .fails() .code_is(1) .stderr_only(format!("od: {} argument '{}' too large", option, BIG_SIZE)); } From 14303c524fa744ad824155925fc764209a4f4976 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 4 Jun 2021 23:55:24 +0200 Subject: [PATCH 035/320] gnu/rm: make another test pass --- util/build-gnu.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 83136e733..1ffde8311 100644 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -116,4 +116,6 @@ sed -i -e "s|rm: cannot remove directory 'b/a/p'|rm: cannot remove 'b'|g" tests/ sed -i -e "s|rm: cannot remove 'a/1'|rm: cannot remove 'a'|g" tests/rm/rm2.sh +sed -i -e "s|removed directory 'a/'|removed directory 'a'|g" tests/rm/v-slash.sh + test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" From 285b27c9b311e92833d310c33b77cf617ebc9623 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 5 Jun 2021 11:04:42 +0200 Subject: [PATCH 036/320] du: add --app as alias of --apparent-size to match GNU --- src/uu/du/src/du.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index a46f74100..c5fff2ed7 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -431,6 +431,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { although the apparent size is usually smaller, it may be larger due to holes \ in ('sparse') files, internal fragmentation, indirect blocks, and the like" ) + .alias("app") // The GNU testsuite uses this alias ) .arg( Arg::with_name(options::BLOCK_SIZE) From 420e9322eac301906ea87bb03a3a9304ebb5f846 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 5 Jun 2021 14:07:09 +0200 Subject: [PATCH 037/320] more: do not accidentically hide last line --- src/uu/more/src/more.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 27829d577..d37dd46f4 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -349,7 +349,7 @@ fn calc_range(mut upper_mark: u16, rows: u16, line_count: u16) -> (u16, u16) { let mut lower_mark = upper_mark.saturating_add(rows); if lower_mark >= line_count { - upper_mark = line_count.saturating_sub(rows); + upper_mark = line_count.saturating_sub(rows).saturating_add(1); lower_mark = line_count; } else { lower_mark = lower_mark.saturating_sub(1) From 2760efb01de4f135287ca3763c6e16f547e1f82e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 5 Jun 2021 14:42:43 +0200 Subject: [PATCH 038/320] more: fix test --- src/uu/more/src/more.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index d37dd46f4..c1df9afa0 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -380,7 +380,7 @@ mod tests { fn test_calc_range() { assert_eq!((0, 24), calc_range(0, 25, 100)); assert_eq!((50, 74), calc_range(50, 25, 100)); - assert_eq!((75, 100), calc_range(85, 25, 100)); + assert_eq!((76, 100), calc_range(85, 25, 100)); } #[test] fn test_break_lines_long() { From 74a7da7b527a8942c45efd7b40c7be0f032bc85d Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sat, 5 Jun 2021 19:56:20 +0200 Subject: [PATCH 039/320] id: clean-up of clap options and usage/help text * add conflicts_with for '-G' * add required flags for '-r' and '-n' * add usage/help texts from BSD's `id` --- src/uu/id/src/id.rs | 99 +++++++++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 40 deletions(-) diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 77b185f24..b32988ebc 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -9,6 +9,10 @@ // Synced with: // http://ftp-archive.freebsd.org/mirror/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/shellutils/src/id.c // http://www.opensource.apple.com/source/shell_cmds/shell_cmds-118/id/id.c +// +// This is not based on coreutils (8.32) GNU's `id`. +// This is based on BSD's `id` (noticeable in functionality, usage text, options text, etc.) +// // spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag @@ -70,18 +74,19 @@ mod audit { } } -static ABOUT: &str = "Display user and group information for the specified USER,\n or (when USER omitted) for the current user."; +static ABOUT: &str = "The id utility displays the user and group names and numeric IDs, of the calling process, to the standard output. If the real and effective IDs are different, both are displayed, otherwise only the real ID is displayed.\n\nIf a user (login name or user ID) is specified, the user and group IDs of that user are displayed. In this case, the real and effective IDs are assumed to be the same."; -static OPT_AUDIT: &str = "audit"; -static OPT_EFFECTIVE_USER: &str = "effective-user"; -static OPT_GROUP: &str = "group"; -static OPT_GROUPS: &str = "groups"; -static OPT_HUMAN_READABLE: &str = "human-readable"; -static OPT_NAME: &str = "name"; -static OPT_PASSWORD: &str = "password"; -static OPT_REAL_ID: &str = "real-id"; - -static ARG_USERS: &str = "users"; +mod options { + pub const OPT_AUDIT: &str = "audit"; // GNU's id does not have this + pub const OPT_EFFECTIVE_USER: &str = "user"; + pub const OPT_GROUP: &str = "group"; + pub const OPT_GROUPS: &str = "groups"; + pub const OPT_HUMAN_READABLE: &str = "human-readable"; // GNU's id does not have this + pub const OPT_NAME: &str = "name"; + pub const OPT_PASSWORD: &str = "password"; // GNU's id does not have this + pub const OPT_REAL_ID: &str = "real"; + pub const ARG_USERS: &str = "USER"; +} fn get_usage() -> String { format!("{0} [OPTION]... [USER]", executable!()) @@ -95,57 +100,76 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .about(ABOUT) .usage(&usage[..]) .arg( - Arg::with_name(OPT_AUDIT) + Arg::with_name(options::OPT_AUDIT) .short("A") - .help("Display the process audit (not available on Linux)"), + .help("Display the process audit user ID and other process audit properties, which requires privilege (not available on Linux)."), ) .arg( - Arg::with_name(OPT_EFFECTIVE_USER) + Arg::with_name(options::OPT_EFFECTIVE_USER) .short("u") - .long("user") - .help("Display the effective user ID as a number"), + .long(options::OPT_EFFECTIVE_USER) + .help("Display the effective user ID as a number."), ) .arg( - Arg::with_name(OPT_GROUP) + Arg::with_name(options::OPT_GROUP) .short("g") - .long(OPT_GROUP) + .long(options::OPT_GROUP) .help("Display the effective group ID as a number"), ) .arg( - Arg::with_name(OPT_GROUPS) + Arg::with_name(options::OPT_GROUPS) .short("G") - .long(OPT_GROUPS) - .help("Display the different group IDs"), + .long(options::OPT_GROUPS) + .conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER, options::OPT_HUMAN_READABLE, options::OPT_PASSWORD, options::OPT_AUDIT]) + .help("Display the different group IDs as white-space separated numbers, in no particular order."), ) .arg( - Arg::with_name(OPT_HUMAN_READABLE) + Arg::with_name(options::OPT_HUMAN_READABLE) .short("p") - .help("Make the output human-readable"), + .help("Make the output human-readable. Each display is on a separate line."), ) .arg( - Arg::with_name(OPT_NAME) + Arg::with_name(options::OPT_NAME) .short("n") - .help("Display the name of the user or group ID for the -G, -g and -u options"), + .long(options::OPT_NAME) + .help("Display the name of the user or group ID for the -G, -g and -u options instead of the number. If any of the ID numbers cannot be mapped into names, the number will be displayed as usual."), ) .arg( - Arg::with_name(OPT_PASSWORD) + Arg::with_name(options::OPT_PASSWORD) .short("P") - .help("Display the id as a password file entry"), + .help("Display the id as a password file entry."), ) .arg( - Arg::with_name(OPT_REAL_ID) + Arg::with_name(options::OPT_REAL_ID) .short("r") - .help("Display the real ID for the -g and -u options"), + .long(options::OPT_REAL_ID) + .help("Display the real ID for the -g and -u options instead of the effective ID."), + ) + .arg( + Arg::with_name(options::ARG_USERS) + .multiple(true) + .takes_value(true) + .value_name(options::ARG_USERS), ) - .arg(Arg::with_name(ARG_USERS).multiple(true).takes_value(true)) .get_matches_from(args); + let nflag = matches.is_present(options::OPT_NAME); + let uflag = matches.is_present(options::OPT_EFFECTIVE_USER); + let gflag = matches.is_present(options::OPT_GROUP); + let gsflag = matches.is_present(options::OPT_GROUPS); + let rflag = matches.is_present(options::OPT_REAL_ID); + + // -ugG + if (nflag || rflag) && !(uflag || gflag || gsflag) { + crash!(1, "cannot print only names or real IDs in default format"); + } + let users: Vec = matches - .values_of(ARG_USERS) + .values_of(options::ARG_USERS) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - if matches.is_present(OPT_AUDIT) { + if matches.is_present(options::OPT_AUDIT) { auditid(); return 0; } @@ -159,11 +183,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } }; - let nflag = matches.is_present(OPT_NAME); - let uflag = matches.is_present(OPT_EFFECTIVE_USER); - let gflag = matches.is_present(OPT_GROUP); - let rflag = matches.is_present(OPT_REAL_ID); - if gflag { let id = possible_pw .map(|p| p.gid()) @@ -194,7 +213,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return 0; } - if matches.is_present(OPT_GROUPS) { + if gsflag { println!( "{}", if nflag { @@ -218,12 +237,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return 0; } - if matches.is_present(OPT_PASSWORD) { + if matches.is_present(options::OPT_PASSWORD) { pline(possible_pw.map(|v| v.uid())); return 0; }; - if matches.is_present(OPT_HUMAN_READABLE) { + if matches.is_present(options::OPT_HUMAN_READABLE) { pretty(possible_pw); return 0; } From b9fe76ab92b39ea0a4b009277184f151a86cf6ce Mon Sep 17 00:00:00 2001 From: Dean Li Date: Sat, 5 Jun 2021 22:33:30 +0800 Subject: [PATCH 040/320] more: Show next file at bottom line Implement feature requested in #2318. --- src/uu/more/src/more.rs | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 27829d577..4f5b95a48 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -143,7 +143,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if let Some(files) = matches.values_of(options::FILES) { let mut stdout = setup_term(); let length = files.len(); - for (idx, file) in files.enumerate() { + + let mut files_iter = files.peekable(); + while let (Some(file), next_file) = (files_iter.next(), files_iter.peek()) { let file = Path::new(file); if file.is_dir() { terminal::disable_raw_mode().unwrap(); @@ -160,15 +162,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } let mut reader = BufReader::new(File::open(file).unwrap()); reader.read_to_string(&mut buff).unwrap(); - let is_last = idx + 1 == length; - more(&buff, &mut stdout, is_last); + more(&buff, &mut stdout, next_file.copied()); buff.clear(); } reset_term(&mut stdout); } else if atty::isnt(atty::Stream::Stdin) { stdin().read_to_string(&mut buff).unwrap(); let mut stdout = setup_term(); - more(&buff, &mut stdout, true); + more(&buff, &mut stdout, None); reset_term(&mut stdout); } else { show_usage_error!("bad usage"); @@ -203,7 +204,7 @@ fn reset_term(stdout: &mut std::io::Stdout) { #[inline(always)] fn reset_term(_: &mut usize) {} -fn more(buff: &str, mut stdout: &mut Stdout, is_last: bool) { +fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) { let (cols, rows) = terminal::size().unwrap(); let lines = break_buff(buff, usize::from(cols)); let line_count: u16 = lines.len().try_into().unwrap(); @@ -217,8 +218,11 @@ fn more(buff: &str, mut stdout: &mut Stdout, is_last: bool) { &mut stdout, lines.clone(), line_count, + next_file, ); + let is_last = next_file.is_none(); + // Specifies whether we have reached the end of the file and should // return on the next key press. However, we immediately return when // this is the last file. @@ -270,6 +274,7 @@ fn more(buff: &str, mut stdout: &mut Stdout, is_last: bool) { &mut stdout, lines.clone(), line_count, + next_file, ); if lines_left == 0 { @@ -288,6 +293,7 @@ fn draw( mut stdout: &mut std::io::Stdout, lines: Vec, lc: u16, + next_file: Option<&str>, ) { execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap(); let (up_mark, lower_mark) = calc_range(*upper_mark, rows, lc); @@ -302,7 +308,7 @@ fn draw( .write_all(format!("\r{}\n", line).as_bytes()) .unwrap(); } - make_prompt_and_flush(&mut stdout, lower_mark, lc); + make_prompt_and_flush(&mut stdout, lower_mark, lc, next_file); *upper_mark = up_mark; } @@ -358,12 +364,20 @@ fn calc_range(mut upper_mark: u16, rows: u16, line_count: u16) -> (u16, u16) { } // Make a prompt similar to original more -fn make_prompt_and_flush(stdout: &mut Stdout, lower_mark: u16, lc: u16) { +fn make_prompt_and_flush(stdout: &mut Stdout, lower_mark: u16, lc: u16, next_file: Option<&str>) { + let status = if lower_mark == lc { + format!("Next file: {}", next_file.unwrap_or_default()) + } else { + format!( + "{}%", + (lower_mark as f64 / lc as f64 * 100.0).round() as u16 + ) + }; write!( stdout, - "\r{}--More--({}%){}", + "\r{}--More--({}){}", Attribute::Reverse, - ((lower_mark as f64 / lc as f64) * 100.0).round() as u16, + status, Attribute::Reset ) .unwrap(); From 7f07bd82d4ee7095e902ce1016e94d96c437f3d9 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 6 Jun 2021 13:25:44 +0200 Subject: [PATCH 041/320] hashsum: document how to benchmark blake2 --- src/uu/hashsum/BENCHMARKING.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/uu/hashsum/BENCHMARKING.md diff --git a/src/uu/hashsum/BENCHMARKING.md b/src/uu/hashsum/BENCHMARKING.md new file mode 100644 index 000000000..cef710a19 --- /dev/null +++ b/src/uu/hashsum/BENCHMARKING.md @@ -0,0 +1,9 @@ +## Benchmarking hashsum + +### To bench blake2 + +Taken from: https://github.com/uutils/coreutils/pull/2296 + +With a large file: +$ hyperfine "./target/release/coreutils hashsum --b2sum large-file" "b2sum large-file" + From 7e41b58845dc887409ad66803f31ac480a0de927 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 6 Jun 2021 13:38:48 +0200 Subject: [PATCH 042/320] ride along: refresh cargo.lock --- Cargo.lock | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c3aae831..43d491cef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,12 +43,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "array-init" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6945cc5422176fc5e602e590c2878d2c2acd9a4fe20a4baa7c28022521698ec6" - [[package]] name = "arrayref" version = "0.3.6" @@ -710,9 +704,9 @@ checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" [[package]] name = "heck" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" dependencies = [ "unicode-segmentation", ] @@ -1393,12 +1387,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" -dependencies = [ - "byteorder", -] +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" @@ -1511,9 +1502,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] From 2dd6824e7628d9d3fad77ed44d57654cb45ef6d4 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 6 Jun 2021 11:29:32 +0200 Subject: [PATCH 043/320] sort: never use a bigger buffer than requested A min buffer size of 8KB makes sense in practice, but decreases testability. --- src/uu/sort/src/ext_sort.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 9b1845efa..d344df428 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -30,7 +30,7 @@ use crate::{ compare_by, merge, output_sorted_lines, sort_by, GlobalSettings, }; -const MIN_BUFFER_SIZE: usize = 8_000; +const START_BUFFER_SIZE: usize = 8_000; /// Sort files by using auxiliary files for storing intermediate chunks (if needed), and output the result. pub fn ext_sort(files: &mut impl Iterator>, settings: &GlobalSettings) { @@ -132,7 +132,14 @@ fn reader_writer( for _ in 0..2 { chunks::read( &mut sender_option, - vec![0; MIN_BUFFER_SIZE], + vec![ + 0; + if START_BUFFER_SIZE < buffer_size { + START_BUFFER_SIZE + } else { + buffer_size + } + ], Some(buffer_size), &mut carry_over, &mut file, From 66359a0f564561edcba6ce154c7536a1cba7869e Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 6 Jun 2021 11:31:42 +0200 Subject: [PATCH 044/320] sort: insert line separators after non-empty files If files don't end witht a line separator we have to insert one, otherwise the last line will be combined with the first line of the next file. --- src/uu/sort/src/chunks.rs | 20 +++++++++++++++++--- tests/by-util/test_sort.rs | 8 ++++++++ tests/fixtures/sort/no_trailing_newline1.txt | 2 ++ tests/fixtures/sort/no_trailing_newline2.txt | 1 + 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/sort/no_trailing_newline1.txt create mode 100644 tests/fixtures/sort/no_trailing_newline2.txt diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index 23567833b..3705f2cf7 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -175,6 +175,7 @@ fn read_to_buffer( separator: u8, ) -> (usize, bool) { let mut read_target = &mut buffer[start_offset..]; + let mut last_file_target_size = read_target.len(); loop { match file.read(read_target) { Ok(0) => { @@ -208,14 +209,27 @@ fn read_to_buffer( read_target = &mut buffer[len..]; } } else { - // This file is empty. + // This file has been fully read. + let mut leftover_len = read_target.len(); + if last_file_target_size != leftover_len { + // The file was not empty. + let read_len = buffer.len() - leftover_len; + if buffer[read_len - 1] != separator { + // The file did not end with a separator. We have to insert one. + buffer[read_len] = separator; + leftover_len -= 1; + } + let read_len = buffer.len() - leftover_len; + read_target = &mut buffer[read_len..]; + } if let Some(next_file) = next_files.next() { // There is another file. + last_file_target_size = leftover_len; *file = next_file; } else { // This was the last file. - let leftover_len = read_target.len(); - return (buffer.len() - leftover_len, false); + let read_len = buffer.len() - leftover_len; + return (read_len, false); } } } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 02636b027..97127b995 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -792,3 +792,11 @@ fn test_nonexistent_file() { fn test_blanks() { test_helper("blanks", &["-b", "--ignore-blanks"]); } + +#[test] +fn sort_multiple() { + new_ucmd!() + .args(&["no_trailing_newline1.txt", "no_trailing_newline2.txt"]) + .succeeds() + .stdout_is("a\nb\nb\n"); +} diff --git a/tests/fixtures/sort/no_trailing_newline1.txt b/tests/fixtures/sort/no_trailing_newline1.txt new file mode 100644 index 000000000..0a207c060 --- /dev/null +++ b/tests/fixtures/sort/no_trailing_newline1.txt @@ -0,0 +1,2 @@ +a +b \ No newline at end of file diff --git a/tests/fixtures/sort/no_trailing_newline2.txt b/tests/fixtures/sort/no_trailing_newline2.txt new file mode 100644 index 000000000..63d8dbd40 --- /dev/null +++ b/tests/fixtures/sort/no_trailing_newline2.txt @@ -0,0 +1 @@ +b \ No newline at end of file From 5a5c7c5a346ca0971f7f42ce216cafbb8ae8f334 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 6 Jun 2021 11:33:22 +0200 Subject: [PATCH 045/320] sort: properly check for empty reads --- src/uu/sort/src/chunks.rs | 22 +++++++++++----------- tests/by-util/test_sort.rs | 9 +++++++++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index 3705f2cf7..dde6febd3 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -102,17 +102,17 @@ pub fn read( carry_over.clear(); carry_over.extend_from_slice(&buffer[read..]); - let payload = Chunk::new(buffer, |buf| { - let mut lines = unsafe { - // SAFETY: It is safe to transmute to a vector of lines with shorter lifetime, - // because it was only temporarily transmuted to a Vec> to make recycling possible. - std::mem::transmute::>, Vec>>(lines) - }; - let read = crash_if_err!(1, std::str::from_utf8(&buf[..read])); - parse_lines(read, &mut lines, separator, &settings); - lines - }); - if !payload.borrow_lines().is_empty() { + if read != 0 { + let payload = Chunk::new(buffer, |buf| { + let mut lines = unsafe { + // SAFETY: It is safe to transmute to a vector of lines with shorter lifetime, + // because it was only temporarily transmuted to a Vec> to make recycling possible. + std::mem::transmute::>, Vec>>(lines) + }; + let read = crash_if_err!(1, std::str::from_utf8(&buf[..read])); + parse_lines(read, &mut lines, separator, &settings); + lines + }); sender.send(payload).unwrap(); } if !should_continue { diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 97127b995..d100e2141 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -800,3 +800,12 @@ fn sort_multiple() { .succeeds() .stdout_is("a\nb\nb\n"); } + +#[test] +fn sort_empty_chunk() { + new_ucmd!() + .args(&["-S", "40B"]) + .pipe_in("a\na\n") + .succeeds() + .stdout_is("a\na\n"); +} From 8d213219c7c4120bf70c926d79fe517b2daf1b29 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 6 Jun 2021 12:07:06 +0200 Subject: [PATCH 046/320] sort: implement --compress-program --- src/uu/sort/src/ext_sort.rs | 75 ++++++++++++++++++++++++++++++++----- src/uu/sort/src/merge.rs | 10 +++-- src/uu/sort/src/sort.rs | 14 ++++++- tests/by-util/test_sort.rs | 31 +++++++++++++++ 4 files changed, 115 insertions(+), 15 deletions(-) diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index d344df428..2d8513e9f 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -12,8 +12,12 @@ //! The buffers for the individual chunks are recycled. There are two buffers. use std::cmp::Ordering; +use std::fs::File; +use std::io::BufReader; use std::io::{BufWriter, Write}; use std::path::Path; +use std::process::Child; +use std::process::{Command, Stdio}; use std::{ fs::OpenOptions, io::Read, @@ -25,6 +29,7 @@ use itertools::Itertools; use tempfile::TempDir; +use crate::Line; use crate::{ chunks::{self, Chunk}, compare_by, merge, output_sorted_lines, sort_by, GlobalSettings, @@ -63,10 +68,31 @@ pub fn ext_sort(files: &mut impl Iterator>, settings ); match read_result { ReadResult::WroteChunksToFile { chunks_written } => { - let files = (0..chunks_written) - .map(|chunk_num| tmp_dir.path().join(chunk_num.to_string())) - .collect::>(); - let mut merger = merge::merge(&files, settings); + let mut children = Vec::new(); + let files = (0..chunks_written).map(|chunk_num| { + let file_path = tmp_dir.path().join(chunk_num.to_string()); + let file = File::open(file_path).unwrap(); + if let Some(compress_prog) = &settings.compress_prog { + let mut command = Command::new(compress_prog); + command.stdin(file).stdout(Stdio::piped()).arg("-d"); + let mut child = crash_if_err!( + 2, + command.spawn().map_err(|err| format!( + "couldn't execute compress program: errno {}", + err.raw_os_error().unwrap() + )) + ); + let child_stdout = child.stdout.take().unwrap(); + children.push(child); + Box::new(BufReader::new(child_stdout)) as Box + } else { + Box::new(BufReader::new(file)) as Box + } + }); + let mut merger = merge::merge(files, settings); + for child in children { + assert_child_success(child, settings.compress_prog.as_ref().unwrap()); + } merger.write_all(settings); } ReadResult::SortedSingleChunk(chunk) => { @@ -178,6 +204,7 @@ fn reader_writer( write( &mut chunk, &tmp_dir.path().join(file_number.to_string()), + settings.compress_prog.as_deref(), separator, ); @@ -200,14 +227,42 @@ fn reader_writer( } /// Write the lines in `chunk` to `file`, separated by `separator`. -fn write(chunk: &mut Chunk, file: &Path, separator: u8) { +/// `compress_prog` is used to optionally compress file contents. +fn write(chunk: &mut Chunk, file: &Path, compress_prog: Option<&str>, separator: u8) { chunk.with_lines_mut(|lines| { // Write the lines to the file let file = crash_if_err!(1, OpenOptions::new().create(true).write(true).open(file)); - let mut writer = BufWriter::new(file); - for s in lines.iter() { - crash_if_err!(1, writer.write_all(s.line.as_bytes())); - crash_if_err!(1, writer.write_all(&[separator])); - } + if let Some(compress_prog) = compress_prog { + let mut command = Command::new(compress_prog); + command.stdin(Stdio::piped()).stdout(file); + let mut child = crash_if_err!( + 2, + command.spawn().map_err(|err| format!( + "couldn't execute compress program: errno {}", + err.raw_os_error().unwrap() + )) + ); + let mut writer = BufWriter::new(child.stdin.take().unwrap()); + write_lines(lines, &mut writer, separator); + writer.flush().unwrap(); + drop(writer); + assert_child_success(child, compress_prog); + } else { + let mut writer = BufWriter::new(file); + write_lines(lines, &mut writer, separator); + }; }); } + +fn write_lines<'a, T: Write>(lines: &[Line<'a>], writer: &mut T, separator: u8) { + for s in lines { + crash_if_err!(1, writer.write_all(s.line.as_bytes())); + crash_if_err!(1, writer.write_all(&[separator])); + } +} + +fn assert_child_success(mut child: Child, program: &str) { + if !matches!(child.wait().map(|e| e.code()), Ok(Some(0)) | Ok(None)) { + crash!(2, "'{}' terminated abnormally", program) + } +} diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index 696353829..b47c58c08 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -9,7 +9,6 @@ use std::{ cmp::Ordering, - ffi::OsStr, io::{Read, Write}, iter, rc::Rc, @@ -21,15 +20,18 @@ use compare::Compare; use crate::{ chunks::{self, Chunk}, - compare_by, open, GlobalSettings, + compare_by, GlobalSettings, }; // Merge already sorted files. -pub fn merge<'a>(files: &[impl AsRef], settings: &'a GlobalSettings) -> FileMerger<'a> { +pub fn merge>>( + files: F, + settings: &GlobalSettings, +) -> FileMerger { let (request_sender, request_receiver) = channel(); let mut reader_files = Vec::with_capacity(files.len()); let mut loaded_receivers = Vec::with_capacity(files.len()); - for (file_number, file) in files.iter().map(open).enumerate() { + for (file_number, file) in files.enumerate() { let (sender, receiver) = sync_channel(2); loaded_receivers.push(receiver); reader_files.push(ReaderFile { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 5825e73bd..6cdf051c1 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -95,6 +95,7 @@ static OPT_PARALLEL: &str = "parallel"; static OPT_FILES0_FROM: &str = "files0-from"; static OPT_BUF_SIZE: &str = "buffer-size"; static OPT_TMP_DIR: &str = "temporary-directory"; +static OPT_COMPRESS_PROG: &str = "compress-program"; static ARG_FILES: &str = "files"; @@ -155,6 +156,7 @@ pub struct GlobalSettings { zero_terminated: bool, buffer_size: usize, tmp_dir: PathBuf, + compress_prog: Option, } impl GlobalSettings { @@ -223,6 +225,7 @@ impl Default for GlobalSettings { zero_terminated: false, buffer_size: DEFAULT_BUF_SIZE, tmp_dir: PathBuf::new(), + compress_prog: None, } } } @@ -1076,6 +1079,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .value_name("DIR"), ) + .arg( + Arg::with_name(OPT_COMPRESS_PROG) + .long(OPT_COMPRESS_PROG) + .help("compress temporary files with PROG, decompress with PROG -d") + .long_help("PROG has to take input from stdin and output to stdout") + .value_name("PROG") + ) .arg( Arg::with_name(OPT_FILES0_FROM) .long(OPT_FILES0_FROM) @@ -1165,6 +1175,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(PathBuf::from) .unwrap_or_else(env::temp_dir); + settings.compress_prog = matches.value_of(OPT_COMPRESS_PROG).map(String::from); + settings.zero_terminated = matches.is_present(OPT_ZERO_TERMINATED); settings.merge = matches.is_present(OPT_MERGE); @@ -1240,7 +1252,7 @@ fn output_sorted_lines<'a>(iter: impl Iterator>, settings: & fn exec(files: &[String], settings: &GlobalSettings) -> i32 { if settings.merge { - let mut file_merger = merge::merge(files, settings); + let mut file_merger = merge::merge(files.iter().map(open), settings); file_merger.write_all(settings); } else if settings.check { if files.len() > 1 { diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index d100e2141..e731d5b1d 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -809,3 +809,34 @@ fn sort_empty_chunk() { .succeeds() .stdout_is("a\na\n"); } + +#[test] +#[cfg(target_os = "linux")] +fn test_compress() { + new_ucmd!() + .args(&[ + "ext_sort.txt", + "-n", + "--compress-program", + "gzip", + "-S", + "10", + ]) + .succeeds() + .stdout_only_fixture("ext_sort.expected"); +} + +#[test] +fn test_compress_fail() { + new_ucmd!() + .args(&[ + "ext_sort.txt", + "-n", + "--compress-program", + "nonexistent-program", + "-S", + "10", + ]) + .fails() + .stderr_only("sort: couldn't execute compress program: errno 2"); +} From 7c9da82b394e0be8b640afb631f7d512a5351aab Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 6 Jun 2021 13:50:38 +0200 Subject: [PATCH 047/320] sort: implement --batch-size --- src/uu/sort/src/ext_sort.rs | 2 +- src/uu/sort/src/merge.rs | 63 ++++++++++++++++++++++++++++++++++--- src/uu/sort/src/sort.rs | 17 +++++++++- tests/by-util/test_sort.rs | 13 ++++++++ 4 files changed, 88 insertions(+), 7 deletions(-) diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 2d8513e9f..91a7ca360 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -89,7 +89,7 @@ pub fn ext_sort(files: &mut impl Iterator>, settings Box::new(BufReader::new(file)) as Box } }); - let mut merger = merge::merge(files, settings); + let mut merger = merge::merge_with_file_limit(files, settings); for child in children { assert_child_success(child, settings.compress_prog.as_ref().unwrap()); } diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index b47c58c08..478b454b6 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -9,7 +9,8 @@ use std::{ cmp::Ordering, - io::{Read, Write}, + fs::File, + io::{BufWriter, Read, Write}, iter, rc::Rc, sync::mpsc::{channel, sync_channel, Receiver, Sender, SyncSender}, @@ -17,6 +18,7 @@ use std::{ }; use compare::Compare; +use itertools::Itertools; use crate::{ chunks::{self, Chunk}, @@ -24,13 +26,60 @@ use crate::{ }; // Merge already sorted files. -pub fn merge>>( +pub fn merge_with_file_limit>>( + files: F, + settings: &GlobalSettings, +) -> FileMerger { + if files.len() > settings.merge_batch_size { + let tmp_dir = tempfile::Builder::new() + .prefix("uutils_sort") + .tempdir_in(&settings.tmp_dir) + .unwrap(); + let mut batch_number = 0; + let mut remaining_files = files.len(); + let batches = files.chunks(settings.merge_batch_size); + let mut batches = batches.into_iter(); + while batch_number + remaining_files > settings.merge_batch_size && remaining_files != 0 { + remaining_files = remaining_files.saturating_sub(settings.merge_batch_size); + let mut merger = merge_without_limit(batches.next().unwrap(), settings); + let tmp_file = File::create(tmp_dir.path().join(batch_number.to_string())).unwrap(); + merger.write_all_to(settings, &mut BufWriter::new(tmp_file)); + batch_number += 1; + } + let batch_files = (0..batch_number).map(|n| { + Box::new(File::open(tmp_dir.path().join(n.to_string())).unwrap()) + as Box + }); + if batch_number > settings.merge_batch_size { + assert!(batches.next().is_none()); + merge_with_file_limit( + Box::new(batch_files) as Box>>, + settings, + ) + } else { + let final_batch = batches.next(); + assert!(batches.next().is_none()); + merge_without_limit( + batch_files.chain(final_batch.into_iter().flatten()), + settings, + ) + } + } else { + merge_without_limit(files, settings) + } +} + +/// Merge files without limiting how many files are concurrently open +/// +/// It is the responsibility of the caller to ensure that `files` yields only +/// as many files as we are allowed to open concurrently. +fn merge_without_limit>>( files: F, settings: &GlobalSettings, ) -> FileMerger { let (request_sender, request_receiver) = channel(); - let mut reader_files = Vec::with_capacity(files.len()); - let mut loaded_receivers = Vec::with_capacity(files.len()); + let mut reader_files = Vec::with_capacity(files.size_hint().0); + let mut loaded_receivers = Vec::with_capacity(files.size_hint().0); for (file_number, file) in files.enumerate() { let (sender, receiver) = sync_channel(2); loaded_receivers.push(receiver); @@ -148,7 +197,11 @@ impl<'a> FileMerger<'a> { /// Write the merged contents to the output file. pub fn write_all(&mut self, settings: &GlobalSettings) { let mut out = settings.out_writer(); - while self.write_next(settings, &mut out) {} + self.write_all_to(settings, &mut out); + } + + pub fn write_all_to(&mut self, settings: &GlobalSettings, out: &mut impl Write) { + while self.write_next(settings, out) {} } fn write_next(&mut self, settings: &GlobalSettings, out: &mut impl Write) -> bool { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 6cdf051c1..70e3325ad 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -96,6 +96,7 @@ static OPT_FILES0_FROM: &str = "files0-from"; static OPT_BUF_SIZE: &str = "buffer-size"; static OPT_TMP_DIR: &str = "temporary-directory"; static OPT_COMPRESS_PROG: &str = "compress-program"; +static OPT_BATCH_SIZE: &str = "batch-size"; static ARG_FILES: &str = "files"; @@ -157,6 +158,7 @@ pub struct GlobalSettings { buffer_size: usize, tmp_dir: PathBuf, compress_prog: Option, + merge_batch_size: usize, } impl GlobalSettings { @@ -226,6 +228,7 @@ impl Default for GlobalSettings { buffer_size: DEFAULT_BUF_SIZE, tmp_dir: PathBuf::new(), compress_prog: None, + merge_batch_size: 16, } } } @@ -1086,6 +1089,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long_help("PROG has to take input from stdin and output to stdout") .value_name("PROG") ) + .arg( + Arg::with_name(OPT_BATCH_SIZE) + .long(OPT_BATCH_SIZE) + .help("Merge at most N_MERGE inputs at once.") + .value_name("N_MERGE") + ) .arg( Arg::with_name(OPT_FILES0_FROM) .long(OPT_FILES0_FROM) @@ -1177,6 +1186,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.compress_prog = matches.value_of(OPT_COMPRESS_PROG).map(String::from); + if let Some(n_merge) = matches.value_of(OPT_BATCH_SIZE) { + settings.merge_batch_size = n_merge + .parse() + .unwrap_or_else(|_| crash!(2, "invalid --batch-size argument '{}'", n_merge)); + } + settings.zero_terminated = matches.is_present(OPT_ZERO_TERMINATED); settings.merge = matches.is_present(OPT_MERGE); @@ -1252,7 +1267,7 @@ fn output_sorted_lines<'a>(iter: impl Iterator>, settings: & fn exec(files: &[String], settings: &GlobalSettings) -> i32 { if settings.merge { - let mut file_merger = merge::merge(files.iter().map(open), settings); + let mut file_merger = merge::merge_with_file_limit(files.iter().map(open), settings); file_merger.write_all(settings); } else if settings.check { if files.len() > 1 { diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index e731d5b1d..75611abfc 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -840,3 +840,16 @@ fn test_compress_fail() { .fails() .stderr_only("sort: couldn't execute compress program: errno 2"); } + +#[test] +fn test_merge_batches() { + new_ucmd!() + .args(&[ + "ext_sort.txt", + "-n", + "-S", + "150B", + ]) + .succeeds() + .stdout_only_fixture("ext_sort.expected"); +} From 6ee6082cf88d656f1af30a9240d888594aa7e44f Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 6 Jun 2021 18:04:55 +0200 Subject: [PATCH 048/320] update Cargo.lock --- Cargo.lock | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c3aae831..43d491cef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,12 +43,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "array-init" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6945cc5422176fc5e602e590c2878d2c2acd9a4fe20a4baa7c28022521698ec6" - [[package]] name = "arrayref" version = "0.3.6" @@ -710,9 +704,9 @@ checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" [[package]] name = "heck" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" dependencies = [ "unicode-segmentation", ] @@ -1393,12 +1387,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" -dependencies = [ - "byteorder", -] +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" @@ -1511,9 +1502,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] From d6da143c4ebffb65221cb5b4a3b5157237aea52b Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 6 Jun 2021 19:53:28 +0200 Subject: [PATCH 049/320] sort: ignore errors when waiting for child --- src/uu/sort/src/ext_sort.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 91a7ca360..c439adcdc 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -262,7 +262,10 @@ fn write_lines<'a, T: Write>(lines: &[Line<'a>], writer: &mut T, separator: u8) } fn assert_child_success(mut child: Child, program: &str) { - if !matches!(child.wait().map(|e| e.code()), Ok(Some(0)) | Ok(None)) { + if !matches!( + child.wait().map(|e| e.code()), + Ok(Some(0)) | Ok(None) | Err(_) + ) { crash!(2, "'{}' terminated abnormally", program) } } From 884f5701251af39b29b6c305ce455fb5bc80ecca Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 6 Jun 2021 21:55:39 +0200 Subject: [PATCH 050/320] du/od/sort: refactor - replace map_or_else with unwrap_or_else --- src/uu/du/src/du.rs | 6 ++---- src/uu/od/src/od.rs | 20 ++++++++------------ src/uu/sort/src/sort.rs | 6 ++---- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index c2d764ebf..93f2fe5bb 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -229,10 +229,8 @@ fn get_file_info(path: &Path) -> Option { fn read_block_size(s: Option<&str>) -> usize { if let Some(s) = s { - parse_size(s).map_or_else( - |e| crash!(1, "{}", format_error_message(e, s, options::BLOCK_SIZE)), - |n| n, - ) + parse_size(s) + .unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::BLOCK_SIZE))) } else { for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { if let Ok(env_size) = env::var(env_var) { diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 1a592f622..d5124e93c 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -134,10 +134,9 @@ impl OdOptions { } let mut skip_bytes = matches.value_of(options::SKIP_BYTES).map_or(0, |s| { - parse_number_of_bytes(s).map_or_else( - |e| crash!(1, "{}", format_error_message(e, s, options::SKIP_BYTES)), - |n| n, - ) + parse_number_of_bytes(s).unwrap_or_else(|e| { + crash!(1, "{}", format_error_message(e, s, options::SKIP_BYTES)) + }) }); let mut label: Option = None; @@ -165,10 +164,8 @@ impl OdOptions { if matches.occurrences_of(options::WIDTH) == 0 { return 16; }; - parse_number_of_bytes(s).map_or_else( - |e| crash!(1, "{}", format_error_message(e, s, options::WIDTH)), - |n| n, - ) + parse_number_of_bytes(s) + .unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::WIDTH))) }); let min_bytes = formats.iter().fold(1, |max, next| { @@ -182,10 +179,9 @@ impl OdOptions { let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES); let read_bytes = matches.value_of(options::READ_BYTES).map(|s| { - parse_number_of_bytes(s).map_or_else( - |e| crash!(1, "{}", format_error_message(e, s, options::READ_BYTES)), - |n| n, - ) + parse_number_of_bytes(s).unwrap_or_else(|e| { + crash!(1, "{}", format_error_message(e, s, options::READ_BYTES)) + }) }); let radix = match matches.value_of(options::ADDRESS_RADIX) { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 9ec90519f..850f039b3 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1158,10 +1158,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.buffer_size = matches .value_of(OPT_BUF_SIZE) .map_or(DEFAULT_BUF_SIZE, |s| { - GlobalSettings::parse_byte_count(s).map_or_else( - |e| crash!(2, "{}", format_error_message(e, s, OPT_BUF_SIZE)), - |n| n, - ) + GlobalSettings::parse_byte_count(s) + .unwrap_or_else(|e| crash!(2, "{}", format_error_message(e, s, OPT_BUF_SIZE))) }); settings.tmp_dir = matches From 0033928128777e6423cc17ff47dad2e74864a6ce Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 6 Jun 2021 22:56:11 +0200 Subject: [PATCH 051/320] sort: fix broken tests for '--batch-size' (replace 'B' with 'b') https://github.com/uutils/coreutils/pull/2297#issuecomment-855460881 --- tests/by-util/test_sort.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 898bb3568..2894d3007 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -849,7 +849,7 @@ fn sort_multiple() { #[test] fn sort_empty_chunk() { new_ucmd!() - .args(&["-S", "40B"]) + .args(&["-S", "40b"]) .pipe_in("a\na\n") .succeeds() .stdout_is("a\na\n"); @@ -889,12 +889,7 @@ fn test_compress_fail() { #[test] fn test_merge_batches() { new_ucmd!() - .args(&[ - "ext_sort.txt", - "-n", - "-S", - "150B", - ]) + .args(&["ext_sort.txt", "-n", "-S", "150b"]) .succeeds() .stdout_only_fixture("ext_sort.expected"); } From 4d5880f098843558c8917637df4b238fe63c4597 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Mon, 31 May 2021 15:33:49 -0500 Subject: [PATCH 052/320] maint/CICD ~ temporarily disable failing tool cache for actionrs/install # [why] The tool cache is currently failing and seems to be getting further behind current versions. The [actions-rs/install#12] issue addresses this but seems to be languishing without any proposed solution. [ref]: --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 32c3537c2..c7d296e60 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -606,7 +606,7 @@ jobs: with: crate: grcov version: latest - use-tool-cache: true + use-tool-cache: false - name: Generate coverage data (via `grcov`) id: coverage shell: bash From 9cdfa06f3a8a40128d2d1e50ec1c6343e6bb9976 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Fri, 4 Jun 2021 10:56:53 -0500 Subject: [PATCH 053/320] maint/CICD ~ (MinRustV) update 'Cargo.lock' for rust v1.43 --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 43d491cef..7b2d989be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2420,6 +2420,7 @@ dependencies = [ "uucore", "uucore_procs", "walkdir", + "winapi 0.3.9", ] [[package]] From 5553416b878535edb88c6ffd997006b8b6bb9409 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Fri, 4 Jun 2021 14:28:53 -0500 Subject: [PATCH 054/320] tests ~ fix clippy complaint (clippy::bool_assert_comparision) --- tests/by-util/test_env.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 4db3b59bd..1d76c433d 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -187,11 +187,10 @@ fn test_change_directory() { .arg(&temporary_path) .succeeds() .stdout_move_str(); - assert_eq!( - out.lines() - .any(|line| line.ends_with(temporary_path.file_name().unwrap().to_str().unwrap())), - false - ); + + assert!(!out + .lines() + .any(|line| line.ends_with(temporary_path.file_name().unwrap().to_str().unwrap()))); } #[test] From 3cfb95668429607dde7ac36f0e4aa1c0d780535e Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Mon, 31 May 2021 16:18:33 -0500 Subject: [PATCH 055/320] refactor/du ~ fix `cargo clippy` warning (clippy::ptr_arg) --- src/uu/du/src/du.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index c5fff2ed7..d0dc845cb 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -24,6 +24,8 @@ use std::os::unix::fs::MetadataExt; use std::os::windows::fs::MetadataExt; #[cfg(windows)] use std::os::windows::io::AsRawHandle; +#[cfg(windows)] +use std::path::Path; use std::path::PathBuf; use std::time::{Duration, UNIX_EPOCH}; use uucore::InvalidEncodingHandling; @@ -159,7 +161,7 @@ fn birth_u64(meta: &Metadata) -> Option { } #[cfg(windows)] -fn get_size_on_disk(path: &PathBuf) -> u64 { +fn get_size_on_disk(path: &Path) -> u64 { let mut size_on_disk = 0; // bind file so it stays in scope until end of function @@ -191,7 +193,7 @@ fn get_size_on_disk(path: &PathBuf) -> u64 { } #[cfg(windows)] -fn get_file_info(path: &PathBuf) -> Option { +fn get_file_info(path: &Path) -> Option { let mut result = None; let file = match fs::File::open(path) { From 1ef820212c8db78b3e11cefd6e1bac3e24103bae Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Mon, 31 May 2021 16:17:54 -0500 Subject: [PATCH 056/320] refactor/rm ~ fix `cargo clippy` warning (clippy::upper_case_acronyms) --- src/uu/rm/Cargo.toml | 2 ++ src/uu/rm/src/rm.rs | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index 9974111aa..d84756fd3 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -18,10 +18,12 @@ path = "src/rm.rs" clap = "2.33" walkdir = "2.2" remove_dir_all = "0.5.1" +winapi = { version="0.3", features=[] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } + [[bin]] name = "rm" path = "src/main.rs" diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index ba764034a..ea56ca170 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -430,9 +430,7 @@ use std::os::windows::prelude::MetadataExt; #[cfg(windows)] fn is_symlink_dir(metadata: &fs::Metadata) -> bool { - use std::os::raw::c_ulong; - pub type DWORD = c_ulong; - pub const FILE_ATTRIBUTE_DIRECTORY: DWORD = 0x10; + use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; metadata.file_type().is_symlink() && ((metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY) != 0) From 114844d9cda1f178b5a9852d3df58132fa15c582 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Mon, 31 May 2021 16:01:54 -0500 Subject: [PATCH 057/320] maint/CICD ~ refactor; use a shell script (`outputs`) for step outputs --- .github/workflows/CICD.yml | 94 +++++++++++++------------------------- 1 file changed, 32 insertions(+), 62 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index c7d296e60..25e2386f0 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -32,12 +32,12 @@ jobs: shell: bash run: | ## VARs setup + outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='' ; if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi - echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} - echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} + outputs CARGO_FEATURES_OPTION - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: @@ -93,12 +93,12 @@ jobs: shell: bash run: | ## VARs setup + outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='' ; if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi - echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} - echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} + outputs CARGO_FEATURES_OPTION - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: @@ -261,22 +261,20 @@ jobs: shell: bash run: | ## VARs setup + outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # toolchain TOOLCHAIN="stable" ## default to "stable" toolchain # * specify alternate/non-default TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: GH:rust-lang/rust#47048, GH:rust-lang/rust#53454, GH:rust-lang/cargo#6754) case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac; # * use requested TOOLCHAIN if specified if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi - echo set-output name=TOOLCHAIN::${TOOLCHAIN:-/false} - echo ::set-output name=TOOLCHAIN::${TOOLCHAIN} + outputs TOOLCHAIN # staging directory STAGING='_staging' - echo set-output name=STAGING::${STAGING} - echo ::set-output name=STAGING::${STAGING} + outputs STAGING # determine EXE suffix EXE_suffix="" ; case '${{ matrix.job.target }}' in *-pc-windows-*) EXE_suffix=".exe" ;; esac; - echo set-output name=EXE_suffix::${EXE_suffix} - echo ::set-output name=EXE_suffix::${EXE_suffix} + outputs EXE_suffix # parse commit reference info echo GITHUB_REF=${GITHUB_REF} echo GITHUB_SHA=${GITHUB_SHA} @@ -284,14 +282,7 @@ jobs: unset REF_BRANCH ; case "${GITHUB_REF}" in refs/heads/*) REF_BRANCH=${GITHUB_REF#refs/heads/} ;; esac; unset REF_TAG ; case "${GITHUB_REF}" in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac; REF_SHAS=${GITHUB_SHA:0:8} - echo set-output name=REF_NAME::${REF_NAME} - echo set-output name=REF_BRANCH::${REF_BRANCH} - echo set-output name=REF_TAG::${REF_TAG} - echo set-output name=REF_SHAS::${REF_SHAS} - echo ::set-output name=REF_NAME::${REF_NAME} - echo ::set-output name=REF_BRANCH::${REF_BRANCH} - echo ::set-output name=REF_TAG::${REF_TAG} - echo ::set-output name=REF_SHAS::${REF_SHAS} + outputs REF_NAME REF_BRANCH REF_TAG REF_SHAS # parse target unset TARGET_ARCH case '${{ matrix.job.target }}' in @@ -301,68 +292,50 @@ jobs: i686-*) TARGET_ARCH=i686 ;; x86_64-*) TARGET_ARCH=x86_64 ;; esac; - echo set-output name=TARGET_ARCH::${TARGET_ARCH} - echo ::set-output name=TARGET_ARCH::${TARGET_ARCH} unset TARGET_OS ; case '${{ matrix.job.target }}' in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac; - echo set-output name=TARGET_OS::${TARGET_OS} - echo ::set-output name=TARGET_OS::${TARGET_OS} + outputs TARGET_ARCH TARGET_OS # package name PKG_suffix=".tar.gz" ; case '${{ matrix.job.target }}' in *-pc-windows-*) PKG_suffix=".zip" ;; esac; PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }} PKG_NAME=${PKG_BASENAME}${PKG_suffix} - echo set-output name=PKG_suffix::${PKG_suffix} - echo set-output name=PKG_BASENAME::${PKG_BASENAME} - echo set-output name=PKG_NAME::${PKG_NAME} - echo ::set-output name=PKG_suffix::${PKG_suffix} - echo ::set-output name=PKG_BASENAME::${PKG_BASENAME} - echo ::set-output name=PKG_NAME::${PKG_NAME} + outputs PKG_suffix PKG_BASENAME PKG_NAME # deployable tag? (ie, leading "vM" or "M"; M == version number) unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi - echo set-output name=DEPLOY::${DEPLOY:-/false} - echo ::set-output name=DEPLOY::${DEPLOY} + outputs DEPLOY # DPKG architecture? unset DPKG_ARCH case ${{ matrix.job.target }} in x86_64-*-linux-*) DPKG_ARCH=amd64 ;; *-linux-*) DPKG_ARCH=${TARGET_ARCH} ;; esac - echo set-output name=DPKG_ARCH::${DPKG_ARCH} - echo ::set-output name=DPKG_ARCH::${DPKG_ARCH} + outputs DPKG_ARCH # DPKG version? unset DPKG_VERSION ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DPKG_VERSION=${REF_TAG/#[vV]/} ; fi - echo set-output name=DPKG_VERSION::${DPKG_VERSION} - echo ::set-output name=DPKG_VERSION::${DPKG_VERSION} + outputs DPKG_VERSION # DPKG base name/conflicts? DPKG_BASENAME=${PROJECT_NAME} DPKG_CONFLICTS=${PROJECT_NAME}-musl case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac; - echo set-output name=DPKG_BASENAME::${DPKG_BASENAME} - echo set-output name=DPKG_CONFLICTS::${DPKG_CONFLICTS} - echo ::set-output name=DPKG_BASENAME::${DPKG_BASENAME} - echo ::set-output name=DPKG_CONFLICTS::${DPKG_CONFLICTS} + outputs DPKG_BASENAME DPKG_CONFLICTS # DPKG name unset DPKG_NAME; if [[ -n $DPKG_ARCH && -n $DPKG_VERSION ]]; then DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" ; fi - echo set-output name=DPKG_NAME::${DPKG_NAME} - echo ::set-output name=DPKG_NAME::${DPKG_NAME} + outputs DPKG_NAME # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='' ; if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi - echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} - echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} + outputs CARGO_FEATURES_OPTION # * CARGO_USE_CROSS (truthy) CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac; - echo set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS:-/false} - echo ::set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS} + outputs CARGO_USE_CROSS # ** pass needed environment into `cross` container (iff `cross` not already configured via "Cross.toml") if [ -n "${CARGO_USE_CROSS}" ] && [ ! -e "Cross.toml" ] ; then printf "[build.env]\npassthrough = [\"CI\"]\n" > Cross.toml fi # * test only library and/or binaries for arm-type targets unset CARGO_TEST_OPTIONS ; case '${{ matrix.job.target }}' in aarch64-* | arm-*) CARGO_TEST_OPTIONS="--bins" ;; esac; - echo set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} - echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} + outputs CARGO_TEST_OPTIONS # * executable for `strip`? STRIP="strip" case ${{ matrix.job.target }} in @@ -370,8 +343,7 @@ jobs: arm-*-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;; *-pc-windows-msvc) STRIP="" ;; esac; - echo set-output name=STRIP::${STRIP:-/false} - echo ::set-output name=STRIP::${STRIP} + outputs STRIP - name: Create all needed build/work directories shell: bash run: | @@ -395,11 +367,12 @@ jobs: shell: bash run: | ## Dependent VARs setup + outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # * determine sub-crate utility list UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" + echo UTILITY_LIST=${UTILITY_LIST} CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" - echo set-output name=UTILITY_LIST::${UTILITY_LIST} - echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS} + outputs CARGO_UTILITY_LIST_OPTIONS - name: Install `cargo-tree` # for dependency information uses: actions-rs/install@v0.1 with: @@ -524,34 +497,31 @@ jobs: id: vars shell: bash run: | + ## VARs setup + outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # toolchain TOOLCHAIN="nightly-${{ env.RUST_COV_SRV }}" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support # * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac; # * use requested TOOLCHAIN if specified if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi - echo set-output name=TOOLCHAIN::${TOOLCHAIN} - echo ::set-output name=TOOLCHAIN::${TOOLCHAIN} + outputs TOOLCHAIN # staging directory STAGING='_staging' - echo set-output name=STAGING::${STAGING} - echo ::set-output name=STAGING::${STAGING} + outputs STAGING ## # check for CODECOV_TOKEN availability (work-around for inaccessible 'secrets' object for 'if'; see ) ## # note: CODECOV_TOKEN / HAS_CODECOV_TOKEN is not needed for public repositories when using AppVeyor, Azure Pipelines, CircleCI, GitHub Actions, Travis (see ) ## unset HAS_CODECOV_TOKEN ## if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi - ## echo set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN} - ## echo ::set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN} + ## outputs HAS_CODECOV_TOKEN # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='--all-features' ; ## default to '--all-features' for code coverage if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi - echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} - echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} + outputs CARGO_FEATURES_OPTION # * CODECOV_FLAGS CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' ) - echo set-output name=CODECOV_FLAGS::${CODECOV_FLAGS} - echo ::set-output name=CODECOV_FLAGS::${CODECOV_FLAGS} + outputs CODECOV_FLAGS - name: rust toolchain ~ install uses: actions-rs/toolchain@v1 with: @@ -563,11 +533,11 @@ jobs: shell: bash run: | ## Dependent VARs setup + outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # * determine sub-crate utility list UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" - echo set-output name=UTILITY_LIST::${UTILITY_LIST} - echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS} + outputs CARGO_UTILITY_LIST_OPTIONS - name: Test uucore uses: actions-rs/cargo@v1 with: From b1cc604b620fc609a2b7de3b8c818f68a2068a3a Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 16:05:58 -0500 Subject: [PATCH 058/320] docs/spell ~ update cspell dictionaries --- .vscode/cspell.dictionaries/people.wordlist.txt | 3 +++ .vscode/cspell.dictionaries/workspace.wordlist.txt | 2 ++ 2 files changed, 5 insertions(+) diff --git a/.vscode/cspell.dictionaries/people.wordlist.txt b/.vscode/cspell.dictionaries/people.wordlist.txt index 01cfa4a3e..0a091633f 100644 --- a/.vscode/cspell.dictionaries/people.wordlist.txt +++ b/.vscode/cspell.dictionaries/people.wordlist.txt @@ -97,6 +97,9 @@ Michael Debertol Michael Gehring Michael Gehring +Mitchell Mebane + Mitchell + Mebane Morten Olsen Lysgaard Morten Olsen diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index e2a864f9c..ed634dffb 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -7,6 +7,7 @@ advapi advapi32-sys aho-corasick backtrace +blake2b_simd bstr byteorder chacha @@ -47,6 +48,7 @@ xattr # * rust/rustc RUSTDOCFLAGS RUSTFLAGS +bitor # BitOr trait function bitxor # BitXor trait function clippy concat From c192550f22976408fb4fe113e3bbdcfee0e83615 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 16:07:56 -0500 Subject: [PATCH 059/320] refactor ~ polish spelling + add spelling exceptions --- .github/workflows/CICD.yml | 2 +- src/uu/du/src/du.rs | 2 +- src/uu/rm/src/rm.rs | 2 +- util/build-gnu.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 25e2386f0..ad3177224 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -5,7 +5,7 @@ name: CICD # spell-checker:ignore (jargon) SHAs deps softprops toolchain # spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy # spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rustc rustfmt rustup shopt xargs -# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend tempfile testsuite uutils +# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend runtest tempfile testsuite uutils env: PROJECT_NAME: coreutils diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index d0dc845cb..9d8d5536f 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -433,7 +433,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { although the apparent size is usually smaller, it may be larger due to holes \ in ('sparse') files, internal fragmentation, indirect blocks, and the like" ) - .alias("app") // The GNU testsuite uses this alias + .alias("app") // The GNU test suite uses this alias ) .arg( Arg::with_name(options::BLOCK_SIZE) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index ea56ca170..40a24cea7 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -5,7 +5,7 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore (ToDO) bitor ulong +// spell-checker:ignore (path) eacces #[macro_use] extern crate uucore; diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 1ffde8311..44ecd2044 100644 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -1,6 +1,6 @@ #!/bin/bash -# spell-checker:ignore (paths) abmon deref discrim getlimits getopt ginstall gnulib inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW ; (vars/env) BUILDDIR SRCDIR +# spell-checker:ignore (paths) abmon deref discrim eacces getlimits getopt ginstall gnulib inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW ; (vars/env) BUILDDIR SRCDIR set -e if test ! -d ../gnu; then From f5e2daa0565fab8e9923c9b0bd491eecb7fb0f10 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 16:09:52 -0500 Subject: [PATCH 060/320] refactor/uucore ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uucore/src/lib/features/utmpx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index a794b01da..5077d9e59 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -207,7 +207,7 @@ impl Utmpx { flags: AI_CANONNAME, ..AddrInfoHints::default() }; - let sockets = getaddrinfo(Some(&hostname), None, Some(hints)) + let sockets = getaddrinfo(Some(hostname), None, Some(hints)) .unwrap() .collect::>>()?; for socket in sockets { From 777e3906f87f0bd2927e968c09b07bbcc8c7a9bc Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 22:01:00 -0500 Subject: [PATCH 061/320] refactor/basename ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/basename/src/basename.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index a0eed93f1..47ad3117f 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -110,7 +110,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let line_ending = if opt_zero { "\0" } else { "\n" }; for path in paths { - print!("{}{}", basename(&path, &suffix), line_ending); + print!("{}{}", basename(path, suffix), line_ending); } 0 From c115ad4274f3237f7014df3cd82fc6fe5f08d6bd Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 21:10:13 -0500 Subject: [PATCH 062/320] refactor/cat ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/cat/src/cat.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 1f2f441d8..005802ce5 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -295,7 +295,7 @@ fn cat_handle( if options.can_write_fast() { write_fast(handle) } else { - write_lines(handle, &options, state) + write_lines(handle, options, state) } } @@ -308,7 +308,7 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat reader: stdin, is_interactive: is_stdin_interactive(), }; - return cat_handle(&mut handle, &options, state); + return cat_handle(&mut handle, options, state); } match get_input_type(path)? { InputType::Directory => Err(CatError::IsDirectory), @@ -322,7 +322,7 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat reader: socket, is_interactive: false, }; - cat_handle(&mut handle, &options, state) + cat_handle(&mut handle, options, state) } _ => { let file = File::open(path)?; @@ -332,7 +332,7 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat reader: file, is_interactive: false, }; - cat_handle(&mut handle, &options, state) + cat_handle(&mut handle, options, state) } } } @@ -345,7 +345,7 @@ fn cat_files(files: Vec, options: &OutputOptions) -> Result<(), u32> { }; for path in &files { - if let Err(err) = cat_path(path, &options, &mut state) { + if let Err(err) = cat_path(path, options, &mut state) { show_error!("{}: {}", path, err); error_count += 1; } From 35b360b8e40d30986bcf86951bc18ea478503de1 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 22:01:12 -0500 Subject: [PATCH 063/320] refactor/chmod ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/chmod/src/chmod.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 9cdabc7d6..7d171a6f7 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -127,13 +127,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let verbose = matches.is_present(options::VERBOSE); let preserve_root = matches.is_present(options::PRESERVE_ROOT); let recursive = matches.is_present(options::RECURSIVE); - let fmode = - matches - .value_of(options::REFERENCE) - .and_then(|ref fref| match fs::metadata(fref) { - Ok(meta) => Some(meta.mode()), - Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err), - }); + let fmode = matches + .value_of(options::REFERENCE) + .and_then(|fref| match fs::metadata(fref) { + Ok(meta) => Some(meta.mode()), + Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err), + }); let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required let mut cmode = if mode_had_minus_prefix { // clap parsing is finished, now put prefix back @@ -230,11 +229,11 @@ impl Chmoder { return Err(1); } if !self.recursive { - r = self.chmod_file(&file).and(r); + r = self.chmod_file(file).and(r); } else { for entry in WalkDir::new(&filename).into_iter().filter_map(|e| e.ok()) { let file = entry.path(); - r = self.chmod_file(&file).and(r); + r = self.chmod_file(file).and(r); } } } From baa656ca8ad7a5d2e1144aa91b6cbb56ebbb6a5d Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 23:28:04 -0500 Subject: [PATCH 064/320] refactor/chown ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/chown/src/chown.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 2bb5133fe..649300d83 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -220,7 +220,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; let filter = if let Some(spec) = matches.value_of(options::FROM) { - match parse_spec(&spec) { + match parse_spec(spec) { Ok((Some(uid), None)) => IfFrom::User(uid), Ok((None, Some(gid))) => IfFrom::Group(gid), Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid), @@ -248,7 +248,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } } else { - match parse_spec(&owner) { + match parse_spec(owner) { Ok((u, g)) => { dest_uid = u; dest_gid = g; From 1e418e8d844e6dc4d7f3de28ca55e292d8c03acf Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 13:53:54 -0500 Subject: [PATCH 065/320] refactor/chroot ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/chroot/src/chroot.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index a05bd4494..8e23d8227 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -106,13 +106,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mut vector: Vec<&str> = Vec::new(); for (&k, v) in matches.args.iter() { vector.push(k); - vector.push(&v.vals[0].to_str().unwrap()); + vector.push(v.vals[0].to_str().unwrap()); } vector } }; - set_context(&newroot, &matches); + set_context(newroot, &matches); let pstatus = Command::new(command[0]) .args(&command[1..]) @@ -132,7 +132,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) { let group_str = options.value_of(options::GROUP).unwrap_or_default(); let groups_str = options.value_of(options::GROUPS).unwrap_or_default(); let userspec = match userspec_str { - Some(ref u) => { + Some(u) => { let s: Vec<&str> = u.split(':').collect(); if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) { crash!(1, "invalid userspec: `{}`", u) From 14bb9ec61619dd90b10bbd843105724f083aa89f Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 10:26:00 -0500 Subject: [PATCH 066/320] refactor/csplit ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/csplit/src/csplit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index e3b2069ab..d69254a3a 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -92,7 +92,7 @@ where T: BufRead, { let mut input_iter = InputSplitter::new(input.lines().enumerate()); - let mut split_writer = SplitWriter::new(&options); + let mut split_writer = SplitWriter::new(options); let ret = do_csplit(&mut split_writer, patterns, &mut input_iter); // consume the rest From 3409dc93e3cfb1da0c1d83e6d6b6242ea0ef3011 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 22:10:55 -0500 Subject: [PATCH 067/320] refactor/du ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/du/src/du.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 9d8d5536f..82424ca32 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -258,7 +258,7 @@ fn unit_string_to_number(s: &str) -> Option { fn translate_to_pure_number(s: &Option<&str>) -> Option { match *s { - Some(ref s) => unit_string_to_number(s), + Some(s) => unit_string_to_number(s), None => None, } } @@ -585,12 +585,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let max_depth_str = matches.value_of(options::MAX_DEPTH); let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::().ok()); match (max_depth_str, max_depth) { - (Some(ref s), _) if summarize => { - show_error!("summarizing conflicts with --max-depth={}", *s); + (Some(s), _) if summarize => { + show_error!("summarizing conflicts with --max-depth={}", s); return 1; } - (Some(ref s), None) => { - show_error!("invalid maximum depth '{}'", *s); + (Some(s), None) => { + show_error!("invalid maximum depth '{}'", s); return 1; } (Some(_), Some(_)) | (None, _) => { /* valid */ } From b08c568748038b5649c82329b3736c95831867cb Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 13:21:25 -0500 Subject: [PATCH 068/320] refactor/echo ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/echo/src/echo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 56cd967f4..d83a4fe06 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -181,7 +181,7 @@ fn execute(no_newline: bool, escaped: bool, free: Vec) -> io::Result<()> write!(output, " ")?; } if escaped { - let should_stop = print_escaped(&input, &mut output)?; + let should_stop = print_escaped(input, &mut output)?; if should_stop { break; } From 9964a21fe3473274de5b11b325ae9757b5e90c6d Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 13:21:35 -0500 Subject: [PATCH 069/320] refactor/env ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/env/src/env.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 50a327260..e20f047b7 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -245,7 +245,7 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> { } // set specified env vars - for &(ref name, ref val) in &opts.sets { + for &(name, val) in &opts.sets { // FIXME: set_var() panics if name is an empty string env::set_var(name, val); } From 122d82e835882cce4a86a88fc107f674ff121512 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 11:28:30 -0500 Subject: [PATCH 070/320] refactor/expand ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/expand/src/expand.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 08a514dbf..17b05c726 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -236,7 +236,7 @@ fn expand(options: Options) { // now dump out either spaces if we're expanding, or a literal tab if we're not if init || !options.iflag { - safe_unwrap!(output.write_all(&options.tspaces[..nts].as_bytes())); + safe_unwrap!(output.write_all(options.tspaces[..nts].as_bytes())); } else { safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); } From 489f9e83864156817627c240487450e69ebd24d3 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 11:29:35 -0500 Subject: [PATCH 071/320] refactor/expand ~ fix `cargo clippy` complaint (clippy::manual_str_repeat) --- src/uu/expand/src/expand.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 17b05c726..d9d669e7c 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -15,7 +15,6 @@ extern crate uucore; use clap::{crate_version, App, Arg, ArgMatches}; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; -use std::iter::repeat; use std::str::from_utf8; use unicode_width::UnicodeWidthChar; @@ -90,7 +89,7 @@ impl Options { }) .max() .unwrap(); // length of tabstops is guaranteed >= 1 - let tspaces = repeat(' ').take(nspaces).collect(); + let tspaces = " ".repeat(nspaces); let files: Vec = match matches.values_of(options::FILES) { Some(s) => s.map(|v| v.to_string()).collect(), From 28e176cbbaf259e6d105352850bd3ebab83b0b84 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 22:01:24 -0500 Subject: [PATCH 072/320] refactor/expr ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/expr/src/expr.rs | 2 +- src/uu/expr/src/tokens.rs | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 5d63bed80..329a79ba2 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -37,7 +37,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } fn process_expr(token_strings: &[String]) -> Result { - let maybe_tokens = tokens::strings_to_tokens(&token_strings); + let maybe_tokens = tokens::strings_to_tokens(token_strings); let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens); evaluate_ast(maybe_ast) } diff --git a/src/uu/expr/src/tokens.rs b/src/uu/expr/src/tokens.rs index 6f2795588..748960bc3 100644 --- a/src/uu/expr/src/tokens.rs +++ b/src/uu/expr/src/tokens.rs @@ -78,27 +78,27 @@ pub fn strings_to_tokens(strings: &[String]) -> Result, Stri "(" => Token::ParOpen, ")" => Token::ParClose, - "^" => Token::new_infix_op(&s, false, 7), + "^" => Token::new_infix_op(s, false, 7), - ":" => Token::new_infix_op(&s, true, 6), + ":" => Token::new_infix_op(s, true, 6), - "*" => Token::new_infix_op(&s, true, 5), - "/" => Token::new_infix_op(&s, true, 5), - "%" => Token::new_infix_op(&s, true, 5), + "*" => Token::new_infix_op(s, true, 5), + "/" => Token::new_infix_op(s, true, 5), + "%" => Token::new_infix_op(s, true, 5), - "+" => Token::new_infix_op(&s, true, 4), - "-" => Token::new_infix_op(&s, true, 4), + "+" => Token::new_infix_op(s, true, 4), + "-" => Token::new_infix_op(s, true, 4), - "=" => Token::new_infix_op(&s, true, 3), - "!=" => Token::new_infix_op(&s, true, 3), - "<" => Token::new_infix_op(&s, true, 3), - ">" => Token::new_infix_op(&s, true, 3), - "<=" => Token::new_infix_op(&s, true, 3), - ">=" => Token::new_infix_op(&s, true, 3), + "=" => Token::new_infix_op(s, true, 3), + "!=" => Token::new_infix_op(s, true, 3), + "<" => Token::new_infix_op(s, true, 3), + ">" => Token::new_infix_op(s, true, 3), + "<=" => Token::new_infix_op(s, true, 3), + ">=" => Token::new_infix_op(s, true, 3), - "&" => Token::new_infix_op(&s, true, 2), + "&" => Token::new_infix_op(s, true, 2), - "|" => Token::new_infix_op(&s, true, 1), + "|" => Token::new_infix_op(s, true, 1), "match" => Token::PrefixOp { arity: 2, @@ -117,9 +117,9 @@ pub fn strings_to_tokens(strings: &[String]) -> Result, Stri value: s.clone(), }, - _ => Token::new_value(&s), + _ => Token::new_value(s), }; - push_token_if_not_escaped(&mut tokens_acc, tok_idx, token_if_not_escaped, &s); + push_token_if_not_escaped(&mut tokens_acc, tok_idx, token_if_not_escaped, s); tok_idx += 1; } maybe_dump_tokens_acc(&tokens_acc); From 2bf06c3104c942a56e2c90bc87b76160f082bfe8 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 10:26:12 -0500 Subject: [PATCH 073/320] refactor/fold ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/fold/src/fold.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index e476fed5b..c49809549 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -109,7 +109,7 @@ fn handle_obsolete(args: &[String]) -> (Vec, Option) { fn fold(filenames: Vec, bytes: bool, spaces: bool, width: usize) { for filename in &filenames { - let filename: &str = &filename; + let filename: &str = filename; let mut stdin_buf; let mut file_buf; let buffer = BufReader::new(if filename == "-" { From 9f2cb2e5e9a64311ba488b2d90b1effef1ae9aba Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 22:13:08 -0500 Subject: [PATCH 074/320] refactor/hashsum ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/hashsum/src/digest.rs | 2 +- src/uu/hashsum/src/hashsum.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/uu/hashsum/src/digest.rs b/src/uu/hashsum/src/digest.rs index 25bc7f4c3..9093d94a7 100644 --- a/src/uu/hashsum/src/digest.rs +++ b/src/uu/hashsum/src/digest.rs @@ -59,7 +59,7 @@ impl Digest for blake2b_simd::State { fn result(&mut self, out: &mut [u8]) { let hash_result = &self.finalize(); - out.copy_from_slice(&hash_result.as_bytes()); + out.copy_from_slice(hash_result.as_bytes()); } fn reset(&mut self) { diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 9822ca3fa..1f097e128 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -90,7 +90,7 @@ fn detect_algo<'a>( 512, ), "sha3sum" => match matches.value_of("bits") { - Some(bits_str) => match (&bits_str).parse::() { + Some(bits_str) => match (bits_str).parse::() { Ok(224) => ( "SHA3-224", Box::new(Sha3_224::new()) as Box, @@ -140,7 +140,7 @@ fn detect_algo<'a>( 512, ), "shake128sum" => match matches.value_of("bits") { - Some(bits_str) => match (&bits_str).parse::() { + Some(bits_str) => match (bits_str).parse::() { Ok(bits) => ( "SHAKE128", Box::new(Shake128::new()) as Box, @@ -151,7 +151,7 @@ fn detect_algo<'a>( None => crash!(1, "--bits required for SHAKE-128"), }, "shake256sum" => match matches.value_of("bits") { - Some(bits_str) => match (&bits_str).parse::() { + Some(bits_str) => match (bits_str).parse::() { Ok(bits) => ( "SHAKE256", Box::new(Shake256::new()) as Box, @@ -194,7 +194,7 @@ fn detect_algo<'a>( } if matches.is_present("sha3") { match matches.value_of("bits") { - Some(bits_str) => match (&bits_str).parse::() { + Some(bits_str) => match (bits_str).parse::() { Ok(224) => set_or_crash( "SHA3-224", Box::new(Sha3_224::new()) as Box, @@ -238,7 +238,7 @@ fn detect_algo<'a>( } if matches.is_present("shake128") { match matches.value_of("bits") { - Some(bits_str) => match (&bits_str).parse::() { + Some(bits_str) => match (bits_str).parse::() { Ok(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), bits), Err(err) => crash!(1, "{}", err), }, @@ -247,7 +247,7 @@ fn detect_algo<'a>( } if matches.is_present("shake256") { match matches.value_of("bits") { - Some(bits_str) => match (&bits_str).parse::() { + Some(bits_str) => match (bits_str).parse::() { Ok(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), bits), Err(err) => crash!(1, "{}", err), }, From c66d67a0b9dca79a27bd54c48496774eebc1c760 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 13:21:46 -0500 Subject: [PATCH 075/320] refactor/install ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/install/src/install.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 7aa6f95ff..bcfe1a396 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -379,7 +379,7 @@ fn directory(paths: Vec, b: Behavior) -> i32 { } } - if mode::chmod(&path, b.mode()).is_err() { + if mode::chmod(path, b.mode()).is_err() { all_successful = false; continue; } @@ -422,7 +422,7 @@ fn standard(paths: Vec, b: Behavior) -> i32 { return 1; } - if mode::chmod(&parent, b.mode()).is_err() { + if mode::chmod(parent, b.mode()).is_err() { show_error!("failed to chmod {}", parent.display()); return 1; } @@ -501,7 +501,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3 /// _target_ must be a non-directory /// fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 { - if copy(file, &target, b).is_err() { + if copy(file, target, b).is_err() { 1 } else { 0 @@ -563,7 +563,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> { } } - if mode::chmod(&to, b.mode()).is_err() { + if mode::chmod(to, b.mode()).is_err() { return Err(()); } From 4a09c72fe7d918d43792be9162a8e6559e8edc1b Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 16:12:00 -0500 Subject: [PATCH 076/320] refactor/join ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/join/src/join.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 7a044789f..4cdfe2141 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -328,8 +328,8 @@ impl<'a> State<'a> { }); } else { repr.print_field(key); - repr.print_fields(&line1, self.key, self.max_fields); - repr.print_fields(&line2, other.key, other.max_fields); + repr.print_fields(line1, self.key, self.max_fields); + repr.print_fields(line2, other.key, other.max_fields); } println!(); @@ -611,7 +611,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 { let mut state1 = State::new( FileNum::File1, - &file1, + file1, &stdin, settings.key1, settings.print_unpaired, @@ -619,7 +619,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 { let mut state2 = State::new( FileNum::File2, - &file2, + file2, &stdin, settings.key2, settings.print_unpaired, From e8e28f15084f541e9cedfc46cc7b002a95516f60 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 11:46:52 -0500 Subject: [PATCH 077/320] refactor/ln ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/ln/src/ln.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index cd5eef842..fff38c939 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -260,17 +260,17 @@ fn exec(files: &[PathBuf], settings: &Settings) -> i32 { // Handle cases where we create links in a directory first. if let Some(ref name) = settings.target_dir { // 4th form: a directory is specified by -t. - return link_files_in_dir(files, &PathBuf::from(name), &settings); + return link_files_in_dir(files, &PathBuf::from(name), settings); } if !settings.no_target_dir { if files.len() == 1 { // 2nd form: the target directory is the current directory. - return link_files_in_dir(files, &PathBuf::from("."), &settings); + return link_files_in_dir(files, &PathBuf::from("."), settings); } let last_file = &PathBuf::from(files.last().unwrap()); if files.len() > 2 || last_file.is_dir() { // 3rd form: create links in the last argument. - return link_files_in_dir(&files[0..files.len() - 1], last_file, &settings); + return link_files_in_dir(&files[0..files.len() - 1], last_file, settings); } } @@ -392,7 +392,7 @@ fn relative_path<'a>(src: &Path, dst: &Path) -> Result> { fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> { let mut backup_path = None; let source: Cow<'_, Path> = if settings.relative { - relative_path(&src, dst)? + relative_path(src, dst)? } else { src.into() }; From 99fae850ae48b8373b05e2503fbdfc2badd056ad Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 21:10:23 -0500 Subject: [PATCH 078/320] refactor/ls ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/ls/src/ls.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 3c7b22360..dc67d5738 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1243,7 +1243,7 @@ fn sort_entries(entries: &mut Vec, config: &Config) { Sort::Time => entries.sort_by_key(|k| { Reverse( k.md() - .and_then(|md| get_system_time(&md, config)) + .and_then(|md| get_system_time(md, config)) .unwrap_or(UNIX_EPOCH), ) }), @@ -1323,7 +1323,7 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) .filter(|p| p.file_type().map(|ft| ft.is_dir()).unwrap_or(false)) { let _ = writeln!(out, "\n{}:", e.p_buf.display()); - enter_directory(&e, config, out); + enter_directory(e, config, out); } } } @@ -1339,8 +1339,8 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result { fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) { if let Some(md) = entry.md() { ( - display_symlink_count(&md).len(), - display_size_or_rdev(&md, config).len(), + display_symlink_count(md).len(), + display_size_or_rdev(md, config).len(), ) } else { (0, 0) @@ -1371,7 +1371,7 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter { @@ -1482,40 +1482,40 @@ fn display_item_long( #[cfg(unix)] { if config.inode { - let _ = write!(out, "{} ", get_inode(&md)); + let _ = write!(out, "{} ", get_inode(md)); } } let _ = write!( out, "{} {}", - display_permissions(&md, true), - pad_left(display_symlink_count(&md), max_links), + display_permissions(md, true), + pad_left(display_symlink_count(md), max_links), ); if config.long.owner { - let _ = write!(out, " {}", display_uname(&md, config)); + let _ = write!(out, " {}", display_uname(md, config)); } if config.long.group { - let _ = write!(out, " {}", display_group(&md, config)); + let _ = write!(out, " {}", display_group(md, config)); } // Author is only different from owner on GNU/Hurd, so we reuse // the owner, since GNU/Hurd is not currently supported by Rust. if config.long.author { - let _ = write!(out, " {}", display_uname(&md, config)); + let _ = write!(out, " {}", display_uname(md, config)); } let _ = writeln!( out, " {} {} {}", pad_left(display_size_or_rdev(md, config), max_size), - display_date(&md, config), + display_date(md, config), // unwrap is fine because it fails when metadata is not available // but we already know that it is because it's checked at the // start of the function. - display_file_name(&item, config).unwrap().contents, + display_file_name(item, config).unwrap().contents, ); } @@ -1741,7 +1741,7 @@ fn display_file_name(path: &PathData, config: &Config) -> Option { let mut width = name.width(); if let Some(ls_colors) = &config.color { - name = color_name(&ls_colors, &path.p_buf, name, path.md()?); + name = color_name(ls_colors, &path.p_buf, name, path.md()?); } if config.indicator_style != IndicatorStyle::None { @@ -1786,7 +1786,7 @@ fn display_file_name(path: &PathData, config: &Config) -> Option { } fn color_name(ls_colors: &LsColors, path: &Path, name: String, md: &Metadata) -> String { - match ls_colors.style_for_path_with_metadata(path, Some(&md)) { + match ls_colors.style_for_path_with_metadata(path, Some(md)) { Some(style) => style.to_ansi_term_style().paint(name).to_string(), None => name, } From 380e28dde55b62c4704dd00216826e9f44e82fbd Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 11:29:49 -0500 Subject: [PATCH 079/320] refactor/mkdir ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/mkdir/src/mkdir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index d1461c0c9..e8a8ef2db 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -77,7 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mode_match = matches.value_of(OPT_MODE); let mode: u16 = match mode_match { Some(m) => { - let res: Option = u16::from_str_radix(&m, 8).ok(); + let res: Option = u16::from_str_radix(m, 8).ok(); match res { Some(r) => r, _ => crash!(1, "no mode given"), From 5889d81fde3b1cc0ac23b45391dfe3ee411ab0d6 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 22:39:15 -0500 Subject: [PATCH 080/320] refactor/mkfifo ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/mkfifo/src/mkfifo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index cf2fefa50..b8a6bbe38 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -59,7 +59,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } let mode = match matches.value_of(options::MODE) { - Some(m) => match usize::from_str_radix(&m, 8) { + Some(m) => match usize::from_str_radix(m, 8) { Ok(m) => m, Err(e) => { show_error!("invalid mode: {}", e); From 768b343ff9e179714c8bbf0f83a15de9a2f871c8 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 13:22:00 -0500 Subject: [PATCH 081/320] refactor/mktemp ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/mktemp/src/mktemp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 67a88273d..e04de8702 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -165,9 +165,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; if dry_run { - dry_exec(tmpdir, prefix, rand, &suffix) + dry_exec(tmpdir, prefix, rand, suffix) } else { - exec(tmpdir, prefix, rand, &suffix, make_dir, suppress_file_err) + exec(tmpdir, prefix, rand, suffix, make_dir, suppress_file_err) } } From ca50eae0034c6c15729593beb9c8673171c66551 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 11:40:35 -0500 Subject: [PATCH 082/320] refactor/mv ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/mv/src/mv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 6b6482702..bb402737e 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -389,7 +389,7 @@ fn rename_with_fallback(from: &Path, to: &Path) -> io::Result<()> { let file_type = metadata.file_type(); if file_type.is_symlink() { - rename_symlink_fallback(&from, &to)?; + rename_symlink_fallback(from, to)?; } else if file_type.is_dir() { // We remove the destination directory if it exists to match the // behavior of `fs::rename`. As far as I can tell, `fs_extra`'s From 63112783b21c6ae862e0dcb6dd309aa4daac9101 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 12:19:42 -0500 Subject: [PATCH 083/320] refactor/numfmt ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/numfmt/src/format.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index ebe380569..ee692d8f0 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -238,7 +238,7 @@ fn format_and_print_delimited(s: &str, options: &NumfmtOptions) -> Result<()> { } if field_selected { - print!("{}", format_string(&field.trim_start(), options, None)?); + print!("{}", format_string(field.trim_start(), options, None)?); } else { // print unselected field without conversion print!("{}", field); @@ -271,7 +271,7 @@ fn format_and_print_whitespace(s: &str, options: &NumfmtOptions) -> Result<()> { None }; - print!("{}", format_string(&field, options, implicit_padding)?); + print!("{}", format_string(field, options, implicit_padding)?); } else { // print unselected field without conversion print!("{}{}", prefix, field); From 94f5011662c457d920dec5b5999169fa8ac721c2 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 23:00:31 -0500 Subject: [PATCH 084/320] refactor/od ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/od/src/inputdecoder.rs | 2 +- src/uu/od/src/od.rs | 6 +++--- src/uu/od/src/output_info.rs | 2 +- src/uu/od/src/parse_inputs.rs | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/uu/od/src/inputdecoder.rs b/src/uu/od/src/inputdecoder.rs index f6ba59885..606495461 100644 --- a/src/uu/od/src/inputdecoder.rs +++ b/src/uu/od/src/inputdecoder.rs @@ -115,7 +115,7 @@ impl<'a> MemoryDecoder<'a> { /// Creates a clone of the internal buffer. The clone only contain the valid data. pub fn clone_buffer(&self, other: &mut Vec) { - other.clone_from(&self.data); + other.clone_from(self.data); other.resize(self.used_normal_length, 0); } diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 33303f0fc..1e7b4533a 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -130,7 +130,7 @@ impl OdOptions { let mut skip_bytes = match matches.value_of(options::SKIP_BYTES) { None => 0, - Some(s) => match parse_number_of_bytes(&s) { + Some(s) => match parse_number_of_bytes(s) { Ok(i) => i, Err(_) => { return Err(format!("Invalid argument --skip-bytes={}", s)); @@ -176,7 +176,7 @@ impl OdOptions { let read_bytes = match matches.value_of(options::READ_BYTES) { None => None, - Some(s) => match parse_number_of_bytes(&s) { + Some(s) => match parse_number_of_bytes(s) { Ok(i) => Some(i), Err(_) => { return Err(format!("Invalid argument --read-bytes={}", s)); @@ -537,7 +537,7 @@ where print_bytes( &input_offset.format_byte_offset(), &memory_decoder, - &output_info, + output_info, ); } diff --git a/src/uu/od/src/output_info.rs b/src/uu/od/src/output_info.rs index a204fa36e..49c2a09a2 100644 --- a/src/uu/od/src/output_info.rs +++ b/src/uu/od/src/output_info.rs @@ -68,7 +68,7 @@ impl OutputInfo { let print_width_line = print_width_block * (line_bytes / byte_size_block); let spaced_formatters = - OutputInfo::create_spaced_formatter_info(&formats, byte_size_block, print_width_block); + OutputInfo::create_spaced_formatter_info(formats, byte_size_block, print_width_block); OutputInfo { byte_size_line: line_bytes, diff --git a/src/uu/od/src/parse_inputs.rs b/src/uu/od/src/parse_inputs.rs index 288c0870f..419b7173d 100644 --- a/src/uu/od/src/parse_inputs.rs +++ b/src/uu/od/src/parse_inputs.rs @@ -55,7 +55,7 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result) -> Result Ok(CommandLineInputs::FileNames(vec!["-".to_string()])), 1 => { - let offset0 = parse_offset_operand(&input_strings[0]); + let offset0 = parse_offset_operand(input_strings[0]); Ok(match offset0 { Ok(n) => CommandLineInputs::FileAndOffset(("-".to_string(), n, None)), _ => CommandLineInputs::FileNames( @@ -97,8 +97,8 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result { - let offset0 = parse_offset_operand(&input_strings[0]); - let offset1 = parse_offset_operand(&input_strings[1]); + let offset0 = parse_offset_operand(input_strings[0]); + let offset1 = parse_offset_operand(input_strings[1]); match (offset0, offset1) { (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( "-".to_string(), @@ -114,8 +114,8 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result { - let offset = parse_offset_operand(&input_strings[1]); - let label = parse_offset_operand(&input_strings[2]); + let offset = parse_offset_operand(input_strings[1]); + let label = parse_offset_operand(input_strings[2]); match (offset, label) { (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( input_strings[0].to_string(), From b3dd80d39cdc0d67e30f049ced895538f77ad3bc Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 12:20:03 -0500 Subject: [PATCH 085/320] refactor/printf ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/printf/src/tokenize/num_format/num_format.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/printf/src/tokenize/num_format/num_format.rs b/src/uu/printf/src/tokenize/num_format/num_format.rs index a8a60cc57..c030358bb 100644 --- a/src/uu/printf/src/tokenize/num_format/num_format.rs +++ b/src/uu/printf/src/tokenize/num_format/num_format.rs @@ -258,7 +258,7 @@ pub fn num_format(field: &FormatField, in_str_opt: Option<&String>) -> Option Date: Sun, 6 Jun 2021 12:19:18 -0500 Subject: [PATCH 086/320] refactor/pathchk ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/pathchk/src/pathchk.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 9667e0ba1..07e3a3289 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -118,10 +118,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // check a path, given as a slice of it's components and an operating mode fn check_path(mode: &Mode, path: &[String]) -> bool { match *mode { - Mode::Basic => check_basic(&path), - Mode::Extra => check_default(&path) && check_extra(&path), - Mode::Both => check_basic(&path) && check_extra(&path), - _ => check_default(&path), + Mode::Basic => check_basic(path), + Mode::Extra => check_default(path) && check_extra(path), + Mode::Both => check_basic(path) && check_extra(path), + _ => check_default(path), } } @@ -156,7 +156,7 @@ fn check_basic(path: &[String]) -> bool { ); return false; } - if !check_portable_chars(&p) { + if !check_portable_chars(p) { return false; } } @@ -168,7 +168,7 @@ fn check_basic(path: &[String]) -> bool { fn check_extra(path: &[String]) -> bool { // components: leading hyphens for p in path { - if !no_leading_hyphen(&p) { + if !no_leading_hyphen(p) { writeln!( &mut std::io::stderr(), "leading hyphen in file name component '{}'", From f7028c4175581bd98258615c3037209166607957 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 21:10:36 -0500 Subject: [PATCH 087/320] refactor/pinky ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/pinky/src/pinky.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 27dcc2421..d15730b32 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -283,7 +283,7 @@ impl Pinky { } } - print!(" {}", time_string(&ut)); + print!(" {}", time_string(ut)); let mut s = ut.host(); if self.include_where && !s.is_empty() { From ad486a77dcf22e17b262cdb8ce92c8162f15d227 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 23:00:43 -0500 Subject: [PATCH 088/320] refactor/pr ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/pr/src/pr.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index 486cedc00..0761dd09d 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -410,7 +410,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let options = &result_options.unwrap(); let cmd_result = if file_group.len() == 1 { - pr(&file_group.get(0).unwrap(), options) + pr(file_group.get(0).unwrap(), options) } else { mpr(&file_group, options) }; @@ -1114,7 +1114,7 @@ fn write_columns( for (i, cell) in row.iter().enumerate() { if cell.is_none() && options.merge_files_print.is_some() { out.write_all( - get_line_for_printing(&options, &blank_line, columns, i, &line_width, indexes) + get_line_for_printing(options, &blank_line, columns, i, &line_width, indexes) .as_bytes(), )?; } else if cell.is_none() { @@ -1124,7 +1124,7 @@ fn write_columns( let file_line = cell.unwrap(); out.write_all( - get_line_for_printing(&options, file_line, columns, i, &line_width, indexes) + get_line_for_printing(options, file_line, columns, i, &line_width, indexes) .as_bytes(), )?; lines_printed += 1; @@ -1149,7 +1149,7 @@ fn get_line_for_printing( indexes: usize, ) -> String { let blank_line = String::new(); - let formatted_line_number = get_formatted_line_number(&options, file_line.line_number, index); + let formatted_line_number = get_formatted_line_number(options, file_line.line_number, index); let mut complete_line = format!( "{}{}", From 750b68a44c759c2530c4131c203b9be005e3a381 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 23:28:19 -0500 Subject: [PATCH 089/320] refactor/printf ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- .../tokenize/num_format/formatters/cninetyninehexfloatf.rs | 2 +- .../printf/src/tokenize/num_format/formatters/float_common.rs | 4 ++-- src/uu/printf/src/tokenize/num_format/formatters/floatf.rs | 2 +- src/uu/printf/src/tokenize/num_format/formatters/intf.rs | 4 ++-- src/uu/printf/src/tokenize/num_format/num_format.rs | 2 +- src/uu/printf/src/tokenize/sub.rs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs b/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs index f96a991b5..0ca993680 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs @@ -26,7 +26,7 @@ impl Formatter for CninetyNineHexFloatf { ) -> Option { let second_field = field.second_field.unwrap_or(6) + 1; let analysis = FloatAnalysis::analyze( - &str_in, + str_in, initial_prefix, Some(second_field as usize), None, diff --git a/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs b/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs index dd8259233..dfd64296c 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs @@ -298,11 +298,11 @@ pub fn get_primitive_dec( pub fn primitive_to_str_common(prim: &FormatPrimitive, field: &FormatField) -> String { let mut final_str = String::new(); if let Some(ref prefix) = prim.prefix { - final_str.push_str(&prefix); + final_str.push_str(prefix); } match prim.pre_decimal { Some(ref pre_decimal) => { - final_str.push_str(&pre_decimal); + final_str.push_str(pre_decimal); } None => { panic!( diff --git a/src/uu/printf/src/tokenize/num_format/formatters/floatf.rs b/src/uu/printf/src/tokenize/num_format/formatters/floatf.rs index aed50f18e..afb2bcf08 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/floatf.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/floatf.rs @@ -21,7 +21,7 @@ impl Formatter for Floatf { ) -> Option { let second_field = field.second_field.unwrap_or(6) + 1; let analysis = FloatAnalysis::analyze( - &str_in, + str_in, initial_prefix, None, Some(second_field as usize), diff --git a/src/uu/printf/src/tokenize/num_format/formatters/intf.rs b/src/uu/printf/src/tokenize/num_format/formatters/intf.rs index 02c59211b..b6c18d436 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/intf.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/intf.rs @@ -252,7 +252,7 @@ impl Formatter for Intf { fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { let mut final_str: String = String::new(); if let Some(ref prefix) = prim.prefix { - final_str.push_str(&prefix); + final_str.push_str(prefix); } // integral second fields is zero-padded minimum-width // which gets handled before general minimum-width @@ -266,7 +266,7 @@ impl Formatter for Intf { i -= 1; } } - final_str.push_str(&pre_decimal); + final_str.push_str(pre_decimal); } None => { panic!( diff --git a/src/uu/printf/src/tokenize/num_format/num_format.rs b/src/uu/printf/src/tokenize/num_format/num_format.rs index c030358bb..b32731f2d 100644 --- a/src/uu/printf/src/tokenize/num_format/num_format.rs +++ b/src/uu/printf/src/tokenize/num_format/num_format.rs @@ -235,7 +235,7 @@ pub fn num_format(field: &FormatField, in_str_opt: Option<&String>) -> Option { text_so_far.push('%'); - err_conv(&text_so_far); + err_conv(text_so_far); false } } From 84e08cd0710a452f6f4afe63c473d936d435549f Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 22:01:57 -0500 Subject: [PATCH 090/320] refactor/ptx ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/ptx/src/ptx.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 69960ac49..31da8f05d 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -213,7 +213,7 @@ fn read_input(input_files: &[String], config: &Config) -> FileMap { files.push("-"); } else if config.gnu_ext { for file in input_files { - files.push(&file); + files.push(file); } } else { files.push(&input_files[0]); @@ -503,7 +503,7 @@ fn format_tex_line( let keyword = &line[word_ref.position..word_ref.position_end]; let after_chars_trim_idx = (word_ref.position_end, chars_line.len()); let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1]; - let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config); + let (tail, before, after, head) = get_output_chunks(all_before, keyword, all_after, config); output.push_str(&format!( "{5}{0}{6}{5}{1}{6}{5}{2}{6}{5}{3}{6}{5}{4}{6}", format_tex_field(&tail), @@ -515,7 +515,7 @@ fn format_tex_line( "}" )); if config.auto_ref || config.input_ref { - output.push_str(&format!("{}{}{}", "{", format_tex_field(&reference), "}")); + output.push_str(&format!("{}{}{}", "{", format_tex_field(reference), "}")); } output } @@ -546,7 +546,7 @@ fn format_roff_line( let keyword = &line[word_ref.position..word_ref.position_end]; let after_chars_trim_idx = (word_ref.position_end, chars_line.len()); let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1]; - let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config); + let (tail, before, after, head) = get_output_chunks(all_before, keyword, all_after, config); output.push_str(&format!( " \"{}\" \"{}\" \"{}{}\" \"{}\"", format_roff_field(&tail), @@ -556,7 +556,7 @@ fn format_roff_line( format_roff_field(&head) )); if config.auto_ref || config.input_ref { - output.push_str(&format!(" \"{}\"", format_roff_field(&reference))); + output.push_str(&format!(" \"{}\"", format_roff_field(reference))); } output } From 2f7c4884258dde96e214ef25d8d6c19f82bc832d Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 23:00:55 -0500 Subject: [PATCH 091/320] refactor/rmdir ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/rmdir/src/rmdir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index d13a21f60..05cc66d51 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -88,7 +88,7 @@ fn remove(dirs: Vec, ignore: bool, parents: bool, verbose: bool) -> Resu for dir in &dirs { let path = Path::new(&dir[..]); - r = remove_dir(&path, ignore, verbose).and(r); + r = remove_dir(path, ignore, verbose).and(r); if parents { let mut p = path; while let Some(new_p) = p.parent() { From 08713d22ec1880c5115f6e22312a6c5cf3b75101 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 22:14:34 -0500 Subject: [PATCH 092/320] refactor/shred ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/shred/src/shred.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 6a43ed478..964e68a9e 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -381,7 +381,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for path_str in matches.values_of(options::FILE).unwrap() { wipe_file( - &path_str, iterations, remove, size, exact, zero, verbose, force, + path_str, iterations, remove, size, exact, zero, verbose, force, ); } @@ -659,7 +659,7 @@ fn do_remove(path: &Path, orig_filename: &str, verbose: bool) -> Result<(), io:: println!("{}: {}: removing", NAME, orig_filename); } - let renamed_path: Option = wipe_name(&path, verbose); + let renamed_path: Option = wipe_name(path, verbose); if let Some(rp) = renamed_path { fs::remove_file(rp)?; } From 39dbcda66e0be87c1c5891c84aae9774d509c7ef Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 13:22:12 -0500 Subject: [PATCH 093/320] refactor/sort ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/sort/src/check.rs | 4 ++-- src/uu/sort/src/chunks.rs | 6 +++--- src/uu/sort/src/custom_str_cmp.rs | 2 +- src/uu/sort/src/sort.rs | 20 ++++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/uu/sort/src/check.rs b/src/uu/sort/src/check.rs index d3b9d6669..a8e5a0d9b 100644 --- a/src/uu/sort/src/check.rs +++ b/src/uu/sort/src/check.rs @@ -49,7 +49,7 @@ pub fn check(path: &str, settings: &GlobalSettings) -> i32 { let prev_last = prev_chunk.borrow_lines().last().unwrap(); let new_first = chunk.borrow_lines().first().unwrap(); - if compare_by(prev_last, new_first, &settings) == Ordering::Greater { + if compare_by(prev_last, new_first, settings) == Ordering::Greater { if !settings.check_silent { println!("sort: {}:{}: disorder: {}", path, line_idx, new_first.line); } @@ -60,7 +60,7 @@ pub fn check(path: &str, settings: &GlobalSettings) -> i32 { for (a, b) in chunk.borrow_lines().iter().tuple_windows() { line_idx += 1; - if compare_by(a, b, &settings) == Ordering::Greater { + if compare_by(a, b, settings) == Ordering::Greater { if !settings.check_silent { println!("sort: {}:{}: disorder: {}", path, line_idx, b.line); } diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index dde6febd3..3d996e6d6 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -90,7 +90,7 @@ pub fn read( if buffer.len() < carry_over.len() { buffer.resize(carry_over.len() + 10 * 1024, 0); } - buffer[..carry_over.len()].copy_from_slice(&carry_over); + buffer[..carry_over.len()].copy_from_slice(carry_over); let (read, should_continue) = read_to_buffer( file, next_files, @@ -110,7 +110,7 @@ pub fn read( std::mem::transmute::>, Vec>>(lines) }; let read = crash_if_err!(1, std::str::from_utf8(&buf[..read])); - parse_lines(read, &mut lines, separator, &settings); + parse_lines(read, &mut lines, separator, settings); lines }); sender.send(payload).unwrap(); @@ -194,7 +194,7 @@ fn read_to_buffer( continue; } } - let mut sep_iter = memchr_iter(separator, &buffer).rev(); + let mut sep_iter = memchr_iter(separator, buffer).rev(); let last_line_end = sep_iter.next(); if sep_iter.next().is_some() { // We read enough lines. diff --git a/src/uu/sort/src/custom_str_cmp.rs b/src/uu/sort/src/custom_str_cmp.rs index a087a9fc2..089d33bc4 100644 --- a/src/uu/sort/src/custom_str_cmp.rs +++ b/src/uu/sort/src/custom_str_cmp.rs @@ -38,7 +38,7 @@ pub fn custom_str_cmp( ) -> Ordering { if !(ignore_case || ignore_non_dictionary || ignore_non_printing) { // There are no custom settings. Fall back to the default strcmp, which is faster. - return a.cmp(&b); + return a.cmp(b); } let mut a_chars = a .chars() diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 70e3325ad..53619d0d6 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -400,9 +400,9 @@ impl<'a> Line<'a> { let line = self.line.replace('\t', ">"); writeln!(writer, "{}", line)?; - let fields = tokenize(&self.line, settings.separator); + let fields = tokenize(self.line, settings.separator); for selector in settings.selectors.iter() { - let mut selection = selector.get_range(&self.line, Some(&fields)); + let mut selection = selector.get_range(self.line, Some(&fields)); match selector.settings.mode { SortMode::Numeric | SortMode::HumanNumeric => { // find out which range is used for numeric comparisons @@ -756,7 +756,7 @@ impl FieldSelector { /// Get the selection that corresponds to this selector for the line. /// If needs_fields returned false, tokens may be None. fn get_selection<'a>(&self, line: &'a str, tokens: Option<&[Field]>) -> Selection<'a> { - let mut range = &line[self.get_range(&line, tokens)]; + let mut range = &line[self.get_range(line, tokens)]; let num_cache = if self.settings.mode == SortMode::Numeric || self.settings.mode == SortMode::HumanNumeric { @@ -846,7 +846,7 @@ impl FieldSelector { match resolve_index(line, tokens, &self.from) { Resolution::StartOfChar(from) => { - let to = self.to.as_ref().map(|to| resolve_index(line, tokens, &to)); + let to = self.to.as_ref().map(|to| resolve_index(line, tokens, to)); let mut range = match to { Some(Resolution::StartOfChar(mut to)) => { @@ -1257,11 +1257,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fn output_sorted_lines<'a>(iter: impl Iterator>, settings: &GlobalSettings) { if settings.unique { print_sorted( - iter.dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal), - &settings, + iter.dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal), + settings, ); } else { - print_sorted(iter, &settings); + print_sorted(iter, settings); } } @@ -1277,16 +1277,16 @@ fn exec(files: &[String], settings: &GlobalSettings) -> i32 { } else { let mut lines = files.iter().map(open); - ext_sort(&mut lines, &settings); + ext_sort(&mut lines, settings); } 0 } fn sort_by<'a>(unsorted: &mut Vec>, settings: &GlobalSettings) { if settings.stable || settings.unique { - unsorted.par_sort_by(|a, b| compare_by(a, b, &settings)) + unsorted.par_sort_by(|a, b| compare_by(a, b, settings)) } else { - unsorted.par_sort_unstable_by(|a, b| compare_by(a, b, &settings)) + unsorted.par_sort_unstable_by(|a, b| compare_by(a, b, settings)) } } From 79a33728ca15811f59a3fe8df60b71cfe467d7ee Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 11:45:19 -0500 Subject: [PATCH 094/320] refactor/split ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/split/src/platform/unix.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/split/src/platform/unix.rs b/src/uu/split/src/platform/unix.rs index 20d9d637b..a115d1959 100644 --- a/src/uu/split/src/platform/unix.rs +++ b/src/uu/split/src/platform/unix.rs @@ -66,7 +66,7 @@ impl FilterWriter { /// * `filepath` - Path of the output file (forwarded to command as $FILE) fn new(command: &str, filepath: &str) -> FilterWriter { // set $FILE, save previous value (if there was one) - let _with_env_var_set = WithEnvVarSet::new("FILE", &filepath); + let _with_env_var_set = WithEnvVarSet::new("FILE", filepath); let shell_process = Command::new(env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_owned())) @@ -117,7 +117,7 @@ pub fn instantiate_current_writer( ) as Box), Some(ref filter_command) => BufWriter::new(Box::new( // spawn a shell command and write to it - FilterWriter::new(&filter_command, &filename), + FilterWriter::new(filter_command, filename), ) as Box), } } From c8c14ca40c4fa7afb6473af1426fed64e478ea9d Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 22:39:02 -0500 Subject: [PATCH 095/320] refactor/stat ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/stat/src/stat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index fa070d9b7..4e1d9d2c9 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -477,7 +477,7 @@ impl Stater { Stater::generate_tokens(&Stater::default_format(show_fs, terse, false), use_printf) .unwrap() } else { - Stater::generate_tokens(&format_str, use_printf)? + Stater::generate_tokens(format_str, use_printf)? }; let default_dev_tokens = Stater::generate_tokens(&Stater::default_format(show_fs, terse, true), use_printf) From 6e98ea78ac1554ca2d533691bac56b1301a81e82 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 13:28:14 -0500 Subject: [PATCH 096/320] refactor/stdbuf ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/stdbuf/src/stdbuf.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 852fe3ef9..5baff4825 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -69,9 +69,9 @@ impl<'a> TryFrom<&ArgMatches<'a>> for ProgramOptions { fn try_from(matches: &ArgMatches) -> Result { Ok(ProgramOptions { - stdin: check_option(&matches, options::INPUT)?, - stdout: check_option(&matches, options::OUTPUT)?, - stderr: check_option(&matches, options::ERROR)?, + stdin: check_option(matches, options::INPUT)?, + stdout: check_option(matches, options::OUTPUT)?, + stderr: check_option(matches, options::ERROR)?, }) } } From a8a2b3ec84f62aa0a0331245651d7701b229012c Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 13:54:09 -0500 Subject: [PATCH 097/320] refactor/timeout ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/timeout/src/timeout.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 4ef9b2331..afe560ee5 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -55,7 +55,7 @@ impl Config { fn from(options: clap::ArgMatches) -> Config { let signal = match options.value_of(options::SIGNAL) { Some(signal_) => { - let signal_result = signal_by_name_or_value(&signal_); + let signal_result = signal_by_name_or_value(signal_); match signal_result { None => { unreachable!("invalid signal '{}'", signal_); @@ -67,7 +67,7 @@ impl Config { }; let kill_after: Duration = match options.value_of(options::KILL_AFTER) { - Some(time) => uucore::parse_time::from_str(&time).unwrap(), + Some(time) => uucore::parse_time::from_str(time).unwrap(), None => Duration::new(0, 0), }; From 8f0d42da39dc7f51afca026e87f8553160e19f35 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 5 Jun 2021 22:38:53 -0500 Subject: [PATCH 098/320] refactor/wc ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- src/uu/wc/src/wc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 031c25739..d1e1f75ca 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -374,8 +374,8 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { let num_inputs = inputs.len(); for input in &inputs { - let word_count = word_count_from_input(&input, settings).unwrap_or_else(|err| { - show_error(&input, err); + let word_count = word_count_from_input(input, settings).unwrap_or_else(|err| { + show_error(input, err); error_count += 1; WordCount::default() }); From 0dc8c18bac79680214fdec07ed053b11b3c4672b Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 14:13:54 -0500 Subject: [PATCH 099/320] tests ~ fix `cargo clippy` complaint (clippy::needless_borrow) --- tests/by-util/test_cat.rs | 2 +- tests/by-util/test_cp.rs | 8 ++++---- tests/by-util/test_ls.rs | 20 ++++++++++---------- tests/by-util/test_pathchk.rs | 12 ++++++------ 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index fadf378ab..d83b5515b 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -438,7 +438,7 @@ fn test_domain_socket() { let child = new_ucmd!().args(&[socket_path]).run_no_wait(); barrier.wait(); let stdout = &child.wait_with_output().unwrap().stdout; - let output = String::from_utf8_lossy(&stdout); + let output = String::from_utf8_lossy(stdout); assert_eq!("a\tb", output); thread.join().unwrap(); diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index ff607f984..bc6c6fc79 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -618,7 +618,7 @@ fn test_cp_deref() { // Check the content of the destination file that was copied. assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); let path_to_check = path_to_new_symlink.to_str().unwrap(); - assert_eq!(at.read(&path_to_check), "Hello, World!\n"); + assert_eq!(at.read(path_to_check), "Hello, World!\n"); } #[test] fn test_cp_no_deref() { @@ -655,7 +655,7 @@ fn test_cp_no_deref() { // Check the content of the destination file that was copied. assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); let path_to_check = path_to_new_symlink.to_str().unwrap(); - assert_eq!(at.read(&path_to_check), "Hello, World!\n"); + assert_eq!(at.read(path_to_check), "Hello, World!\n"); } #[test] @@ -823,7 +823,7 @@ fn test_cp_deref_folder_to_folder() { // Check the content of the symlink let path_to_check = path_to_new_symlink.to_str().unwrap(); - assert_eq!(at.read(&path_to_check), "Hello, World!\n"); + assert_eq!(at.read(path_to_check), "Hello, World!\n"); } #[test] @@ -923,7 +923,7 @@ fn test_cp_no_deref_folder_to_folder() { // Check the content of the symlink let path_to_check = path_to_new_symlink.to_str().unwrap(); - assert_eq!(at.read(&path_to_check), "Hello, World!\n"); + assert_eq!(at.read(path_to_check), "Hello, World!\n"); } #[test] diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 20c6b913d..33373960f 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -398,7 +398,7 @@ fn test_ls_long_formats() { .arg("--author") .arg("test-long-formats") .succeeds(); - assert!(re_three.is_match(&result.stdout_str())); + assert!(re_three.is_match(result.stdout_str())); #[cfg(unix)] { @@ -701,20 +701,20 @@ fn test_ls_styles() { .arg("-l") .arg("--time-style=full-iso") .succeeds(); - assert!(re_full.is_match(&result.stdout_str())); + assert!(re_full.is_match(result.stdout_str())); //long-iso let result = scene .ucmd() .arg("-l") .arg("--time-style=long-iso") .succeeds(); - assert!(re_long.is_match(&result.stdout_str())); + assert!(re_long.is_match(result.stdout_str())); //iso let result = scene.ucmd().arg("-l").arg("--time-style=iso").succeeds(); - assert!(re_iso.is_match(&result.stdout_str())); + assert!(re_iso.is_match(result.stdout_str())); //locale let result = scene.ucmd().arg("-l").arg("--time-style=locale").succeeds(); - assert!(re_locale.is_match(&result.stdout_str())); + assert!(re_locale.is_match(result.stdout_str())); //Overwrite options tests let result = scene @@ -723,19 +723,19 @@ fn test_ls_styles() { .arg("--time-style=long-iso") .arg("--time-style=iso") .succeeds(); - assert!(re_iso.is_match(&result.stdout_str())); + assert!(re_iso.is_match(result.stdout_str())); let result = scene .ucmd() .arg("--time-style=iso") .arg("--full-time") .succeeds(); - assert!(re_full.is_match(&result.stdout_str())); + assert!(re_full.is_match(result.stdout_str())); let result = scene .ucmd() .arg("--full-time") .arg("--time-style=iso") .succeeds(); - assert!(re_iso.is_match(&result.stdout_str())); + assert!(re_iso.is_match(result.stdout_str())); let result = scene .ucmd() @@ -743,7 +743,7 @@ fn test_ls_styles() { .arg("--time-style=iso") .arg("--full-time") .succeeds(); - assert!(re_full.is_match(&result.stdout_str())); + assert!(re_full.is_match(result.stdout_str())); let result = scene .ucmd() @@ -751,7 +751,7 @@ fn test_ls_styles() { .arg("-x") .arg("-l") .succeeds(); - assert!(re_full.is_match(&result.stdout_str())); + assert!(re_full.is_match(result.stdout_str())); at.touch("test2"); let result = scene.ucmd().arg("--full-time").arg("-x").succeeds(); diff --git a/tests/by-util/test_pathchk.rs b/tests/by-util/test_pathchk.rs index 3bc12f0b6..8ba3b9033 100644 --- a/tests/by-util/test_pathchk.rs +++ b/tests/by-util/test_pathchk.rs @@ -38,7 +38,7 @@ fn test_posix_mode() { // fail on long path new_ucmd!() - .args(&["-p", &"dir".repeat(libc::PATH_MAX as usize + 1).as_str()]) + .args(&["-p", "dir".repeat(libc::PATH_MAX as usize + 1).as_str()]) .fails() .no_stdout(); @@ -46,7 +46,7 @@ fn test_posix_mode() { new_ucmd!() .args(&[ "-p", - &format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(), + format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(), ]) .fails() .no_stdout(); @@ -76,7 +76,7 @@ fn test_posix_special() { // fail on long path new_ucmd!() - .args(&["-P", &"dir".repeat(libc::PATH_MAX as usize + 1).as_str()]) + .args(&["-P", "dir".repeat(libc::PATH_MAX as usize + 1).as_str()]) .fails() .no_stdout(); @@ -84,7 +84,7 @@ fn test_posix_special() { new_ucmd!() .args(&[ "-P", - &format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(), + format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(), ]) .fails() .no_stdout(); @@ -117,7 +117,7 @@ fn test_posix_all() { .args(&[ "-p", "-P", - &"dir".repeat(libc::PATH_MAX as usize + 1).as_str(), + "dir".repeat(libc::PATH_MAX as usize + 1).as_str(), ]) .fails() .no_stdout(); @@ -127,7 +127,7 @@ fn test_posix_all() { .args(&[ "-p", "-P", - &format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(), + format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(), ]) .fails() .no_stdout(); From 26cb6540da6ae38d1c9562cff85144b5e1902cbd Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 14:14:35 -0500 Subject: [PATCH 100/320] tests ~ fix `cargo clippy` complaint (clippy::useless_format) --- tests/by-util/test_ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 33373960f..f8aa4453b 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1143,7 +1143,7 @@ fn test_ls_indicator_style() { for opt in options { scene .ucmd() - .arg(format!("{}", opt)) + .arg(opt.to_string()) .succeeds() .stdout_contains(&"/"); } From 3f0ac0612220dc7bf669338060cf08ac8a4db89b Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 13:59:48 -0500 Subject: [PATCH 101/320] refactor/od ~ fix `cargo clippy` complaint (*allow* clippy::enum_variant_names) --- src/uu/od/src/formatteriteminfo.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/od/src/formatteriteminfo.rs b/src/uu/od/src/formatteriteminfo.rs index d44d97a92..13cf62246 100644 --- a/src/uu/od/src/formatteriteminfo.rs +++ b/src/uu/od/src/formatteriteminfo.rs @@ -2,6 +2,7 @@ use std::fmt; +#[allow(clippy::enum_variant_names)] #[derive(Copy)] pub enum FormatWriter { IntWriter(fn(u64) -> String), From 7df5acc2dccdaa8d7bdc5396c3f3f6f21632cabb Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 17:43:44 -0500 Subject: [PATCH 102/320] tests ~ fix `cargo clippy` complaint (*allow* clippy::manual_strip; with FixME/ToDO) - replace with the included/noted code when MSRV includes a stabilized `String::strip_prefix()` --- tests/common/util.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/common/util.rs b/tests/common/util.rs index 2f7d7dcc4..11425e9b8 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -625,11 +625,20 @@ impl AtPath { // Source: // http://stackoverflow.com/questions/31439011/getfinalpathnamebyhandle-without-prepended let prefix = "\\\\?\\"; + // FixME: replace ... + #[allow(clippy::manual_strip)] if s.starts_with(prefix) { String::from(&s[prefix.len()..]) } else { s } + // ... with ... + // if let Some(stripped) = s.strip_prefix(prefix) { + // String::from(stripped) + // } else { + // s + // } + // ... when using MSRV with stabilized `strip_prefix()` } } From 9feff1e441f4e04e393231b8346cae897cb04639 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 15:07:48 -0500 Subject: [PATCH 103/320] tests ~ fix `cargo clippy` complaint (*allow* clippy::needless_borrow; for *false positives*) --- tests/by-util/test_pinky.rs | 2 ++ tests/by-util/test_stat.rs | 2 ++ tests/by-util/test_users.rs | 2 ++ tests/by-util/test_who.rs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index 0813e5e1b..8b50ec2bd 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -102,6 +102,8 @@ fn expected_result(args: &[&str]) -> String { #[cfg(target_vendor = "apple")] let util_name = format!("g{}", util_name!()); + // note: clippy::needless_borrow *false positive* + #[allow(clippy::needless_borrow)] TestScenario::new(&util_name) .cmd_keepenv(util_name) .env("LANGUAGE", "C") diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 89dd96752..37328d5ae 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -313,6 +313,8 @@ fn expected_result(args: &[&str]) -> String { #[cfg(target_vendor = "apple")] let util_name = format!("g{}", util_name!()); + // note: clippy::needless_borrow *false positive* + #[allow(clippy::needless_borrow)] TestScenario::new(&util_name) .cmd_keepenv(util_name) .env("LANGUAGE", "C") diff --git a/tests/by-util/test_users.rs b/tests/by-util/test_users.rs index 8ceb0eeb8..68bdf9a5e 100644 --- a/tests/by-util/test_users.rs +++ b/tests/by-util/test_users.rs @@ -13,6 +13,8 @@ fn test_users_check_name() { #[cfg(target_vendor = "apple")] let util_name = format!("g{}", util_name!()); + // note: clippy::needless_borrow *false positive* + #[allow(clippy::needless_borrow)] let expected = TestScenario::new(&util_name) .cmd_keepenv(util_name) .env("LANGUAGE", "C") diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 16444b0cb..4907d2306 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -238,6 +238,8 @@ fn expected_result(args: &[&str]) -> String { #[cfg(target_vendor = "apple")] let util_name = format!("g{}", util_name!()); + // note: clippy::needless_borrow *false positive* + #[allow(clippy::needless_borrow)] TestScenario::new(&util_name) .cmd_keepenv(util_name) .env("LANGUAGE", "C") From 423f4f9bf12103e1382c7f5497a6de51d34d0693 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 17:40:22 -0500 Subject: [PATCH 104/320] fix/cp ~ correct `cargo clippy` complaint exception (*allow* clippy::unnecessary_wraps) --- src/uu/cp/src/cp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 6a114cf44..cc0103044 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1088,7 +1088,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu } #[cfg(not(windows))] -#[allow(clippy::unnecessary_unwrap)] // needed for windows version +#[allow(clippy::unnecessary_wraps)] // needed for windows version fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { match std::os::unix::fs::symlink(source, dest).context(context) { Ok(_) => Ok(()), From 5b697aace81944327be20b32e21b618e5494b5fa Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 11:18:12 -0500 Subject: [PATCH 105/320] maint/build ~ normalize makefile recipe format --- GNUmakefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 102856b66..5d7966722 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -268,11 +268,11 @@ test: ${CARGO} test ${CARGOFLAGS} --features "$(TESTS) $(TEST_SPEC_FEATURE)" --no-default-features $(TEST_NO_FAIL_FAST) busybox-src: - if [ ! -e $(BUSYBOX_SRC) ]; then \ - mkdir -p $(BUSYBOX_ROOT); \ - wget https://busybox.net/downloads/busybox-$(BUSYBOX_VER).tar.bz2 -P $(BUSYBOX_ROOT); \ - tar -C $(BUSYBOX_ROOT) -xf $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER).tar.bz2; \ - fi; \ + if [ ! -e "$(BUSYBOX_SRC)" ] ; then \ + mkdir -p "$(BUSYBOX_ROOT)" ; \ + wget "https://busybox.net/downloads/busybox-$(BUSYBOX_VER).tar.bz2" -P "$(BUSYBOX_ROOT)" ; \ + tar -C "$(BUSYBOX_ROOT)" -xf "$(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER).tar.bz2" ; \ + fi ; # This is a busybox-specific config file their test suite wants to parse. $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config @@ -280,8 +280,8 @@ $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config # Test under the busybox test suite $(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config - cp $(BUILDDIR)/coreutils $(BUILDDIR)/busybox; \ - chmod +x $@; + cp "$(BUILDDIR)/coreutils" "$(BUILDDIR)/busybox" + chmod +x $@ prepare-busytest: $(BUILDDIR)/busybox From 4495964864c2758c652981fe8b310c323ae7ade6 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 11:21:38 -0500 Subject: [PATCH 106/320] maint/CICD ~ disable inapplicable tests from BusyTests --- GNUmakefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 5d7966722..e5ad01340 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -284,6 +284,8 @@ $(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config chmod +x $@ prepare-busytest: $(BUILDDIR)/busybox + # disable inapplicable tests + -( cd "$(BUSYBOX_SRC)/testsuite" ; if [ -e "busybox.tests" ] ; then mv busybox.tests busybox.tests- ; fi ; ) ifeq ($(EXES),) busytest: From 2ff46a78b108690d13e4cda409a57ec9252c37d4 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 11:22:42 -0500 Subject: [PATCH 107/320] maint/CICD ~ summarize BusyTest output for dashboard --- .github/workflows/CICD.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index ad3177224..a8ed1b704 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -197,7 +197,11 @@ jobs: run: | bindir=$(pwd)/target/debug cd tmp/busybox-*/testsuite - S=$(bindir=$bindir ./runtest) && printf "%s\n" "$S" || { printf "%s\n" "$S" | grep "FAIL:" | sed -e "s/FAIL: /::warning ::Test failure:/g" ; } + ## S=$(bindir=$bindir ./runtest) && printf "%s\n" "$S" || { printf "%s\n" "$S" | grep "FAIL:" | sed -e "s/FAIL: /::warning ::Test failure:/g" ; } + output=$(bindir=$bindir ./runtest 2>&1 || true) + printf "%s\n" "${output}" + n_fails=$(echo "$output" | grep "^FAIL:\s" | wc --lines) + if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi makefile_build: name: Test the build target of the Makefile From 1faa9eebab2bca3baeb6ef387d784971a90ca1ab Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 6 Jun 2021 19:24:04 -0500 Subject: [PATCH 108/320] refactor/polish ~ `cargo make format` --- tests/by-util/test_sort.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 75611abfc..7a0143b43 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -844,12 +844,7 @@ fn test_compress_fail() { #[test] fn test_merge_batches() { new_ucmd!() - .args(&[ - "ext_sort.txt", - "-n", - "-S", - "150B", - ]) + .args(&["ext_sort.txt", "-n", "-S", "150B"]) .succeeds() .stdout_only_fixture("ext_sort.expected"); } From 98225105afc35feb703c4fe8ce6c41add551c582 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 6 Jun 2021 00:03:53 +0200 Subject: [PATCH 109/320] id: implement '--zero' flag * add tests for '--zero' flag * add a bunch of requires/conflicts rules for flags (incl. tests) --- src/uu/id/src/id.rs | 56 +++++++++++++----- tests/by-util/test_id.rs | 123 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 157 insertions(+), 22 deletions(-) diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index b32988ebc..f44d77c5f 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -13,8 +13,10 @@ // This is not based on coreutils (8.32) GNU's `id`. // This is based on BSD's `id` (noticeable in functionality, usage text, options text, etc.) // +// Option '--zero' does not exist for BSD's `id`, therefor '--zero' is only allowed together +// with other options that are available on GNU's `id`. -// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag +// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag #![allow(non_camel_case_types)] #![allow(dead_code)] @@ -85,6 +87,7 @@ mod options { pub const OPT_NAME: &str = "name"; pub const OPT_PASSWORD: &str = "password"; // GNU's id does not have this pub const OPT_REAL_ID: &str = "real"; + pub const OPT_ZERO: &str = "zero"; // BSD's id does not have this pub const ARG_USERS: &str = "USER"; } @@ -102,26 +105,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(options::OPT_AUDIT) .short("A") + .conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER, options::OPT_HUMAN_READABLE, options::OPT_PASSWORD, options::OPT_GROUPS, options::OPT_ZERO]) .help("Display the process audit user ID and other process audit properties, which requires privilege (not available on Linux)."), ) .arg( Arg::with_name(options::OPT_EFFECTIVE_USER) .short("u") .long(options::OPT_EFFECTIVE_USER) - .help("Display the effective user ID as a number."), + .conflicts_with(options::OPT_GROUP) + .help("Display only the effective user ID as a number."), ) .arg( Arg::with_name(options::OPT_GROUP) .short("g") .long(options::OPT_GROUP) - .help("Display the effective group ID as a number"), + .help("Display only the effective group ID as a number"), ) .arg( Arg::with_name(options::OPT_GROUPS) .short("G") .long(options::OPT_GROUPS) .conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER, options::OPT_HUMAN_READABLE, options::OPT_PASSWORD, options::OPT_AUDIT]) - .help("Display the different group IDs as white-space separated numbers, in no particular order."), + .help("Display only the different group IDs as white-space separated numbers, in no particular order."), ) .arg( Arg::with_name(options::OPT_HUMAN_READABLE) @@ -145,6 +150,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::OPT_REAL_ID) .help("Display the real ID for the -g and -u options instead of the effective ID."), ) + .arg( + Arg::with_name(options::OPT_ZERO) + .short("z") + .long(options::OPT_ZERO) + .help("delimit entries with NUL characters, not whitespace;\nnot permitted in default format"), + ) .arg( Arg::with_name(options::ARG_USERS) .multiple(true) @@ -158,11 +169,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let gflag = matches.is_present(options::OPT_GROUP); let gsflag = matches.is_present(options::OPT_GROUPS); let rflag = matches.is_present(options::OPT_REAL_ID); + let zflag = matches.is_present(options::OPT_ZERO); - // -ugG + // "default format" is when none of '-ugG' was used + // could not implement these "required" rules with just clap if (nflag || rflag) && !(uflag || gflag || gsflag) { crash!(1, "cannot print only names or real IDs in default format"); } + if (zflag) && !(uflag || gflag || gsflag) { + crash!(1, "option --zero not permitted in default format"); + } let users: Vec = matches .values_of(options::ARG_USERS) @@ -183,17 +199,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } }; + let line_ending = if zflag { '\0' } else { '\n' }; + if gflag { let id = possible_pw .map(|p| p.gid()) .unwrap_or(if rflag { getgid() } else { getegid() }); - println!( - "{}", + print!( + "{}{}", if nflag { entries::gid2grp(id).unwrap_or_else(|_| id.to_string()) } else { id.to_string() - } + }, + line_ending ); return 0; } @@ -202,20 +221,22 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let id = possible_pw .map(|p| p.uid()) .unwrap_or(if rflag { getuid() } else { geteuid() }); - println!( - "{}", + print!( + "{}{}", if nflag { entries::uid2usr(id).unwrap_or_else(|_| id.to_string()) } else { id.to_string() - } + }, + line_ending ); return 0; } if gsflag { - println!( - "{}", + let delimiter = if zflag { "" } else { " " }; + print!( + "{}{}", if nflag { possible_pw .map(|p| p.belongs_to()) @@ -223,7 +244,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .iter() .map(|&id| entries::gid2grp(id).unwrap()) .collect::>() - .join(" ") + .join(delimiter) } else { possible_pw .map(|p| p.belongs_to()) @@ -231,8 +252,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .iter() .map(|&id| id.to_string()) .collect::>() - .join(" ") - } + .join(delimiter) + }, + line_ending ); return 0; } @@ -398,3 +420,5 @@ fn id_print(possible_pw: Option, p_euid: bool, p_egid: bool) { .join(",") ); } + +fn get_groups() -> diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 1f8249aab..a8ad37190 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -96,11 +96,19 @@ fn test_id_group() { let mut result = scene.ucmd().arg("-g").succeeds(); let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); + assert!(s1.parse::().is_ok()); result = scene.ucmd().arg("--group").succeeds(); let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); + assert!(s1.parse::().is_ok()); + + #[cfg(any(target_vendor = "apple", target_os = "linux"))] + for flag in &["-g", "--group"] { + new_ucmd!() + .arg(flag) + .succeeds() + .stdout_is(expected_result(&[flag], false)); + } } #[test] @@ -110,13 +118,22 @@ fn test_id_groups() { let result = scene.ucmd().arg("-G").succeeds(); let groups = result.stdout_str().trim().split_whitespace(); for s in groups { - assert!(s.parse::().is_ok()); + assert!(s.parse::().is_ok()); } let result = scene.ucmd().arg("--groups").succeeds(); let groups = result.stdout_str().trim().split_whitespace(); for s in groups { - assert!(s.parse::().is_ok()); + assert!(s.parse::().is_ok()); + } + + #[cfg(any(target_vendor = "apple", target_os = "linux"))] + for args in &["-G", "--groups"] { + let expect = expected_result(&[args], false); + let actual = new_ucmd!().arg(&args).succeeds().stdout_move_str(); + let mut v_actual: Vec<&str> = actual.split_whitespace().collect(); + let mut v_expect: Vec<&str> = expect.split_whitespace().collect(); + assert_eq!(v_actual.sort_unstable(), v_expect.sort_unstable()); } } @@ -126,11 +143,19 @@ fn test_id_user() { let result = scene.ucmd().arg("-u").succeeds(); let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); + assert!(s1.parse::().is_ok()); let result = scene.ucmd().arg("--user").succeeds(); let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); + assert!(s1.parse::().is_ok()); + + #[cfg(any(target_vendor = "apple", target_os = "linux"))] + for flag in &["-u", "--user"] { + new_ucmd!() + .arg(flag) + .succeeds() + .stdout_is(expected_result(&[flag], false)); + } } #[test] @@ -167,3 +192,89 @@ fn test_id_password_style() { assert!(result.stdout_str().starts_with(&username)); } + +#[test] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] +fn test_id_default_format() { + // -ugG + for flag in &["--name", "--real"] { + new_ucmd!() + .arg(flag) + .fails() + .stderr_is(expected_result(&[flag], true)); + for &opt in &["--user", "--group", "--groups"] { + if is_ci() && *flag == "--name" { + // '--name' does not work in CI: + // id: cannot find name for user ID 1001 + // id: cannot find name for group ID 116 + println!("test skipped:"); + continue; + } + let args = [opt, flag]; + let expect = expected_result(&args, false); + let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); + let mut v_actual: Vec<&str> = actual.split_whitespace().collect(); + let mut v_expect: Vec<&str> = expect.split_whitespace().collect(); + assert_eq!(v_actual.sort_unstable(), v_expect.sort_unstable()); + } + } +} + +#[test] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] +fn test_id_zero() { + for z_flag in &["-z", "--zero"] { + for &opt in &["-n", "--name", "-r", "--real"] { + let args = [opt, z_flag]; + new_ucmd!() + .args(&args) + .fails() + .stderr_is(expected_result(&args, true)); + } + for &opt in &["-u", "--user", "-g", "--group"] { + let args = [opt, z_flag]; + new_ucmd!() + .args(&args) + .succeeds() + .stdout_is(expected_result(&args, false)); + } + // '--groups' ids are in no particular order and when paired with '--zero' there's no + // delimiter which makes the split_whitespace-collect-into-vector comparison impossible. + for opt in &["-G", "--groups"] { + let args = [opt, z_flag]; + let result = new_ucmd!().args(&args).succeeds().stdout_move_str(); + assert!(!result.contains(" ")); + assert!(result.ends_with('\0')); + } + } +} + +#[cfg(any(target_vendor = "apple", target_os = "linux"))] +fn expected_result(args: &[&str], exp_fail: bool) -> String { + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(target_vendor = "apple")] + let util_name = format!("g{}", util_name!()); + + let result = if !exp_fail { + TestScenario::new(&util_name) + .cmd_keepenv(util_name) + .env("LANGUAGE", "C") + .args(args) + .succeeds() + .stdout_move_str() + } else { + TestScenario::new(&util_name) + .cmd_keepenv(util_name) + .env("LANGUAGE", "C") + .args(args) + .fails() + .stderr_move_str() + }; + // #[cfg(target_vendor = "apple")] + return if cfg!(target_os = "macos") && result.starts_with("gid") { + result[1..].to_string() + } else { + result + }; +} From 448caa3d1c09272868e3dad71c8edb24c5cd432e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 7 Jun 2021 14:51:58 +0200 Subject: [PATCH 110/320] ln: refactor argument handling --- src/uu/ln/src/ln.rs | 111 ++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index cd5eef842..3d40b6bab 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -78,17 +78,19 @@ fn get_long_usage() -> String { static ABOUT: &str = "change file owner and group"; -static OPT_B: &str = "b"; -static OPT_BACKUP: &str = "backup"; -static OPT_FORCE: &str = "force"; -static OPT_INTERACTIVE: &str = "interactive"; -static OPT_NO_DEREFERENCE: &str = "no-dereference"; -static OPT_SYMBOLIC: &str = "symbolic"; -static OPT_SUFFIX: &str = "suffix"; -static OPT_TARGET_DIRECTORY: &str = "target-directory"; -static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; -static OPT_RELATIVE: &str = "relative"; -static OPT_VERBOSE: &str = "verbose"; +mod options { + pub const B: &str = "b"; + pub const BACKUP: &str = "backup"; + pub const FORCE: &str = "force"; + pub const INTERACTIVE: &str = "interactive"; + pub const NO_DEREFERENCE: &str = "no-dereference"; + pub const SYMBOLIC: &str = "symbolic"; + pub const SUFFIX: &str = "suffix"; + pub const TARGET_DIRECTORY: &str = "target-directory"; + pub const NO_TARGET_DIRECTORY: &str = "no-target-directory"; + pub const RELATIVE: &str = "relative"; + pub const VERBOSE: &str = "verbose"; +} static ARG_FILES: &str = "files"; @@ -101,47 +103,42 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .about(ABOUT) .usage(&usage[..]) .after_help(&long_usage[..]) - .arg(Arg::with_name(OPT_B).short(OPT_B).help( + .arg(Arg::with_name(options::B).short(options::B).help( "make a backup of each file that would otherwise be overwritten or \ removed", )) .arg( - Arg::with_name(OPT_BACKUP) - .long(OPT_BACKUP) + Arg::with_name(options::BACKUP) + .long(options::BACKUP) .help( "make a backup of each file that would otherwise be overwritten \ or removed", ) .takes_value(true) - .possible_value("simple") - .possible_value("never") - .possible_value("numbered") - .possible_value("t") - .possible_value("existing") - .possible_value("nil") - .possible_value("none") - .possible_value("off") + .possible_values(&[ + "simple", "never", "numbered", "t", "existing", "nil", "none", "off", + ]) .value_name("METHOD"), ) // TODO: opts.arg( // Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \ // to make hard links to directories"); .arg( - Arg::with_name(OPT_FORCE) + Arg::with_name(options::FORCE) .short("f") - .long(OPT_FORCE) + .long(options::FORCE) .help("remove existing destination files"), ) .arg( - Arg::with_name(OPT_INTERACTIVE) + Arg::with_name(options::INTERACTIVE) .short("i") - .long(OPT_INTERACTIVE) + .long(options::INTERACTIVE) .help("prompt whether to remove existing destination files"), ) .arg( - Arg::with_name(OPT_NO_DEREFERENCE) + Arg::with_name(options::NO_DEREFERENCE) .short("n") - .long(OPT_NO_DEREFERENCE) + .long(options::NO_DEREFERENCE) .help( "treat LINK_executable!() as a normal file if it is a \ symbolic link to a directory", @@ -153,43 +150,45 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // TODO: opts.arg( // Arg::with_name(("P", "physical", "make hard links directly to symbolic links"); .arg( - Arg::with_name(OPT_SYMBOLIC) + Arg::with_name(options::SYMBOLIC) .short("s") .long("symbolic") - .help("make symbolic links instead of hard links"), + .help("make symbolic links instead of hard links") + // override added for https://github.com/uutils/coreutils/issues/2359 + .overrides_with(options::SYMBOLIC), ) .arg( - Arg::with_name(OPT_SUFFIX) + Arg::with_name(options::SUFFIX) .short("S") - .long(OPT_SUFFIX) + .long(options::SUFFIX) .help("override the usual backup suffix") .value_name("SUFFIX") .takes_value(true), ) .arg( - Arg::with_name(OPT_TARGET_DIRECTORY) + Arg::with_name(options::TARGET_DIRECTORY) .short("t") - .long(OPT_TARGET_DIRECTORY) + .long(options::TARGET_DIRECTORY) .help("specify the DIRECTORY in which to create the links") .value_name("DIRECTORY") - .conflicts_with(OPT_NO_TARGET_DIRECTORY), + .conflicts_with(options::NO_TARGET_DIRECTORY), ) .arg( - Arg::with_name(OPT_NO_TARGET_DIRECTORY) + Arg::with_name(options::NO_TARGET_DIRECTORY) .short("T") - .long(OPT_NO_TARGET_DIRECTORY) + .long(options::NO_TARGET_DIRECTORY) .help("treat LINK_executable!() as a normal file always"), ) .arg( - Arg::with_name(OPT_RELATIVE) + Arg::with_name(options::RELATIVE) .short("r") - .long(OPT_RELATIVE) + .long(options::RELATIVE) .help("create symbolic links relative to link location"), ) .arg( - Arg::with_name(OPT_VERBOSE) + Arg::with_name(options::VERBOSE) .short("v") - .long(OPT_VERBOSE) + .long(options::VERBOSE) .help("print name of each linked file"), ) .arg( @@ -209,18 +208,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(PathBuf::from) .collect(); - let overwrite_mode = if matches.is_present(OPT_FORCE) { + let overwrite_mode = if matches.is_present(options::FORCE) { OverwriteMode::Force - } else if matches.is_present(OPT_INTERACTIVE) { + } else if matches.is_present(options::INTERACTIVE) { OverwriteMode::Interactive } else { OverwriteMode::NoClobber }; - let backup_mode = if matches.is_present(OPT_B) { + let backup_mode = if matches.is_present(options::B) { BackupMode::ExistingBackup - } else if matches.is_present(OPT_BACKUP) { - match matches.value_of(OPT_BACKUP) { + } else if matches.is_present(options::BACKUP) { + match matches.value_of(options::BACKUP) { None => BackupMode::ExistingBackup, Some(mode) => match mode { "simple" | "never" => BackupMode::SimpleBackup, @@ -234,8 +233,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { BackupMode::NoBackup }; - let backup_suffix = if matches.is_present(OPT_SUFFIX) { - matches.value_of(OPT_SUFFIX).unwrap() + let backup_suffix = if matches.is_present(options::SUFFIX) { + matches.value_of(options::SUFFIX).unwrap() } else { "~" }; @@ -243,14 +242,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let settings = Settings { overwrite: overwrite_mode, backup: backup_mode, - force: matches.is_present(OPT_FORCE), + force: matches.is_present(options::FORCE), suffix: backup_suffix.to_string(), - symbolic: matches.is_present(OPT_SYMBOLIC), - relative: matches.is_present(OPT_RELATIVE), - target_dir: matches.value_of(OPT_TARGET_DIRECTORY).map(String::from), - no_target_dir: matches.is_present(OPT_NO_TARGET_DIRECTORY), - no_dereference: matches.is_present(OPT_NO_DEREFERENCE), - verbose: matches.is_present(OPT_VERBOSE), + symbolic: matches.is_present(options::SYMBOLIC), + relative: matches.is_present(options::RELATIVE), + target_dir: matches + .value_of(options::TARGET_DIRECTORY) + .map(String::from), + no_target_dir: matches.is_present(options::NO_TARGET_DIRECTORY), + no_dereference: matches.is_present(options::NO_DEREFERENCE), + verbose: matches.is_present(options::VERBOSE), }; exec(&paths[..], &settings) From 26ad05cbb4194bd4fd51dec86c1c04db9712f90a Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 7 Jun 2021 19:52:49 +0200 Subject: [PATCH 111/320] uucore: fix order of group IDs returned from entries::get_groups() As discussed here: https://github.com/uutils/coreutils/pull/2361 the group IDs returned for GNU's 'group' and GNU's 'id --groups' starts with the effective group ID. This implements a wrapper for `entris::get_groups()` which mimics GNU's behaviour. * add tests for `id` * add tests for `groups` * fix `id --groups --real` to no longer ignore `--real` --- Cargo.toml | 2 +- src/uu/groups/src/groups.rs | 4 +- src/uu/id/src/id.rs | 45 +++++++++-------- src/uucore/src/lib/features/entries.rs | 52 ++++++++++++++++++- tests/by-util/test_groups.rs | 70 +++++++++++++++----------- tests/by-util/test_id.rs | 55 ++++++++++++++++---- 6 files changed, 162 insertions(+), 66 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f89a4077..19ebca511 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -349,7 +349,7 @@ sha1 = { version="0.6", features=["std"] } tempfile = "3.2.0" time = "0.1" unindent = "0.1" -uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] } +uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries", "process"] } walkdir = "2.2" atty = "0.2.14" diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 5b9cd948a..07c25cebb 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -10,7 +10,7 @@ #[macro_use] extern crate uucore; -use uucore::entries::{get_groups, gid2grp, Locate, Passwd}; +use uucore::entries::{get_groups_gnu, gid2grp, Locate, Passwd}; use clap::{crate_version, App, Arg}; @@ -35,7 +35,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { None => { println!( "{}", - get_groups() + get_groups_gnu(None) .unwrap() .iter() .map(|&g| gid2grp(g).unwrap()) diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 77b185f24..4f8f92fe4 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -10,7 +10,7 @@ // http://ftp-archive.freebsd.org/mirror/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/shellutils/src/id.c // http://www.opensource.apple.com/source/shell_cmds/shell_cmds-118/id/id.c -// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag +// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag #![allow(non_camel_case_types)] #![allow(dead_code)] @@ -79,7 +79,7 @@ static OPT_GROUPS: &str = "groups"; static OPT_HUMAN_READABLE: &str = "human-readable"; static OPT_NAME: &str = "name"; static OPT_PASSWORD: &str = "password"; -static OPT_REAL_ID: &str = "real-id"; +static OPT_REAL_ID: &str = "real"; static ARG_USERS: &str = "users"; @@ -135,7 +135,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(OPT_REAL_ID) .short("r") - .help("Display the real ID for the -g and -u options"), + .long(OPT_REAL_ID) + .help( + "Display the real ID for the -G, -g and -u options instead of the effective ID.", + ), ) .arg(Arg::with_name(ARG_USERS).multiple(true).takes_value(true)) .get_matches_from(args); @@ -162,6 +165,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let nflag = matches.is_present(OPT_NAME); let uflag = matches.is_present(OPT_EFFECTIVE_USER); let gflag = matches.is_present(OPT_GROUP); + let gsflag = matches.is_present(OPT_GROUPS); let rflag = matches.is_present(OPT_REAL_ID); if gflag { @@ -194,26 +198,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return 0; } - if matches.is_present(OPT_GROUPS) { + if gsflag { + let id = possible_pw + .map(|p| p.gid()) + .unwrap_or(if rflag { getgid() } else { getegid() }); println!( "{}", - if nflag { - possible_pw - .map(|p| p.belongs_to()) - .unwrap_or_else(|| entries::get_groups().unwrap()) - .iter() - .map(|&id| entries::gid2grp(id).unwrap()) - .collect::>() - .join(" ") - } else { - possible_pw - .map(|p| p.belongs_to()) - .unwrap_or_else(|| entries::get_groups().unwrap()) - .iter() - .map(|&id| id.to_string()) - .collect::>() - .join(" ") - } + possible_pw + .map(|p| p.belongs_to()) + .unwrap_or_else(|| entries::get_groups_gnu(Some(id)).unwrap()) + .iter() + .map(|&id| if nflag { + entries::gid2grp(id).unwrap_or_else(|_| id.to_string()) + } else { + id.to_string() + }) + .collect::>() + .join(" ") ); return 0; } @@ -280,7 +281,7 @@ fn pretty(possible_pw: Option) { println!( "groups\t{}", - entries::get_groups() + entries::get_groups_gnu(None) .unwrap() .iter() .map(|&gr| entries::gid2grp(gr).unwrap()) diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index d2dce2461..b94abbe4f 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -5,7 +5,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (vars) Passwd cstr fnam gecos ngroups +// spell-checker:ignore (vars) Passwd cstr fnam gecos ngroups egid //! Get password/group file entry //! @@ -72,6 +72,41 @@ pub fn get_groups() -> IOResult> { } } +/// The list of group IDs returned from GNU's `groups` and GNU's `id --groups` +/// starts with the effective group ID (egid). +/// This is a wrapper for `get_groups()` to mimic this behavior. +/// +/// If `arg_id` is `None` (default), `get_groups_gnu` moves the effective +/// group id (egid) to the first entry in the returned Vector. +/// If `arg_id` is `Some(x)`, `get_groups_gnu` moves the id with value `x` +/// to the first entry in the returned Vector. This might be necessary +/// for `id --groups --real` if `gid` and `egid` are not equal. +/// +/// From: https://www.man7.org/linux/man-pages/man3/getgroups.3p.html +/// As implied by the definition of supplementary groups, the +/// effective group ID may appear in the array returned by +/// getgroups() or it may be returned only by getegid(). Duplication +/// may exist, but the application needs to call getegid() to be sure +/// of getting all of the information. Various implementation +/// variations and administrative sequences cause the set of groups +/// appearing in the result of getgroups() to vary in order and as to +/// whether the effective group ID is included, even when the set of +/// groups is the same (in the mathematical sense of ``set''). (The +/// history of a process and its parents could affect the details of +/// the result.) +pub fn get_groups_gnu(arg_id: Option) -> IOResult> { + let mut groups = get_groups()?; + let egid = arg_id.unwrap_or_else(crate::features::process::getegid); + if !groups.is_empty() && *groups.first().unwrap() == egid { + return Ok(groups); + } else if let Some(index) = groups.iter().position(|&x| x == egid) { + groups.remove(index); + } + groups.insert(0, egid); + Ok(groups) +} + +#[derive(Copy, Clone)] pub struct Passwd { inner: passwd, } @@ -268,3 +303,18 @@ pub fn usr2uid(name: &str) -> IOResult { pub fn grp2gid(name: &str) -> IOResult { Group::locate(name).map(|p| p.gid()) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_entries_get_groups_gnu() { + if let Ok(mut groups) = get_groups() { + if let Some(last) = groups.pop() { + groups.insert(0, last); + assert_eq!(get_groups_gnu(Some(last)).unwrap(), groups); + } + } + } +} diff --git a/tests/by-util/test_groups.rs b/tests/by-util/test_groups.rs index cee13bdc3..26ab6a75a 100644 --- a/tests/by-util/test_groups.rs +++ b/tests/by-util/test_groups.rs @@ -1,41 +1,53 @@ use crate::common::util::*; #[test] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] fn test_groups() { - let result = new_ucmd!().run(); - println!("result.stdout = {}", result.stdout_str()); - println!("result.stderr = {}", result.stderr_str()); - if is_ci() && result.stdout_str().trim().is_empty() { - // In the CI, some server are failing to return the group. - // As seems to be a configuration issue, ignoring it - return; + if !is_ci() { + new_ucmd!().succeeds().stdout_is(expected_result(&[])); + } else { + // TODO: investigate how this could be tested in CI + // stderr = groups: cannot find name for group ID 116 + println!("test skipped:"); } - result.success(); - assert!(!result.stdout_str().trim().is_empty()); } #[test] -fn test_groups_arg() { - // get the username with the "id -un" command - let result = TestScenario::new("id").ucmd_keepenv().arg("-un").run(); - println!("result.stdout = {}", result.stdout_str()); - println!("result.stderr = {}", result.stderr_str()); - let s1 = String::from(result.stdout_str().trim()); - if is_ci() && s1.parse::().is_ok() { - // In the CI, some server are failing to return id -un. - // So, if we are getting a uid, just skip this test - // As seems to be a configuration issue, ignoring it +#[cfg(any(target_os = "linux"))] +#[ignore = "fixme: 'groups USERNAME' needs more debugging"] +fn test_groups_username() { + let scene = TestScenario::new(util_name!()); + let whoami_result = scene.cmd("whoami").run(); + + let username = if whoami_result.succeeded() { + whoami_result.stdout_move_str() + } else if is_ci() { + String::from("docker") + } else { + println!("test skipped:"); return; - } + }; - println!("result.stdout = {}", result.stdout_str()); - println!("result.stderr = {}", result.stderr_str()); - result.success(); - assert!(!result.stdout_str().is_empty()); - let username = result.stdout_str().trim(); + // TODO: stdout should be in the form: "username : group1 group2 group3" - // call groups with the user name to check that we - // are getting something - new_ucmd!().arg(username).succeeds(); - assert!(!result.stdout_str().is_empty()); + scene + .ucmd() + .arg(&username) + .succeeds() + .stdout_is(expected_result(&[&username])); +} + +#[cfg(any(target_vendor = "apple", target_os = "linux"))] +fn expected_result(args: &[&str]) -> String { + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(target_vendor = "apple")] + let util_name = format!("g{}", util_name!()); + + TestScenario::new(&util_name) + .cmd_keepenv(util_name) + .env("LANGUAGE", "C") + .args(args) + .succeeds() + .stdout_move_str() } diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 1f8249aab..c3a08810a 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -104,19 +104,23 @@ fn test_id_group() { } #[test] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] fn test_id_groups() { let scene = TestScenario::new(util_name!()); - - let result = scene.ucmd().arg("-G").succeeds(); - let groups = result.stdout_str().trim().split_whitespace(); - for s in groups { - assert!(s.parse::().is_ok()); - } - - let result = scene.ucmd().arg("--groups").succeeds(); - let groups = result.stdout_str().trim().split_whitespace(); - for s in groups { - assert!(s.parse::().is_ok()); + for g_flag in &["-G", "--groups"] { + scene + .ucmd() + .arg(g_flag) + .succeeds() + .stdout_is(expected_result(&[g_flag], false)); + for &r_flag in &["-r", "--real"] { + let args = [g_flag, r_flag]; + scene + .ucmd() + .args(&args) + .succeeds() + .stdout_is(expected_result(&args, false)); + } } } @@ -167,3 +171,32 @@ fn test_id_password_style() { assert!(result.stdout_str().starts_with(&username)); } + +#[cfg(any(target_vendor = "apple", target_os = "linux"))] +fn expected_result(args: &[&str], exp_fail: bool) -> String { + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(target_vendor = "apple")] + let util_name = format!("g{}", util_name!()); + + let result = if !exp_fail { + TestScenario::new(&util_name) + .cmd_keepenv(util_name) + .env("LANGUAGE", "C") + .args(args) + .succeeds() + .stdout_move_str() + } else { + TestScenario::new(&util_name) + .cmd_keepenv(util_name) + .env("LANGUAGE", "C") + .args(args) + .fails() + .stderr_move_str() + }; + return if cfg!(target_os = "macos") && result.starts_with("gid") { + result[1..].to_string() + } else { + result + }; +} From d846c403724be80714b090b0b7bad507ddbb06b8 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 8 Jun 2021 21:45:02 +0200 Subject: [PATCH 112/320] groups: fix test for arch --- tests/by-util/test_groups.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/by-util/test_groups.rs b/tests/by-util/test_groups.rs index 26ab6a75a..657503471 100644 --- a/tests/by-util/test_groups.rs +++ b/tests/by-util/test_groups.rs @@ -13,7 +13,7 @@ fn test_groups() { } #[test] -#[cfg(any(target_os = "linux"))] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[ignore = "fixme: 'groups USERNAME' needs more debugging"] fn test_groups_username() { let scene = TestScenario::new(util_name!()); @@ -39,15 +39,13 @@ fn test_groups_username() { #[cfg(any(target_vendor = "apple", target_os = "linux"))] fn expected_result(args: &[&str]) -> String { - #[cfg(target_os = "linux")] - let util_name = util_name!(); - #[cfg(target_vendor = "apple")] - let util_name = format!("g{}", util_name!()); + let util_name = "id"; TestScenario::new(&util_name) .cmd_keepenv(util_name) .env("LANGUAGE", "C") .args(args) + .args(&["-Gn"]) .succeeds() .stdout_move_str() } From 06b6066e89bfe32bcff277ff41e8adfe8cbed1dd Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 8 Jun 2021 22:53:48 +0200 Subject: [PATCH 113/320] groups: enable tests for non-linux unix systems --- tests/by-util/test_groups.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_groups.rs b/tests/by-util/test_groups.rs index 657503471..af0334e5a 100644 --- a/tests/by-util/test_groups.rs +++ b/tests/by-util/test_groups.rs @@ -1,7 +1,7 @@ use crate::common::util::*; #[test] -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] fn test_groups() { if !is_ci() { new_ucmd!().succeeds().stdout_is(expected_result(&[])); @@ -13,7 +13,7 @@ fn test_groups() { } #[test] -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] #[ignore = "fixme: 'groups USERNAME' needs more debugging"] fn test_groups_username() { let scene = TestScenario::new(util_name!()); @@ -37,9 +37,14 @@ fn test_groups_username() { .stdout_is(expected_result(&[&username])); } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] fn expected_result(args: &[&str]) -> String { + // We want to use GNU id. On most linux systems, this is "id", but on + // bsd-like systems (e.g. FreeBSD, MacOS), it is commonly "gid". + #[cfg(any(target_os = "linux"))] let util_name = "id"; + #[cfg(not(target_os = "linux"))] + let util_name = "gid"; TestScenario::new(&util_name) .cmd_keepenv(util_name) From 145e705b746d0bdf777ea180a99de70e7cd5a509 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 9 Jun 2021 12:39:25 +0200 Subject: [PATCH 114/320] groups: fix clippy lint in test --- tests/by-util/test_groups.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_groups.rs b/tests/by-util/test_groups.rs index af0334e5a..c1b98aea1 100644 --- a/tests/by-util/test_groups.rs +++ b/tests/by-util/test_groups.rs @@ -46,7 +46,7 @@ fn expected_result(args: &[&str]) -> String { #[cfg(not(target_os = "linux"))] let util_name = "gid"; - TestScenario::new(&util_name) + TestScenario::new(util_name) .cmd_keepenv(util_name) .env("LANGUAGE", "C") .args(args) From 3818e5441594b1ed439e2b4bf0fc17dae60223e5 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 9 Jun 2021 14:34:37 +0200 Subject: [PATCH 115/320] who: cleanup argument handling --- src/uu/who/src/who.rs | 126 ++++++++++-------------------------------- 1 file changed, 30 insertions(+), 96 deletions(-) diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index d2f64aa94..44f565438 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -179,124 +179,58 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // Ignored for 'who am i'. let short_list = matches.is_present(options::COUNT); - // If true, display only name, line, and time fields. - let mut short_output = false; - - // If true, display the hours:minutes since each user has touched - // the keyboard, or "." if within the last minute, or "old" if - // not within the last day. - let mut include_idle = false; + let all = matches.is_present(options::ALL); // If true, display a line at the top describing each field. let include_heading = matches.is_present(options::HEADING); // If true, display a '+' for each user if mesg y, a '-' if mesg n, // or a '?' if their tty cannot be statted. - let include_mesg = matches.is_present(options::ALL) - || matches.is_present(options::MESG) - || matches.is_present("w"); - - // If true, display process termination & exit status. - let mut include_exit = false; + let include_mesg = all || matches.is_present(options::MESG) || matches.is_present("w"); // If true, display the last boot time. - let mut need_boottime = false; + let need_boottime = all || matches.is_present(options::BOOT); // If true, display dead processes. - let mut need_deadprocs = false; + let need_deadprocs = all || matches.is_present(options::DEAD); // If true, display processes waiting for user login. - let mut need_login = false; + let need_login = all || matches.is_present(options::LOGIN); // If true, display processes started by init. - let mut need_initspawn = false; + let need_initspawn = all || matches.is_present(options::PROCESS); // If true, display the last clock change. - let mut need_clockchange = false; + let need_clockchange = all || matches.is_present(options::TIME); // If true, display the current runlevel. - let mut need_runlevel = false; + let need_runlevel = all || matches.is_present(options::RUNLEVEL); + + let use_defaults = !(all + || need_boottime + || need_deadprocs + || need_login + || need_initspawn + || need_runlevel + || need_clockchange + || matches.is_present(options::USERS)); // If true, display user processes. - let mut need_users = false; + let need_users = all || matches.is_present(options::USERS) || use_defaults; + + // If true, display the hours:minutes since each user has touched + // the keyboard, or "." if within the last minute, or "old" if + // not within the last day. + let include_idle = need_deadprocs || need_login || need_runlevel || need_users; + + // If true, display process termination & exit status. + let include_exit = need_deadprocs; + + // If true, display only name, line, and time fields. + let short_output = !include_exit && use_defaults; // If true, display info only for the controlling tty. - let mut my_line_only = false; - - let mut assumptions = true; - - #[allow(clippy::useless_let_if_seq)] - { - if matches.is_present(options::ALL) { - need_boottime = true; - need_deadprocs = true; - need_login = true; - need_initspawn = true; - need_runlevel = true; - need_clockchange = true; - need_users = true; - include_idle = true; - include_exit = true; - assumptions = false; - } - - if matches.is_present(options::BOOT) { - need_boottime = true; - assumptions = false; - } - - if matches.is_present(options::DEAD) { - need_deadprocs = true; - include_idle = true; - include_exit = true; - assumptions = false; - } - - if matches.is_present(options::LOGIN) { - need_login = true; - include_idle = true; - assumptions = false; - } - - if matches.is_present(options::ONLY_HOSTNAME_USER) || files.len() == 2 { - my_line_only = true; - } - - if matches.is_present(options::PROCESS) { - need_initspawn = true; - assumptions = false; - } - - if matches.is_present(options::RUNLEVEL) { - need_runlevel = true; - include_idle = true; - assumptions = false; - } - - if matches.is_present(options::SHORT) { - short_output = true; - } - - if matches.is_present(options::TIME) { - need_clockchange = true; - assumptions = false; - } - - if matches.is_present(options::USERS) { - need_users = true; - include_idle = true; - assumptions = false; - } - - if assumptions { - need_users = true; - short_output = true; - } - - if include_exit { - short_output = false; - } - } + let my_line_only = matches.is_present(options::ONLY_HOSTNAME_USER) || files.len() == 2; let mut who = Who { do_lookup, From 8ae4a8d06e20221a801d779f5b97d4ac8708bec7 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 9 Jun 2021 14:37:19 +0200 Subject: [PATCH 116/320] Revert "groups: fix test for arch" This reverts commit d846c403724be80714b090b0b7bad507ddbb06b8. --- tests/by-util/test_groups.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_groups.rs b/tests/by-util/test_groups.rs index 657503471..26ab6a75a 100644 --- a/tests/by-util/test_groups.rs +++ b/tests/by-util/test_groups.rs @@ -13,7 +13,7 @@ fn test_groups() { } #[test] -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(any(target_os = "linux"))] #[ignore = "fixme: 'groups USERNAME' needs more debugging"] fn test_groups_username() { let scene = TestScenario::new(util_name!()); @@ -39,13 +39,15 @@ fn test_groups_username() { #[cfg(any(target_vendor = "apple", target_os = "linux"))] fn expected_result(args: &[&str]) -> String { - let util_name = "id"; + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(target_vendor = "apple")] + let util_name = format!("g{}", util_name!()); TestScenario::new(&util_name) .cmd_keepenv(util_name) .env("LANGUAGE", "C") .args(args) - .args(&["-Gn"]) .succeeds() .stdout_move_str() } From 1b824f491460e7f7274ce597224e4d3a86e41cc9 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 9 Jun 2021 15:56:29 +0200 Subject: [PATCH 117/320] fix clippy warnings --- src/uu/head/src/parse.rs | 2 +- src/uu/split/src/split.rs | 2 +- src/uu/tail/src/tail.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 7e36594b5..f6f291814 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -113,7 +113,7 @@ pub fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> { return Err(ParseSizeError::ParseFailure(src.to_string())); } - parse_size(&size_string).map(|n| (n, all_but_last)) + parse_size(size_string).map(|n| (n, all_but_last)) } #[cfg(test)] diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index b905a4592..0d5543d8b 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -278,7 +278,7 @@ struct ByteSplitter { impl ByteSplitter { fn new(settings: &Settings) -> ByteSplitter { let size_string = &settings.strategy_param; - let size_num = match parse_size(&size_string) { + let size_num = match parse_size(size_string) { Ok(n) => n, Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()), }; diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 75cc43db1..8950886a2 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -419,5 +419,5 @@ fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> { return Err(ParseSizeError::ParseFailure(src.to_string())); } - parse_size(&size_string).map(|n| (n, starting_with)) + parse_size(size_string).map(|n| (n, starting_with)) } From f40f9fbf91298f920e0ce5760768c5a083e5db08 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 9 Jun 2021 23:51:04 +0900 Subject: [PATCH 118/320] Fix build when not(feature = "process") --- src/uucore/src/lib/features/entries.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index b94abbe4f..988d88fd3 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -94,6 +94,7 @@ pub fn get_groups() -> IOResult> { /// groups is the same (in the mathematical sense of ``set''). (The /// history of a process and its parents could affect the details of /// the result.) +#[cfg(all(unix, feature = "process"))] pub fn get_groups_gnu(arg_id: Option) -> IOResult> { let mut groups = get_groups()?; let egid = arg_id.unwrap_or_else(crate::features::process::getegid); From 394eb82af1be3aa44abb857e38199c4888fc5074 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 9 Jun 2021 17:07:44 +0200 Subject: [PATCH 119/320] cat/cut/tty/nohup: replace is_std{in, out, err}_interactive with atty --- Cargo.lock | 4 +++ src/uu/cat/Cargo.toml | 1 + src/uu/cat/src/cat.rs | 3 +-- src/uu/cut/Cargo.toml | 1 + src/uu/cut/src/cut.rs | 3 +-- src/uu/nohup/Cargo.toml | 1 + src/uu/nohup/src/nohup.rs | 7 +++-- src/uu/tty/Cargo.toml | 1 + src/uu/tty/src/tty.rs | 3 +-- src/uucore/src/lib/features/fs.rs | 45 ------------------------------- 10 files changed, 14 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b2d989be..931bff981 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1772,6 +1772,7 @@ dependencies = [ name = "uu_cat" version = "0.0.6" dependencies = [ + "atty", "clap", "nix 0.20.0", "thiserror", @@ -1872,6 +1873,7 @@ dependencies = [ name = "uu_cut" version = "0.0.6" dependencies = [ + "atty", "bstr", "clap", "memchr 2.4.0", @@ -2262,6 +2264,7 @@ dependencies = [ name = "uu_nohup" version = "0.0.6" dependencies = [ + "atty", "clap", "libc", "uucore", @@ -2660,6 +2663,7 @@ dependencies = [ name = "uu_tty" version = "0.0.6" dependencies = [ + "atty", "clap", "libc", "uucore", diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index 09b289253..9218e84fe 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -17,6 +17,7 @@ path = "src/cat.rs" [dependencies] clap = "2.33" thiserror = "1.0" +atty = "0.2" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 005802ce5..889ba424a 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -20,7 +20,6 @@ use clap::{crate_version, App, Arg}; use std::fs::{metadata, File}; use std::io::{self, Read, Write}; use thiserror::Error; -use uucore::fs::is_stdin_interactive; /// Linux splice support #[cfg(any(target_os = "linux", target_os = "android"))] @@ -306,7 +305,7 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat #[cfg(any(target_os = "linux", target_os = "android"))] file_descriptor: stdin.as_raw_fd(), reader: stdin, - is_interactive: is_stdin_interactive(), + is_interactive: atty::is(atty::Stream::Stdin), }; return cat_handle(&mut handle, options, state); } diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index c863c1772..47b8223c5 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -20,6 +20,7 @@ uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } memchr = "2" bstr = "0.2" +atty = "0.2" [[bin]] name = "cut" diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 819cbb989..af4a27d8a 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -17,7 +17,6 @@ use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::path::Path; use self::searcher::Searcher; -use uucore::fs::is_stdout_interactive; use uucore::ranges::Range; use uucore::InvalidEncodingHandling; @@ -127,7 +126,7 @@ enum Mode { } fn stdout_writer() -> Box { - if is_stdout_interactive() { + if atty::is(atty::Stream::Stdout) { Box::new(stdout()) } else { Box::new(BufWriter::new(stdout())) as Box diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index 5bbbd9dff..0e47b5cad 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -17,6 +17,7 @@ path = "src/nohup.rs" [dependencies] clap = "2.33" libc = "0.2.42" +atty = "0.2.14" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index ea379ff49..4e6fd7a7e 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -19,7 +19,6 @@ use std::fs::{File, OpenOptions}; use std::io::Error; use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; -use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interactive}; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Run COMMAND ignoring hangup signals."; @@ -84,7 +83,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } fn replace_fds() { - if is_stdin_interactive() { + if atty::is(atty::Stream::Stdin) { let new_stdin = match File::open(Path::new("/dev/null")) { Ok(t) => t, Err(e) => crash!(2, "Cannot replace STDIN: {}", e), @@ -94,7 +93,7 @@ fn replace_fds() { } } - if is_stdout_interactive() { + if atty::is(atty::Stream::Stdout) { let new_stdout = find_stdout(); let fd = new_stdout.as_raw_fd(); @@ -103,7 +102,7 @@ fn replace_fds() { } } - if is_stderr_interactive() && unsafe { dup2(1, 2) } != 2 { + if atty::is(atty::Stream::Stderr) && unsafe { dup2(1, 2) } != 2 { crash!(2, "Cannot replace STDERR: {}", Error::last_os_error()) } } diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index 7be27a900..49b7669df 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -17,6 +17,7 @@ path = "src/tty.rs" [dependencies] clap = "2.33" libc = "0.2.42" +atty = "0.2" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 074bcf182..edcdf091e 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -14,7 +14,6 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::ffi::CStr; -use uucore::fs::is_stdin_interactive; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Print the file name of the terminal connected to standard input."; @@ -67,7 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } - if is_stdin_interactive() { + if atty::is(atty::Stream::Stdin) { libc::EXIT_SUCCESS } else { libc::EXIT_FAILURE diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 38cdbef94..525f305e3 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -225,51 +225,6 @@ pub fn canonicalize>(original: P, can_mode: CanonicalizeMode) -> Ok(result) } -#[cfg(unix)] -pub fn is_stdin_interactive() -> bool { - unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } -} - -#[cfg(windows)] -pub fn is_stdin_interactive() -> bool { - false -} - -#[cfg(target_os = "redox")] -pub fn is_stdin_interactive() -> bool { - termion::is_tty(&io::stdin()) -} - -#[cfg(unix)] -pub fn is_stdout_interactive() -> bool { - unsafe { libc::isatty(libc::STDOUT_FILENO) == 1 } -} - -#[cfg(windows)] -pub fn is_stdout_interactive() -> bool { - false -} - -#[cfg(target_os = "redox")] -pub fn is_stdout_interactive() -> bool { - termion::is_tty(&io::stdout()) -} - -#[cfg(unix)] -pub fn is_stderr_interactive() -> bool { - unsafe { libc::isatty(libc::STDERR_FILENO) == 1 } -} - -#[cfg(windows)] -pub fn is_stderr_interactive() -> bool { - false -} - -#[cfg(target_os = "redox")] -pub fn is_stderr_interactive() -> bool { - termion::is_tty(&io::stderr()) -} - #[cfg(not(unix))] #[allow(unused_variables)] pub fn display_permissions(metadata: &fs::Metadata, display_file_type: bool) -> String { From be8e5f5f30f8f94b1c03ea1125f8273dec7aeb2c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 9 Jun 2021 17:15:42 +0200 Subject: [PATCH 120/320] use the same spec for atty everywhere --- Cargo.toml | 2 +- src/uu/more/Cargo.toml | 2 +- src/uu/nohup/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 19ebca511..804c5f978 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -351,7 +351,7 @@ time = "0.1" unindent = "0.1" uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries", "process"] } walkdir = "2.2" -atty = "0.2.14" +atty = "0.2" [target.'cfg(unix)'.dev-dependencies] rlimit = "0.4.0" diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index b3b97e6dd..af6781876 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -19,7 +19,7 @@ clap = "2.33" uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" } uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" } crossterm = ">=0.19" -atty = "0.2.14" +atty = "0.2" unicode-width = "0.1.7" unicode-segmentation = "1.7.1" diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index 0e47b5cad..839219a84 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -17,7 +17,7 @@ path = "src/nohup.rs" [dependencies] clap = "2.33" libc = "0.2.42" -atty = "0.2.14" +atty = "0.2" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } From 7009cb04869b9abc9ca1e81774ff97a6ed96594c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 01:07:01 +0900 Subject: [PATCH 121/320] Add "process" dependency to groups/Cargo.toml --- src/uu/groups/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index 1a56bc2ab..4a5a537e5 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/groups.rs" [dependencies] -uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "process"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } clap = "2.33" From cebf1f09dff0925b5056bc49c5c43751a168dc30 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 01:54:10 +0900 Subject: [PATCH 122/320] get_groups_gnu sort with rotate_right --- src/uucore/src/lib/features/entries.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index b94abbe4f..75bf245e4 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -95,15 +95,18 @@ pub fn get_groups() -> IOResult> { /// history of a process and its parents could affect the details of /// the result.) pub fn get_groups_gnu(arg_id: Option) -> IOResult> { - let mut groups = get_groups()?; + let groups = get_groups()?; let egid = arg_id.unwrap_or_else(crate::features::process::getegid); - if !groups.is_empty() && *groups.first().unwrap() == egid { - return Ok(groups); - } else if let Some(index) = groups.iter().position(|&x| x == egid) { - groups.remove(index); + Ok(sort_groups(groups, egid)) +} + +fn sort_groups(mut groups: Vec, egid: gid_t) -> Vec { + if let Some(index) = groups.iter().position(|&x| x == egid) { + groups[..index + 1].rotate_right(1); + } else { + groups.insert(0, egid); } - groups.insert(0, egid); - Ok(groups) + groups } #[derive(Copy, Clone)] @@ -308,6 +311,15 @@ pub fn grp2gid(name: &str) -> IOResult { mod test { use super::*; + #[test] + fn test_sort_groups() { + assert_eq!(sort_groups(vec![1, 2, 3], 4), vec![4, 1, 2, 3]); + assert_eq!(sort_groups(vec![1, 2, 3], 3), vec![3, 1, 2]); + assert_eq!(sort_groups(vec![1, 2, 3], 2), vec![2, 1, 3]); + assert_eq!(sort_groups(vec![1, 2, 3], 1), vec![1, 2, 3]); + assert_eq!(sort_groups(vec![1, 2, 3], 0), vec![0, 1, 2, 3]); + } + #[test] fn test_entries_get_groups_gnu() { if let Ok(mut groups) = get_groups() { From 40720dc52de71d93809c1e12c8a6403d60b51662 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 9 Jun 2021 19:39:56 +0200 Subject: [PATCH 123/320] more: rewrite drawing logic --- src/uu/more/src/more.rs | 202 +++++++++++++++++++--------------------- 1 file changed, 98 insertions(+), 104 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 4d345e96b..8dbfb8460 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -11,7 +11,6 @@ extern crate uucore; use std::{ - convert::TryInto, fs::File, io::{stdin, stdout, BufReader, Read, Stdout, Write}, path::Path, @@ -207,32 +206,11 @@ fn reset_term(_: &mut usize) {} fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) { let (cols, rows) = terminal::size().unwrap(); let lines = break_buff(buff, usize::from(cols)); - let line_count: u16 = lines.len().try_into().unwrap(); - let mut upper_mark = 0; - let mut lines_left = line_count.saturating_sub(upper_mark + rows); - - draw( - &mut upper_mark, - rows, - &mut stdout, - lines.clone(), - line_count, - next_file, - ); - - let is_last = next_file.is_none(); - - // Specifies whether we have reached the end of the file and should - // return on the next key press. However, we immediately return when - // this is the last file. - let mut to_be_done = false; - if lines_left == 0 && is_last { - if is_last { - return; - } else { - to_be_done = true; - } + let mut pager = Pager::new(rows as usize, lines, next_file); + pager.draw(stdout); + if pager.should_close() { + return; } loop { @@ -257,59 +235,116 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) { code: KeyCode::Char(' '), modifiers: KeyModifiers::NONE, }) => { - upper_mark = upper_mark.saturating_add(rows.saturating_sub(1)); + pager.page_down(); } Event::Key(KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::NONE, }) => { - upper_mark = upper_mark.saturating_sub(rows.saturating_sub(1)); + pager.page_up(); } _ => continue, } - lines_left = line_count.saturating_sub(upper_mark + rows); - draw( - &mut upper_mark, - rows, - &mut stdout, - lines.clone(), - line_count, - next_file, - ); - if lines_left == 0 { - if to_be_done || is_last { - return; - } - to_be_done = true; + pager.draw(stdout); + if pager.should_close() { + return; } } } } -fn draw( - upper_mark: &mut u16, - rows: u16, - mut stdout: &mut std::io::Stdout, +struct Pager<'a> { + // The current line at the top of the screen + upper_mark: usize, + // The number of rows that fit on the screen + content_rows: usize, lines: Vec, - lc: u16, - next_file: Option<&str>, -) { - execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap(); - let (up_mark, lower_mark) = calc_range(*upper_mark, rows, lc); - // Reduce the row by 1 for the prompt - let displayed_lines = lines - .iter() - .skip(up_mark.into()) - .take(usize::from(rows.saturating_sub(1))); + next_file: Option<&'a str>, + line_count: usize, + close_on_down: bool, +} - for line in displayed_lines { - stdout - .write_all(format!("\r{}\n", line).as_bytes()) - .unwrap(); +impl<'a> Pager<'a> { + fn new(rows: usize, lines: Vec, next_file: Option<&'a str>) -> Self { + let line_count = lines.len(); + Self { + upper_mark: 0, + content_rows: rows - 1, + lines, + next_file, + line_count, + close_on_down: false, + } + } + + fn should_close(&mut self) -> bool { + if self.upper_mark + self.content_rows >= self.line_count { + if self.close_on_down { + return true; + } + if self.next_file.is_none() { + return true; + } else { + self.close_on_down = true; + } + } else { + self.close_on_down = false; + } + false + } + + fn page_down(&mut self) { + self.upper_mark = self + .line_count + .saturating_sub(self.content_rows) + .min(self.upper_mark + self.content_rows); + } + + fn page_up(&mut self) { + self.upper_mark = self.upper_mark.saturating_sub(self.content_rows); + } + + fn draw(&self, stdout: &mut std::io::Stdout) { + let lower_mark = self.line_count.min(self.upper_mark + self.content_rows); + self.draw_lines(stdout); + self.draw_prompt(stdout, lower_mark); + stdout.flush().unwrap(); + } + + fn draw_lines(&self, stdout: &mut std::io::Stdout) { + execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap(); + let displayed_lines = self + .lines + .iter() + .skip(self.upper_mark) + .take(self.content_rows); + + for line in displayed_lines { + stdout + .write_all(format!("\r{}\n", line).as_bytes()) + .unwrap(); + } + } + + fn draw_prompt(&self, stdout: &mut Stdout, lower_mark: usize) { + let status = if lower_mark == self.line_count { + format!("Next file: {}", self.next_file.unwrap_or_default()) + } else { + format!( + "{}%", + (lower_mark as f64 / self.line_count as f64 * 100.0).round() as usize + ) + }; + write!( + stdout, + "\r{}--More--({}){}", + Attribute::Reverse, + status, + Attribute::Reset + ) + .unwrap(); } - make_prompt_and_flush(&mut stdout, lower_mark, lc, next_file); - *upper_mark = up_mark; } // Break the lines on the cols of the terminal @@ -350,52 +385,11 @@ fn break_line(line: &str, cols: usize) -> Vec { lines } -// Calculate upper_mark based on certain parameters -fn calc_range(mut upper_mark: u16, rows: u16, line_count: u16) -> (u16, u16) { - let mut lower_mark = upper_mark.saturating_add(rows); - - if lower_mark >= line_count { - upper_mark = line_count.saturating_sub(rows).saturating_add(1); - lower_mark = line_count; - } else { - lower_mark = lower_mark.saturating_sub(1) - } - (upper_mark, lower_mark) -} - -// Make a prompt similar to original more -fn make_prompt_and_flush(stdout: &mut Stdout, lower_mark: u16, lc: u16, next_file: Option<&str>) { - let status = if lower_mark == lc { - format!("Next file: {}", next_file.unwrap_or_default()) - } else { - format!( - "{}%", - (lower_mark as f64 / lc as f64 * 100.0).round() as u16 - ) - }; - write!( - stdout, - "\r{}--More--({}){}", - Attribute::Reverse, - status, - Attribute::Reset - ) - .unwrap(); - stdout.flush().unwrap(); -} - #[cfg(test)] mod tests { - use super::{break_line, calc_range}; + use super::break_line; use unicode_width::UnicodeWidthStr; - // It is good to test the above functions - #[test] - fn test_calc_range() { - assert_eq!((0, 24), calc_range(0, 25, 100)); - assert_eq!((50, 74), calc_range(50, 25, 100)); - assert_eq!((76, 100), calc_range(85, 25, 100)); - } #[test] fn test_break_lines_long() { let mut test_string = String::with_capacity(100); From e73743eb0d363582f9b9134b0931f1675e58d5d1 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 9 Jun 2021 21:56:32 +0200 Subject: [PATCH 124/320] more: simpler page_down --- src/uu/more/src/more.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 8dbfb8460..271abd994 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -295,10 +295,7 @@ impl<'a> Pager<'a> { } fn page_down(&mut self) { - self.upper_mark = self - .line_count - .saturating_sub(self.content_rows) - .min(self.upper_mark + self.content_rows); + self.upper_mark += self.content_rows; } fn page_up(&mut self) { From 44f6dc6098d6f47fbd054ac170ebeec7fd81535d Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 9 Jun 2021 22:10:28 +0200 Subject: [PATCH 125/320] whoami: remove advapi32-sys dependency --- Cargo.lock | 11 ----------- src/uu/whoami/Cargo.toml | 1 - src/uu/whoami/src/platform/windows.rs | 4 ++-- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b2d989be..9c03e22bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,16 +6,6 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -[[package]] -name = "advapi32-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "aho-corasick" version = "0.7.18" @@ -2751,7 +2741,6 @@ dependencies = [ name = "uu_whoami" version = "0.0.6" dependencies = [ - "advapi32-sys", "clap", "uucore", "uucore_procs", diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index f8dc01440..28670c8b5 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -20,7 +20,6 @@ uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=[" uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(target_os = "windows")'.dependencies] -advapi32-sys = "0.2.0" winapi = { version = "0.3", features = ["lmcons"] } [[bin]] diff --git a/src/uu/whoami/src/platform/windows.rs b/src/uu/whoami/src/platform/windows.rs index 5d648877b..3fe8eb1e7 100644 --- a/src/uu/whoami/src/platform/windows.rs +++ b/src/uu/whoami/src/platform/windows.rs @@ -11,7 +11,7 @@ extern crate winapi; use self::winapi::shared::lmcons; use self::winapi::shared::minwindef; -use self::winapi::um::winnt; +use self::winapi::um::{winbase, winnt}; use std::io::{Error, Result}; use std::mem; use uucore::wide::FromWide; @@ -20,7 +20,7 @@ pub unsafe fn get_username() -> Result { #[allow(deprecated)] let mut buffer: [winnt::WCHAR; lmcons::UNLEN as usize + 1] = mem::uninitialized(); let mut len = buffer.len() as minwindef::DWORD; - if advapi32::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 { + if winbase::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 { return Err(Error::last_os_error()); } let username = String::from_wide(&buffer[..len as usize - 1]); From 026570ff9c887b4c3e2fac56a213ae7e59f59f38 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 9 Jun 2021 13:24:28 +0200 Subject: [PATCH 126/320] id: add more tests for '--zero' * fix clippy warnings --- src/uu/id/src/id.rs | 1 + tests/by-util/test_id.rs | 100 +++++++++++++++++++++++++++++---------- 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 360f6a09c..f0659faf3 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -177,6 +177,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!(1, "cannot print only names or real IDs in default format"); } if (zflag) && !(uflag || gflag || gsflag) { + // GNU testsuite "id/zero.sh" needs this stderr output crash!(1, "option --zero not permitted in default format"); } diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index a9c7e31ae..a885069a1 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -6,8 +6,12 @@ use crate::common::util::*; // // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" // stderr: "whoami: cannot find name for user ID 1001" -// Maybe: "adduser --uid 1001 username" can put things right? -// stderr = id: Could not find uid 1001: No such id: 1001 +// stderr: "id: Could not find uid 1001: No such id: 1001" +// +// However, when running "id" from within "/bin/bash" it looks fine: +// id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)" +// whoami: "runner" +// fn skipping_test_is_okay(result: &CmdResult, needle: &str) -> bool { if !result.succeeded() { println!("result.stdout = {}", result.stdout_str()); @@ -191,53 +195,97 @@ fn test_id_password_style() { #[test] #[cfg(any(target_vendor = "apple", target_os = "linux"))] fn test_id_default_format() { + // These are the same tests like in test_id_zero but without --zero flag. let scene = TestScenario::new(util_name!()); - // -ugG - for flag in &["--name", "--real"] { + for &opt1 in &["--name", "--real"] { + // id: cannot print only names or real IDs in default format + let args = [opt1]; scene .ucmd() - .arg(flag) + .args(&args) .fails() - .stderr_is(expected_result(&[flag], true)); - for &opt in &["--user", "--group", "--groups"] { - if is_ci() && *flag == "--name" { + .stderr_only(expected_result(&args, true)); + for &opt2 in &["--user", "--group", "--groups"] { + // u/g/G n/r + let args = [opt2, opt1]; + let result = scene.ucmd().args(&args).run(); + + if !result.succeeded() && is_ci() + // && (result.stderr_str().contains("cannot find name for") + // || result.stdout_str().contains("cannot find name for")) + { // '--name' does not work on CICD ubuntu-16/ubuntu-18 // id: cannot find name for user ID 1001 // id: cannot find name for group ID 116 - println!("test skipped"); - continue; + scene + .ucmd() + .args(&args) + .fails() + .stderr_only(expected_result(&args, true)); + } else { + result.stdout_only(expected_result(&args, false)); } - let args = [opt, flag]; - scene - .ucmd() - .args(&args) - .succeeds() - .stdout_is(expected_result(&args, false)); } } + // u/g/G + for &opt2 in &["--user", "--group", "--groups"] { + let args = [opt2]; + scene + .ucmd() + .args(&args) + .succeeds() + .stdout_only(expected_result(&args, false)); + } } #[test] #[cfg(any(target_vendor = "apple", target_os = "linux"))] fn test_id_zero() { + let scene = TestScenario::new(util_name!()); for z_flag in &["-z", "--zero"] { - for &opt in &["-n", "--name", "-r", "--real"] { - let args = [opt, z_flag]; - new_ucmd!() + for &opt1 in &["--name", "--real"] { + // id: cannot print only names or real IDs in default format + let args = [opt1, z_flag]; + scene + .ucmd() .args(&args) .fails() - .stderr_is(expected_result(&args, true)); + .stderr_only(expected_result(&args, true)); + for &opt2 in &["--user", "--group", "--groups"] { + // u/g/G n/r z + let args = [opt2, z_flag, opt1]; + let result = scene.ucmd().args(&args).run(); + + if !result.succeeded() && is_ci() + // && (result.stderr_str().contains("cannot find name for") + // || result.stdout_str().contains("cannot find name for")) + { + // '--name' does not work on CICD ubuntu-16/ubuntu-18 + // id: cannot find name for user ID 1001 + // id: cannot find name for group ID 116 + scene + .ucmd() + .args(&args) + .fails() + .stderr_only(expected_result(&args, true)); + } else { + result.stdout_only(expected_result(&args, false)); + } + } } - for &opt in &["-u", "--user", "-g", "--group", "-G", "--groups"] { - let args = [opt, z_flag]; - new_ucmd!() + // u/g/G z + for &opt2 in &["--user", "--group", "--groups"] { + let args = [opt2, z_flag]; + scene + .ucmd() .args(&args) .succeeds() - .stdout_is(expected_result(&args, false)); + .stdout_only(expected_result(&args, false)); } } } +#[allow(clippy::needless_borrow)] #[cfg(any(target_vendor = "apple", target_os = "linux"))] fn expected_result(args: &[&str], exp_fail: bool) -> String { #[cfg(target_os = "linux")] @@ -260,9 +308,9 @@ fn expected_result(args: &[&str], exp_fail: bool) -> String { .fails() .stderr_move_str() }; - return if cfg!(target_os = "macos") && result.starts_with("gid") { + if cfg!(target_os = "macos") && result.starts_with("gid") { result[1..].to_string() } else { result - }; + } } From 6d7d57e13c9f0e3de2a3f00be09d66087b186360 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 9 Jun 2021 22:52:59 +0200 Subject: [PATCH 127/320] remove a legacy declaration to getopt --- Cargo.lock | 1 - src/uu/timeout/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b2d989be..c69e1fde3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2602,7 +2602,6 @@ name = "uu_timeout" version = "0.0.6" dependencies = [ "clap", - "getopts", "libc", "uucore", "uucore_procs", diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index 206a98c08..d16559858 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -16,7 +16,6 @@ path = "src/timeout.rs" [dependencies] clap = "2.33" -getopts = "0.2.18" libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time", "process", "signals"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } From 00c05b8687cca0d8a38dc3ad79e93857490f5799 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 10 Jun 2021 00:19:23 +0200 Subject: [PATCH 128/320] id: add error handling (stderr/exit_code) for '-ugG' --- src/uu/id/src/id.rs | 25 +++++++++++++++++++------ tests/by-util/test_id.rs | 14 ++++++-------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index f0659faf3..570a87790 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -201,6 +201,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; let line_ending = if zflag { '\0' } else { '\n' }; + let mut exit_code = 0; if gflag { let id = possible_pw @@ -209,13 +210,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { print!( "{}{}", if nflag { - entries::gid2grp(id).unwrap_or_else(|_| id.to_string()) + entries::gid2grp(id).unwrap_or_else(|_| { + show_error!("cannot find name for group ID {}", id); + exit_code = 1; + id.to_string() + }) } else { id.to_string() }, line_ending ); - return 0; + return exit_code; } if uflag { @@ -225,13 +230,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { print!( "{}{}", if nflag { - entries::uid2usr(id).unwrap_or_else(|_| id.to_string()) + entries::uid2usr(id).unwrap_or_else(|_| { + show_error!("cannot find name for user ID {}", id); + exit_code = 1; + id.to_string() + }) } else { id.to_string() }, line_ending ); - return 0; + return exit_code; } if gsflag { @@ -246,7 +255,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap_or_else(|| entries::get_groups_gnu(Some(id)).unwrap()) .iter() .map(|&id| if nflag { - entries::gid2grp(id).unwrap_or_else(|_| id.to_string()) + entries::gid2grp(id).unwrap_or_else(|_| { + show_error!("cannot find name for group ID {}", id); + exit_code = 1; + id.to_string() + }) } else { id.to_string() }) @@ -254,7 +267,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .join(delimiter), line_ending ); - return 0; + return exit_code; } if matches.is_present(options::OPT_PASSWORD) { diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index a885069a1..e8e2f8d35 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -209,10 +209,9 @@ fn test_id_default_format() { // u/g/G n/r let args = [opt2, opt1]; let result = scene.ucmd().args(&args).run(); - - if !result.succeeded() && is_ci() - // && (result.stderr_str().contains("cannot find name for") - // || result.stdout_str().contains("cannot find name for")) + if !result.succeeded() + && is_ci() + && result.stderr_str().contains("cannot find name for") { // '--name' does not work on CICD ubuntu-16/ubuntu-18 // id: cannot find name for user ID 1001 @@ -255,10 +254,9 @@ fn test_id_zero() { // u/g/G n/r z let args = [opt2, z_flag, opt1]; let result = scene.ucmd().args(&args).run(); - - if !result.succeeded() && is_ci() - // && (result.stderr_str().contains("cannot find name for") - // || result.stdout_str().contains("cannot find name for")) + if !result.succeeded() + && is_ci() + && result.stderr_str().contains("cannot find name for") { // '--name' does not work on CICD ubuntu-16/ubuntu-18 // id: cannot find name for user ID 1001 From 23f655e2a514ee2730601f7ac184d84763e8e07e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 11:15:01 +0900 Subject: [PATCH 129/320] Use inclusive range Co-authored-by: Michael Debertol --- src/uucore/src/lib/features/entries.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index 75bf245e4..7d8c78a2f 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -102,7 +102,7 @@ pub fn get_groups_gnu(arg_id: Option) -> IOResult> { fn sort_groups(mut groups: Vec, egid: gid_t) -> Vec { if let Some(index) = groups.iter().position(|&x| x == egid) { - groups[..index + 1].rotate_right(1); + groups[..=index].rotate_right(1); } else { groups.insert(0, egid); } From 3eae399ec4e38bf4ea9fb4600a34b792e1076145 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 11:51:04 +0900 Subject: [PATCH 130/320] Remove trivially unnessessary unwrap() from base32/64 --- src/uu/base32/src/base32.rs | 9 +-------- src/uu/base64/src/base64.rs | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index f0e187c31..e6a01cb34 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -38,18 +38,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let config_result: Result = base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); - - if config_result.is_err() { - match config_result { - Ok(_) => panic!(), - Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s), - } - } + let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s)); // Create a reference to stdin so we can return a locked stdin from // parse_base_cmd_args let stdin_raw = stdin(); - let config = config_result.unwrap(); let mut input: Box = base_common::get_input(&config, &stdin_raw); base_common::handle_input( diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 810df4fe8..0dd831027 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -38,18 +38,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let name = executable!(); let config_result: Result = base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); - - if config_result.is_err() { - match config_result { - Ok(_) => panic!(), - Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s), - } - } + let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s)); // Create a reference to stdin so we can return a locked stdin from // parse_base_cmd_args let stdin_raw = stdin(); - let config = config_result.unwrap(); let mut input: Box = base_common::get_input(&config, &stdin_raw); base_common::handle_input( From 774c01f00800be92ec28501731ffd917c8847f42 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 12:29:41 +0900 Subject: [PATCH 131/320] Remove trivially unnessessary unwrap() from du --- src/uu/du/src/du.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 82424ca32..94d90c6cd 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -320,8 +320,7 @@ fn du( if this_stat.is_dir { futures.push(du(this_stat, options, depth + 1, inodes)); } else { - if this_stat.inode.is_some() { - let inode = this_stat.inode.unwrap(); + if let Some(inode) = this_stat.inode { if inodes.contains(&inode) { continue; } @@ -360,7 +359,9 @@ fn du( my_stat.size += stat.size; my_stat.blocks += stat.blocks; } - options.max_depth == None || depth < options.max_depth.unwrap() + options + .max_depth + .map_or(true, |max_depth| depth < max_depth) })); stats.push(my_stat); Box::new(stats.into_iter()) From da9558c6841e7721fcbecb71da9f860e7a07d004 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 12:36:34 +0900 Subject: [PATCH 132/320] Remove trivially unnessessary unwrap() from expr --- src/uu/expr/src/expr.rs | 6 +----- src/uu/expr/src/syntax_tree.rs | 17 ++++------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 329a79ba2..8238917f7 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -56,11 +56,7 @@ fn print_expr_error(expr_error: &str) -> ! { } fn evaluate_ast(maybe_ast: Result, String>) -> Result { - if maybe_ast.is_err() { - Err(maybe_ast.err().unwrap()) - } else { - maybe_ast.ok().unwrap().evaluate() - } + maybe_ast.and_then(|ast| ast.evaluate()) } fn maybe_handle_help_or_version(args: &[String]) -> bool { diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index b72d78729..ba477414e 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -175,23 +175,14 @@ impl AstNode { pub fn tokens_to_ast( maybe_tokens: Result, String>, ) -> Result, String> { - if maybe_tokens.is_err() { - Err(maybe_tokens.err().unwrap()) - } else { - let tokens = maybe_tokens.ok().unwrap(); + maybe_tokens.and_then(|tokens| { let mut out_stack: TokenStack = Vec::new(); let mut op_stack: TokenStack = Vec::new(); for (token_idx, token) in tokens { - if let Err(reason) = - push_token_to_either_stack(token_idx, &token, &mut out_stack, &mut op_stack) - { - return Err(reason); - } - } - if let Err(reason) = move_rest_of_ops_to_out(&mut out_stack, &mut op_stack) { - return Err(reason); + push_token_to_either_stack(token_idx, &token, &mut out_stack, &mut op_stack)?; } + move_rest_of_ops_to_out(&mut out_stack, &mut op_stack)?; assert!(op_stack.is_empty()); maybe_dump_rpn(&out_stack); @@ -205,7 +196,7 @@ pub fn tokens_to_ast( maybe_dump_ast(&result); result } - } + }) } fn maybe_dump_ast(result: &Result, String>) { From 797c4a340e1941ecd276f0241f24bf90f63c7f00 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 13:10:16 +0900 Subject: [PATCH 133/320] Remove trivially unnessessary unwrap() from od --- src/uu/od/src/parse_formats.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/uu/od/src/parse_formats.rs b/src/uu/od/src/parse_formats.rs index abf05ea18..ccac44d72 100644 --- a/src/uu/od/src/parse_formats.rs +++ b/src/uu/od/src/parse_formats.rs @@ -275,17 +275,13 @@ fn parse_type_string(params: &str) -> Result, Strin let mut chars = params.chars(); let mut ch = chars.next(); - while ch.is_some() { - let type_char = ch.unwrap(); - let type_char = match format_type(type_char) { - Some(t) => t, - None => { - return Err(format!( - "unexpected char '{}' in format specification '{}'", - type_char, params - )); - } - }; + while let Some(type_char) = ch { + let type_char = format_type(type_char).ok_or_else(|| { + format!( + "unexpected char '{}' in format specification '{}'", + type_char, params + ) + })?; let type_cat = format_type_category(type_char); From 1ac4eb171ee7976c8de30fcf786e2873ec2b7685 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 12:37:52 +0900 Subject: [PATCH 134/320] move cmode rather than mut --- src/uu/chmod/src/chmod.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 7d171a6f7..2d5787099 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -134,23 +134,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err), }); let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required - let mut cmode = if mode_had_minus_prefix { + let cmode = if mode_had_minus_prefix { // clap parsing is finished, now put prefix back - Some(format!("-{}", modes)) + format!("-{}", modes) } else { - Some(modes.to_string()) + modes.to_string() }; let mut files: Vec = matches .values_of(options::FILE) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - if fmode.is_some() { + let cmode = if fmode.is_some() { // "--reference" and MODE are mutually exclusive // if "--reference" was used MODE needs to be interpreted as another FILE // it wasn't possible to implement this behavior directly with clap - files.push(cmode.unwrap()); - cmode = None; - } + files.push(cmode); + None + } else { + Some(cmode) + }; let chmoder = Chmoder { changes, From ded92dbca0253be2bd5b9cd886bbc5e8ed2bc6d6 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 13:09:35 +0900 Subject: [PATCH 135/320] clean up fold, hashsum, kill --- src/uu/fold/src/fold.rs | 2 +- src/uu/hashsum/src/hashsum.rs | 6 ++---- src/uu/kill/src/kill.rs | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index c49809549..118f7f5f9 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -98,7 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fn handle_obsolete(args: &[String]) -> (Vec, Option) { for (i, arg) in args.iter().enumerate() { let slice = &arg; - if slice.starts_with('-') && slice.len() > 1 && slice.chars().nth(1).unwrap().is_digit(10) { + if slice.starts_with('-') && slice.chars().nth(1).map_or(false, |c| c.is_digit(10)) { let mut v = args.to_vec(); v.remove(i); return (v, Some(slice[1..].to_owned())); diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 1f097e128..a007473ab 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -255,10 +255,8 @@ fn detect_algo<'a>( } } } - if alg.is_none() { - crash!(1, "You must specify hash algorithm!") - }; - (name, alg.unwrap(), output_bits) + let alg = alg.unwrap_or_else(|| crash!(1, "You must specify hash algorithm!")); + (name, alg, output_bits) } } } diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 6c2464c92..a49acaa05 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -111,7 +111,7 @@ fn handle_obsolete(mut args: Vec) -> (Vec, Option) { while i < args.len() { // this is safe because slice is valid when it is referenced let slice = &args[i].clone(); - if slice.starts_with('-') && slice.len() > 1 && slice.chars().nth(1).unwrap().is_digit(10) { + if slice.starts_with('-') && slice.chars().nth(1).map_or(false, |c| c.is_digit(10)) { let val = &slice[1..]; match val.parse() { Ok(num) => { From 9c6750252d2c7a981de5cf6ff527fc54de69b147 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 15:07:07 +0900 Subject: [PATCH 136/320] du winapi dependency only for windows --- src/uu/du/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 023c0a021..dcd1f720e 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -19,6 +19,8 @@ clap = "2.33" chrono = "0.4" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } + +[target.'cfg(target_os = "windows")'.dependencies] winapi = { version="0.3", features=[] } [[bin]] From 44d1790d1fee2e9272ab38d854291a97b359299c Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 10 Jun 2021 10:33:22 +0200 Subject: [PATCH 137/320] id: add more tests for '--zero' --- tests/by-util/test_id.rs | 189 +++++++++------------------------------ tests/common/util.rs | 16 ++++ 2 files changed, 59 insertions(+), 146 deletions(-) diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index e8e2f8d35..bfed78886 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -5,8 +5,9 @@ use crate::common::util::*; // considered okay. If we are not inside the CI this calls assert!(result.success). // // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" -// stderr: "whoami: cannot find name for user ID 1001" -// stderr: "id: Could not find uid 1001: No such id: 1001" +// whoami: cannot find name for user ID 1001 +// id --name: cannot find name for user ID 1001 +// id --name: cannot find name for group ID 116 // // However, when running "id" from within "/bin/bash" it looks fine: // id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)" @@ -94,69 +95,6 @@ fn test_id_name_from_id() { assert_eq!(username_id, username_whoami); } -#[test] -fn test_id_group() { - let scene = TestScenario::new(util_name!()); - - let mut result = scene.ucmd().arg("-g").succeeds(); - let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); - - result = scene.ucmd().arg("--group").succeeds(); - let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); - - #[cfg(any(target_vendor = "apple", target_os = "linux"))] - for flag in &["-g", "--group"] { - new_ucmd!() - .arg(flag) - .succeeds() - .stdout_is(expected_result(&[flag], false)); - } -} - -#[test] -#[cfg(any(target_vendor = "apple", target_os = "linux"))] -fn test_id_groups() { - let scene = TestScenario::new(util_name!()); - for g_flag in &["-G", "--groups"] { - scene - .ucmd() - .arg(g_flag) - .succeeds() - .stdout_is(expected_result(&[g_flag], false)); - for &r_flag in &["-r", "--real"] { - let args = [g_flag, r_flag]; - scene - .ucmd() - .args(&args) - .succeeds() - .stdout_is(expected_result(&args, false)); - } - } -} - -#[test] -fn test_id_user() { - let scene = TestScenario::new(util_name!()); - - let result = scene.ucmd().arg("-u").succeeds(); - let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); - - let result = scene.ucmd().arg("--user").succeeds(); - let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); - - #[cfg(any(target_vendor = "apple", target_os = "linux"))] - for flag in &["-u", "--user"] { - new_ucmd!() - .arg(flag) - .succeeds() - .stdout_is(expected_result(&[flag], false)); - } -} - #[test] fn test_id_pretty_print() { let username = return_whoami_username(); @@ -193,52 +131,13 @@ fn test_id_password_style() { } #[test] -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] fn test_id_default_format() { - // These are the same tests like in test_id_zero but without --zero flag. - let scene = TestScenario::new(util_name!()); - for &opt1 in &["--name", "--real"] { - // id: cannot print only names or real IDs in default format - let args = [opt1]; - scene - .ucmd() - .args(&args) - .fails() - .stderr_only(expected_result(&args, true)); - for &opt2 in &["--user", "--group", "--groups"] { - // u/g/G n/r - let args = [opt2, opt1]; - let result = scene.ucmd().args(&args).run(); - if !result.succeeded() - && is_ci() - && result.stderr_str().contains("cannot find name for") - { - // '--name' does not work on CICD ubuntu-16/ubuntu-18 - // id: cannot find name for user ID 1001 - // id: cannot find name for group ID 116 - scene - .ucmd() - .args(&args) - .fails() - .stderr_only(expected_result(&args, true)); - } else { - result.stdout_only(expected_result(&args, false)); - } - } - } - // u/g/G - for &opt2 in &["--user", "--group", "--groups"] { - let args = [opt2]; - scene - .ucmd() - .args(&args) - .succeeds() - .stdout_only(expected_result(&args, false)); - } + // TODO: These are the same tests like in test_id_zero but without --zero flag. } #[test] -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(unix)] fn test_id_zero() { let scene = TestScenario::new(util_name!()); for z_flag in &["-z", "--zero"] { @@ -249,26 +148,15 @@ fn test_id_zero() { .ucmd() .args(&args) .fails() - .stderr_only(expected_result(&args, true)); + .stderr_only(expected_result(&args).stderr_str()); for &opt2 in &["--user", "--group", "--groups"] { // u/g/G n/r z let args = [opt2, z_flag, opt1]; let result = scene.ucmd().args(&args).run(); - if !result.succeeded() - && is_ci() - && result.stderr_str().contains("cannot find name for") - { - // '--name' does not work on CICD ubuntu-16/ubuntu-18 - // id: cannot find name for user ID 1001 - // id: cannot find name for group ID 116 - scene - .ucmd() - .args(&args) - .fails() - .stderr_only(expected_result(&args, true)); - } else { - result.stdout_only(expected_result(&args, false)); - } + let expected_result = expected_result(&args); + result + .stdout_is_bytes(expected_result.stdout()) + .stderr_is_bytes(expected_result.stderr()); } } // u/g/G z @@ -278,37 +166,46 @@ fn test_id_zero() { .ucmd() .args(&args) .succeeds() - .stdout_only(expected_result(&args, false)); + .stdout_only_bytes(expected_result(&args).stdout()); } } } #[allow(clippy::needless_borrow)] -#[cfg(any(target_vendor = "apple", target_os = "linux"))] -fn expected_result(args: &[&str], exp_fail: bool) -> String { +#[cfg(unix)] +fn expected_result(args: &[&str]) -> CmdResult { #[cfg(target_os = "linux")] let util_name = util_name!(); - #[cfg(target_vendor = "apple")] + #[cfg(all(unix, not(target_os = "linux")))] let util_name = format!("g{}", util_name!()); - let result = if !exp_fail { - TestScenario::new(&util_name) - .cmd_keepenv(util_name) - .env("LANGUAGE", "C") - .args(args) - .succeeds() - .stdout_move_str() - } else { - TestScenario::new(&util_name) - .cmd_keepenv(util_name) - .env("LANGUAGE", "C") - .args(args) - .fails() - .stderr_move_str() - }; - if cfg!(target_os = "macos") && result.starts_with("gid") { - result[1..].to_string() - } else { - result + let result = TestScenario::new(&util_name) + .cmd_keepenv(&util_name) + .env("LANGUAGE", "C") + .args(args) + .run(); + + let mut _o = 0; + let mut _e = 0; + #[cfg(all(unix, not(target_os = "linux")))] + { + _o = if result.stdout_str().starts_with(&util_name) { + 1 + } else { + 0 + }; + _e = if result.stderr_str().starts_with(&util_name) { + 1 + } else { + 0 + }; } + + CmdResult::new( + Some(result.tmpd()), + Some(result.code()), + result.succeeded(), + &result.stdout()[_o..], + &result.stderr()[_e..], + ) } diff --git a/tests/common/util.rs b/tests/common/util.rs index 2f7d7dcc4..64485c0a8 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -69,6 +69,22 @@ pub struct CmdResult { } impl CmdResult { + pub fn new( + tmpd: Option>, + code: Option, + success: bool, + stdout: &[u8], + stderr: &[u8], + ) -> CmdResult { + CmdResult { + tmpd, + code, + success, + stdout: stdout.to_vec(), + stderr: stderr.to_vec(), + } + } + /// Returns a reference to the program's standard output as a slice of bytes pub fn stdout(&self) -> &[u8] { &self.stdout From 357886b5999205317bbfcc1479d40ebf7d9e63de Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 18:05:36 +0900 Subject: [PATCH 138/320] Remove unnessassary chars() and unwrap_or() from tail --- src/uu/tail/src/tail.rs | 44 ++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 15a819d35..7670313a4 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -174,7 +174,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match matches.value_of(options::LINES) { Some(n) => { let mut slice: &str = n; - if slice.chars().next().unwrap_or('_') == '+' { + if slice.as_bytes().first() == Some(&b'+') { settings.beginning = true; slice = &slice[1..]; } @@ -189,7 +189,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { None => { if let Some(n) = matches.value_of(options::BYTES) { let mut slice: &str = n; - if slice.chars().next().unwrap_or('_') == '+' { + if slice.as_bytes().first() == Some(&b'+') { settings.beginning = true; slice = &slice[1..]; } @@ -305,41 +305,35 @@ impl ParseSizeErr { pub type ParseSizeResult = Result; pub fn parse_size(mut size_slice: &str) -> Result { - let mut base = if size_slice.chars().last().unwrap_or('_') == 'B' { + let mut base = if size_slice.as_bytes().last() == Some(&b'B') { size_slice = &size_slice[..size_slice.len() - 1]; 1000u64 } else { 1024u64 }; - let exponent = if !size_slice.is_empty() { - let mut has_suffix = true; - let exp = match size_slice.chars().last().unwrap_or('_') { - 'K' | 'k' => 1u64, - 'M' => 2u64, - 'G' => 3u64, - 'T' => 4u64, - 'P' => 5u64, - 'E' => 6u64, - 'Z' | 'Y' => { + let exponent = match size_slice.as_bytes().last() { + Some(unit) => match unit { + b'K' | b'k' => 1u64, + b'M' => 2u64, + b'G' => 3u64, + b'T' => 4u64, + b'P' => 5u64, + b'E' => 6u64, + b'Z' | b'Y' => { return Err(ParseSizeErr::size_too_big(size_slice)); } - 'b' => { + b'b' => { base = 512u64; 1u64 } - _ => { - has_suffix = false; - 0u64 - } - }; - if has_suffix { - size_slice = &size_slice[..size_slice.len() - 1]; - } - exp - } else { - 0u64 + _ => 0u64, + }, + None => 0u64, }; + if exponent != 0 { + size_slice = &size_slice[..size_slice.len() - 1]; + } let mut multiplier = 1u64; for _ in 0u64..exponent { From 8a03ac6caa4095a14ef1f3d84db67e9dd4bf8b6e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 12:03:25 +0900 Subject: [PATCH 139/320] Prevent double scanning from dircolors --- src/uu/dircolors/src/dircolors.rs | 35 +++++++++++++++++-------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 2fa2e8b91..80c94c047 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -191,24 +191,27 @@ pub trait StrUtils { impl StrUtils for str { fn purify(&self) -> &Self { - let mut line = self; - for (n, c) in self.chars().enumerate() { - if c != '#' { - continue; - } - + let line = if self.as_bytes().first() == Some(&b'#') { // Ignore if '#' is at the beginning of line - if n == 0 { - line = &self[..0]; - break; + &self[..0] + } else { + let mut line = self; + for (n, _) in self + .as_bytes() + .iter() + .enumerate() + .rev() + .filter(|(_, c)| **c == b'#') + { + // Ignore the content after '#' + // only if it is preceded by at least one whitespace + if self[..n].chars().last().unwrap().is_whitespace() { + line = &self[..n]; + break; + } } - - // Ignore the content after '#' - // only if it is preceded by at least one whitespace - if self.chars().nth(n - 1).unwrap().is_whitespace() { - line = &self[..n]; - } - } + line + }; line.trim() } From e3197bea39ca6093126dbdf1d24d834f03e09837 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 18:14:23 +0900 Subject: [PATCH 140/320] dircolor purify forward match '#' --- src/uu/dircolors/src/dircolors.rs | 37 ++++++++++++++++--------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 80c94c047..530f051b3 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -191,27 +191,28 @@ pub trait StrUtils { impl StrUtils for str { fn purify(&self) -> &Self { - let line = if self.as_bytes().first() == Some(&b'#') { - // Ignore if '#' is at the beginning of line - &self[..0] - } else { - let mut line = self; - for (n, _) in self - .as_bytes() - .iter() - .enumerate() - .rev() - .filter(|(_, c)| **c == b'#') - { - // Ignore the content after '#' - // only if it is preceded by at least one whitespace - if self[..n].chars().last().unwrap().is_whitespace() { - line = &self[..n]; + let mut line = self; + for (n, _) in self + .as_bytes() + .iter() + .enumerate() + .filter(|(_, c)| **c == b'#') + { + // Ignore the content after '#' + // only if it is preceded by at least one whitespace + match self[..n].chars().last() { + Some(c) if c.is_whitespace() => { + line = &self[..n - c.len_utf8()]; break; } + None => { + // n == 0 + line = &self[..0]; + break; + } + _ => (), } - line - }; + } line.trim() } From 1fecd98ebe805f778f6fb0f6073613fb21507ba2 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 18:30:19 +0900 Subject: [PATCH 141/320] bytes operation for pathchk --- src/uu/pathchk/src/pathchk.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 07e3a3289..358881509 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -241,13 +241,14 @@ fn no_leading_hyphen(path_segment: &str) -> bool { // check whether a path segment contains only valid (read: portable) characters fn check_portable_chars(path_segment: &str) -> bool { - let valid_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-".to_string(); - for ch in path_segment.chars() { - if !valid_str.contains(ch) { + const VALID_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-"; + for (i, ch) in path_segment.as_bytes().iter().enumerate() { + if !VALID_CHARS.contains(ch) { + let invalid = path_segment[i..].chars().next().unwrap(); writeln!( &mut std::io::stderr(), "nonportable character '{}' in file name component '{}'", - ch, + invalid, path_segment ); return false; From 3ee09fa783026a02c892440b2d380370beeb2403 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 12:29:41 +0900 Subject: [PATCH 142/320] only matches Some() in match --- src/uu/du/src/du.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 94d90c6cd..bf1272a44 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -229,10 +229,9 @@ fn unit_string_to_number(s: &str) -> Option { let mut offset = 0; let mut s_chars = s.chars().rev(); - let (mut ch, multiple) = match s_chars.next() { - Some('B') | Some('b') => ('B', 1000u64), - Some(ch) => (ch, 1024u64), - None => return None, + let (mut ch, multiple) = match s_chars.next()? { + 'B' | 'b' => ('B', 1000u64), + ch => (ch, 1024u64), }; if ch == 'B' { ch = s_chars.next()?; From cc6c2f64b1ce36f02501b8c0fb52ddb66cdf7a46 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 13:09:35 +0900 Subject: [PATCH 143/320] clean up fold, hashsum, kill, nl, printf, truncate --- src/uu/nl/src/nl.rs | 2 +- .../src/tokenize/num_format/formatters/decf.rs | 15 +++------------ .../num_format/formatters/float_common.rs | 13 +++++++------ src/uu/truncate/src/truncate.rs | 5 +---- 4 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index c062eedd9..41750259f 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -247,7 +247,7 @@ fn nl(reader: &mut BufReader, settings: &Settings) { let mut line_filter: fn(&str, ®ex::Regex) -> bool = pass_regex; for mut l in reader.lines().map(|r| r.unwrap()) { // Sanitize the string. We want to print the newline ourselves. - if !l.is_empty() && l.chars().rev().next().unwrap() == '\n' { + if l.chars().last() == Some('\n') { l.pop(); } // Next we iterate through the individual chars to see if this diff --git a/src/uu/printf/src/tokenize/num_format/formatters/decf.rs b/src/uu/printf/src/tokenize/num_format/formatters/decf.rs index 5798eadcb..3376345e0 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/decf.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/decf.rs @@ -55,18 +55,9 @@ impl Formatter for Decf { ); // strip trailing zeroes if let Some(ref post_dec) = f_sci.post_decimal { - let mut i = post_dec.len(); - { - let mut it = post_dec.chars(); - while let Some(c) = it.next_back() { - if c != '0' { - break; - } - i -= 1; - } - } - if i != post_dec.len() { - f_sci.post_decimal = Some(String::from(&post_dec[0..i])); + let trimmed = post_dec.trim_end_matches('0'); + if trimmed.len() != post_dec.len() { + f_sci.post_decimal = Some(trimmed.to_owned()); } } let f_fl = get_primitive_dec( diff --git a/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs b/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs index dfd64296c..97009b586 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs @@ -247,8 +247,12 @@ pub fn get_primitive_dec( first_segment.len() as isize - 1, ) } else { - match first_segment.chars().next() { - Some('0') => { + match first_segment + .chars() + .next() + .expect("float_common: no chars in first segment.") + { + '0' => { let it = second_segment.chars().enumerate(); let mut m: isize = 0; let mut pre = String::from("0"); @@ -266,10 +270,7 @@ pub fn get_primitive_dec( } (pre, post, m) } - Some(_) => (first_segment, second_segment, 0), - None => { - panic!("float_common: no chars in first segment."); - } + _ => (first_segment, second_segment, 0), } } } else { diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 8e785ad21..22c0252f7 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -363,10 +363,7 @@ fn parse_size(size: &str) -> Result { // Get the numeric part of the size argument. For example, if the // argument is "123K", then the numeric part is "123". let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect(); - let number: u64 = match numeric_string.parse() { - Ok(n) => n, - Err(_) => return Err(()), - }; + let number: u64 = numeric_string.parse().map_err(|_| ())?; // Get the alphabetic units part of the size argument and compute // the factor it represents. For example, if the argument is "123K", From b21d189fcfa99af3b78b2e8a3206460d313f75a9 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 14:35:09 +0900 Subject: [PATCH 144/320] Remove trivially unnessessary unwrap() pr --- src/uu/pr/src/pr.rs | 53 +++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index 0761dd09d..d7b95d215 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -401,18 +401,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for file_group in file_groups { let result_options = build_options(&matches, &file_group, args.join(" ")); + let options = match result_options { + Ok(options) => options, + Err(err) => { + print_error(&matches, err); + return 1; + } + }; - if result_options.is_err() { - print_error(&matches, result_options.err().unwrap()); - return 1; - } - - let options = &result_options.unwrap(); - - let cmd_result = if file_group.len() == 1 { - pr(file_group.get(0).unwrap(), options) + let cmd_result = if let Ok(group) = file_group.iter().exactly_one() { + pr(group, &options) } else { - mpr(&file_group, options) + mpr(&file_group, &options) }; let status = match cmd_result { @@ -442,11 +442,12 @@ fn recreate_arguments(args: &[String]) -> Vec { let mut arguments = args.to_owned(); let num_option = args.iter().find_position(|x| n_regex.is_match(x.trim())); if let Some((pos, _value)) = num_option { - let num_val_opt = args.get(pos + 1); - if num_val_opt.is_some() && !num_regex.is_match(num_val_opt.unwrap()) { - let could_be_file = arguments.remove(pos + 1); - arguments.insert(pos + 1, format!("{}", NumberingMode::default().width)); - arguments.insert(pos + 2, could_be_file); + if let Some(num_val_opt) = args.get(pos + 1) { + if !num_regex.is_match(num_val_opt) { + let could_be_file = arguments.remove(pos + 1); + arguments.insert(pos + 1, format!("{}", NumberingMode::default().width)); + arguments.insert(pos + 2, could_be_file); + } } } @@ -666,12 +667,14 @@ fn build_options( None => end_page_in_plus_option, }; - if end_page.is_some() && start_page > end_page.unwrap() { - return Err(PrError::EncounteredErrors(format!( - "invalid --pages argument '{}:{}'", - start_page, - end_page.unwrap() - ))); + if let Some(end_page) = end_page { + if start_page > end_page { + return Err(PrError::EncounteredErrors(format!( + "invalid --pages argument '{}:{}'", + start_page, + end_page + ))); + } } let default_lines_per_page = if form_feed_used { @@ -947,7 +950,7 @@ fn read_stream_and_create_pages( let current_page = x + 1; current_page >= start_page - && (last_page.is_none() || current_page <= last_page.unwrap()) + && last_page.map_or(true, |last_page| current_page <= last_page) }), ) } @@ -1030,8 +1033,7 @@ fn print_page(lines: &[FileLine], options: &OutputOptions, page: usize) -> Resul let lines_written = write_columns(lines, options, out)?; - for index in 0..trailer_content.len() { - let x = trailer_content.get(index).unwrap(); + for (index, x) in trailer_content.iter().enumerate() { out.write_all(x.as_bytes())?; if index + 1 != trailer_content.len() { out.write_all(line_separator)?; @@ -1074,8 +1076,7 @@ fn write_columns( let mut offset = 0; for col in 0..columns { let mut inserted = 0; - for i in offset..lines.len() { - let line = lines.get(i).unwrap(); + for line in &lines[offset..] { if line.file_id != col { break; } From b9611b71ee46b761b2e29e6dec2af3a78b2841b5 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 17:22:05 +0900 Subject: [PATCH 145/320] use ? operator for od --- src/uu/od/src/parse_formats.rs | 48 ++++++++++++++-------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/uu/od/src/parse_formats.rs b/src/uu/od/src/parse_formats.rs index ccac44d72..fca908016 100644 --- a/src/uu/od/src/parse_formats.rs +++ b/src/uu/od/src/parse_formats.rs @@ -297,30 +297,25 @@ fn parse_type_string(params: &str) -> Result, Strin ch = chars.next(); } if !decimal_size.is_empty() { - byte_size = match decimal_size.parse() { - Err(_) => { - return Err(format!( - "invalid number '{}' in format specification '{}'", - decimal_size, params - )) - } - Ok(n) => n, - } + byte_size = decimal_size.parse().map_err(|_| { + format!( + "invalid number '{}' in format specification '{}'", + decimal_size, params + ) + })?; } } if is_format_dump_char(ch, &mut show_ascii_dump) { ch = chars.next(); } - match od_format_type(type_char, byte_size) { - Some(ft) => formats.push(ParsedFormatterItemInfo::new(ft, show_ascii_dump)), - None => { - return Err(format!( - "invalid size '{}' in format specification '{}'", - byte_size, params - )) - } - } + let ft = od_format_type(type_char, byte_size).ok_or_else(|| { + format!( + "invalid size '{}' in format specification '{}'", + byte_size, params + ) + })?; + formats.push(ParsedFormatterItemInfo::new(ft, show_ascii_dump)); } Ok(formats) @@ -331,16 +326,13 @@ pub fn parse_format_flags_str( args_str: &Vec<&'static str>, ) -> Result, String> { let args: Vec = args_str.iter().map(|s| s.to_string()).collect(); - match parse_format_flags(&args) { - Err(e) => Err(e), - Ok(v) => { - // tests using this function assume add_ascii_dump is not set - Ok(v.into_iter() - .inspect(|f| assert!(!f.add_ascii_dump)) - .map(|f| f.formatter_item_info) - .collect()) - } - } + parse_format_flags(&args).map(|v| { + // tests using this function assume add_ascii_dump is not set + v.into_iter() + .inspect(|f| assert!(!f.add_ascii_dump)) + .map(|f| f.formatter_item_info) + .collect() + }) } #[test] From 8433c7726ddadf186fe322aa1345d1c78ecf8573 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 10 Jun 2021 17:41:39 +0900 Subject: [PATCH 146/320] tr parse_sequence reuses chars iterator --- src/uu/tr/src/expand.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uu/tr/src/expand.rs b/src/uu/tr/src/expand.rs index 7d0c61c30..5d960921e 100644 --- a/src/uu/tr/src/expand.rs +++ b/src/uu/tr/src/expand.rs @@ -22,14 +22,15 @@ use std::ops::RangeInclusive; /// character; octal escape sequences consume 1 to 3 octal digits. #[inline] fn parse_sequence(s: &str) -> (char, usize) { - let c = s.chars().next().expect("invalid escape: empty string"); + let mut s = s.chars(); + let c = s.next().expect("invalid escape: empty string"); if ('0'..='7').contains(&c) { let mut v = c.to_digit(8).unwrap(); let mut consumed = 1; let bits_per_digit = 3; - for c in s.chars().skip(1).take(2) { + for c in s.take(2) { match c.to_digit(8) { Some(c) => { v = (v << bits_per_digit) | c; From dc57e1535eb708e2acbbf6b7d60e05147a0a932f Mon Sep 17 00:00:00 2001 From: Dean Li Date: Wed, 9 Jun 2021 22:42:19 +0800 Subject: [PATCH 147/320] more: Implement option '-d' Implement option '-d' (silent mode) Related to #2320 --- src/uu/more/src/more.rs | 53 ++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 4d345e96b..fa21c1e12 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -32,6 +32,8 @@ use crossterm::{ use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; +const BELL: &str = "\x07"; + pub mod options { pub const SILENT: &str = "silent"; pub const LOGICAL: &str = "logical"; @@ -53,14 +55,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = App::new(executable!()) .about("A file perusal filter for CRT viewing.") .version(crate_version!()) - // The commented arguments below are unimplemented: - /* .arg( Arg::with_name(options::SILENT) .short("d") .long(options::SILENT) .help("Display help instead of ringing bell"), ) + // The commented arguments below are unimplemented: + /* .arg( Arg::with_name(options::LOGICAL) .short("f") @@ -140,6 +142,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .get_matches_from(args); let mut buff = String::new(); + let silent = matches.is_present(options::SILENT); if let Some(files) = matches.values_of(options::FILES) { let mut stdout = setup_term(); let length = files.len(); @@ -162,14 +165,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } let mut reader = BufReader::new(File::open(file).unwrap()); reader.read_to_string(&mut buff).unwrap(); - more(&buff, &mut stdout, next_file.copied()); + more(&buff, &mut stdout, next_file.copied(), silent); buff.clear(); } reset_term(&mut stdout); } else if atty::isnt(atty::Stream::Stdin) { stdin().read_to_string(&mut buff).unwrap(); let mut stdout = setup_term(); - more(&buff, &mut stdout, None); + more(&buff, &mut stdout, None, silent); reset_term(&mut stdout); } else { show_usage_error!("bad usage"); @@ -204,13 +207,14 @@ fn reset_term(stdout: &mut std::io::Stdout) { #[inline(always)] fn reset_term(_: &mut usize) {} -fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) { +fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bool) { let (cols, rows) = terminal::size().unwrap(); let lines = break_buff(buff, usize::from(cols)); let line_count: u16 = lines.len().try_into().unwrap(); let mut upper_mark = 0; let mut lines_left = line_count.saturating_sub(upper_mark + rows); + let mut wrong_key = false; draw( &mut upper_mark, @@ -219,6 +223,8 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) { lines.clone(), line_count, next_file, + silent, + wrong_key, ); let is_last = next_file.is_none(); @@ -237,6 +243,7 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) { loop { if event::poll(Duration::from_millis(10)).unwrap() { + wrong_key = false; match event::read().unwrap() { Event::Key(KeyEvent { code: KeyCode::Char('q'), @@ -265,7 +272,9 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) { }) => { upper_mark = upper_mark.saturating_sub(rows.saturating_sub(1)); } - _ => continue, + _ => { + wrong_key = true; + } } lines_left = line_count.saturating_sub(upper_mark + rows); draw( @@ -275,6 +284,8 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) { lines.clone(), line_count, next_file, + silent, + wrong_key, ); if lines_left == 0 { @@ -287,6 +298,7 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) { } } +#[allow(clippy::too_many_arguments)] fn draw( upper_mark: &mut u16, rows: u16, @@ -294,6 +306,8 @@ fn draw( lines: Vec, lc: u16, next_file: Option<&str>, + silent: bool, + wrong_key: bool, ) { execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap(); let (up_mark, lower_mark) = calc_range(*upper_mark, rows, lc); @@ -308,7 +322,7 @@ fn draw( .write_all(format!("\r{}\n", line).as_bytes()) .unwrap(); } - make_prompt_and_flush(&mut stdout, lower_mark, lc, next_file); + make_prompt_and_flush(&mut stdout, lower_mark, lc, next_file, silent, wrong_key); *upper_mark = up_mark; } @@ -364,8 +378,15 @@ fn calc_range(mut upper_mark: u16, rows: u16, line_count: u16) -> (u16, u16) { } // Make a prompt similar to original more -fn make_prompt_and_flush(stdout: &mut Stdout, lower_mark: u16, lc: u16, next_file: Option<&str>) { - let status = if lower_mark == lc { +fn make_prompt_and_flush( + stdout: &mut Stdout, + lower_mark: u16, + lc: u16, + next_file: Option<&str>, + silent: bool, + wrong_key: bool, +) { + let status_inner = if lower_mark == lc { format!("Next file: {}", next_file.unwrap_or_default()) } else { format!( @@ -373,11 +394,21 @@ fn make_prompt_and_flush(stdout: &mut Stdout, lower_mark: u16, lc: u16, next_fil (lower_mark as f64 / lc as f64 * 100.0).round() as u16 ) }; + + let status = format!("--More--({})", status_inner); + + let banner = match (silent, wrong_key) { + (true, true) => "[Press 'h' for instructions. (unimplemented)]".to_string(), + (true, false) => format!("{}[Press space to continue, 'q' to quit.]", status), + (false, true) => format!("{}{}", status, BELL), + (false, false) => status, + }; + write!( stdout, - "\r{}--More--({}){}", + "\r{}{}{}", Attribute::Reverse, - status, + banner, Attribute::Reset ) .unwrap(); From c5d7cbda32f443964559c3e66f1c3360b561af4c Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 10 Jun 2021 16:59:06 +0200 Subject: [PATCH 148/320] timeout: handle arguments for the command to run To prevent clap from parsing flags for the command to run as flags for timeout, remove the "args" positional argument, but allow to pass flags via the "command" positional arg. --- src/uu/timeout/src/timeout.rs | 28 ++++++++++------------------ tests/by-util/test_timeout.rs | 8 ++++++++ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index afe560ee5..ea9a0dc65 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -37,7 +37,6 @@ pub mod options { // Positional args. pub static DURATION: &str = "duration"; pub static COMMAND: &str = "command"; - pub static ARGS: &str = "args"; } struct Config { @@ -47,8 +46,7 @@ struct Config { duration: Duration, preserve_status: bool, - command: String, - command_args: Vec, + command: Vec, } impl Config { @@ -77,12 +75,11 @@ impl Config { let preserve_status: bool = options.is_present(options::PRESERVE_STATUS); let foreground = options.is_present(options::FOREGROUND); - let command: String = options.value_of(options::COMMAND).unwrap().to_string(); - - let command_args: Vec = match options.values_of(options::ARGS) { - Some(values) => values.map(|x| x.to_owned()).collect(), - None => vec![], - }; + let command = options + .values_of(options::COMMAND) + .unwrap() + .map(String::from) + .collect::>(); Config { foreground, @@ -91,7 +88,6 @@ impl Config { duration, preserve_status, command, - command_args, } } } @@ -137,9 +133,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::COMMAND) .index(2) .required(true) - ) - .arg( - Arg::with_name(options::ARGS).multiple(true) + .multiple(true) ) .setting(AppSettings::TrailingVarArg); @@ -148,7 +142,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let config = Config::from(matches); timeout( &config.command, - &config.command_args, config.duration, config.signal, config.kill_after, @@ -160,8 +153,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { /// TODO: Improve exit codes, and make them consistent with the GNU Coreutils exit codes. fn timeout( - cmdname: &str, - args: &[String], + cmd: &[String], duration: Duration, signal: usize, kill_after: Duration, @@ -171,8 +163,8 @@ fn timeout( if !foreground { unsafe { libc::setpgid(0, 0) }; } - let mut process = match Command::new(cmdname) - .args(args) + let mut process = match Command::new(&cmd[0]) + .args(&cmd[1..]) .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index 28273e00f..2346a17aa 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -9,3 +9,11 @@ fn test_subcommand_return_code() { new_ucmd!().arg("1").arg("false").run().status_code(1); } + +#[test] +fn test_command_with_args() { + new_ucmd!() + .args(&["1700", "echo", "-n", "abcd"]) + .succeeds() + .stdout_only("abcd"); +} From ed646090c209fe4376d20439ecf6279b6ee26131 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 10 Jun 2021 17:00:12 +0200 Subject: [PATCH 149/320] timeout: fix usage string --- src/uu/timeout/src/timeout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index ea9a0dc65..3a35c351f 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -23,7 +23,7 @@ use uucore::InvalidEncodingHandling; static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; fn get_usage() -> String { - format!("{0} [OPTION]... [FILE]...", executable!()) + format!("{0} [OPTION] DURATION COMMAND...", executable!()) } const ERR_EXIT_STATUS: i32 = 125; From ceb5a2998c18215bad1684d337da3220cb8201d0 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 10 Jun 2021 18:28:37 +0200 Subject: [PATCH 150/320] core: add EXIT signal EXIT is supported by GNU (see https://github.com/coreutils/gnulib/blob/993ca832d232c33da1d2bb07e91acd6d301ebea0/lib/sig2str.c#L258), so we have to support it too to pass GNU tests. --- src/uucore/src/lib/features/signals.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index d22fa1791..2e6069cb7 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -29,7 +29,11 @@ Linux Programmer's Manual */ #[cfg(target_os = "linux")] -pub static ALL_SIGNALS: [Signal<'static>; 31] = [ +pub static ALL_SIGNALS: [Signal<'static>; 32] = [ + Signal { + name: "EXIT", + value: 0, + }, Signal { name: "HUP", value: 1, @@ -198,7 +202,11 @@ No Name Default Action Description */ #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -pub static ALL_SIGNALS: [Signal<'static>; 31] = [ +pub static ALL_SIGNALS: [Signal<'static>; 32] = [ + Signal { + name: "EXIT", + value: 0, + }, Signal { name: "HUP", value: 1, From b0b937dc3e2a81c95b0d621f726fd9b39673dfa6 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 10 Jun 2021 18:29:06 +0200 Subject: [PATCH 151/320] core: add signal name lookup by value --- src/uucore/src/lib/features/signals.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 2e6069cb7..3c52a9158 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -349,6 +349,13 @@ pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option { .map(|s| s.value) } +pub fn signal_name_by_value(signal_value: usize) -> Option<&'static str> { + ALL_SIGNALS + .iter() + .find(|signal| signal.value == signal_value) + .map(|signal| signal.name) +} + #[inline(always)] pub fn is_signal(num: usize) -> bool { // Named signals start at 1 @@ -358,7 +365,7 @@ pub fn is_signal(num: usize) -> bool { #[test] fn signals_all_contiguous() { for (i, signal) in ALL_SIGNALS.iter().enumerate() { - assert_eq!(signal.value, i + 1); + assert_eq!(signal.value, i); } } @@ -396,3 +403,10 @@ fn signal_by_long_name() { ); } } + +#[test] +fn name() { + for signal in &ALL_SIGNALS { + assert_eq!(signal_name_by_value(signal.value), Some(signal.name)); + } +} From 8e0ed2d20e03b56a7ade4f41ff3e1ab71bcb5e41 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 10 Jun 2021 18:34:05 +0200 Subject: [PATCH 152/320] timeout: support --verbose --- src/uu/timeout/src/timeout.rs | 24 +++++++++++++++++++++++- tests/by-util/test_timeout.rs | 14 ++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 3a35c351f..3c82c4be2 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -17,7 +17,7 @@ use std::io::ErrorKind; use std::process::{Command, Stdio}; use std::time::Duration; use uucore::process::ChildExt; -use uucore::signals::signal_by_name_or_value; +use uucore::signals::{signal_by_name_or_value, signal_name_by_value}; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; @@ -33,6 +33,7 @@ pub mod options { pub static KILL_AFTER: &str = "kill-after"; pub static SIGNAL: &str = "signal"; pub static PRESERVE_STATUS: &str = "preserve-status"; + pub static VERBOSE: &str = "verbose"; // Positional args. pub static DURATION: &str = "duration"; @@ -45,6 +46,7 @@ struct Config { signal: usize, duration: Duration, preserve_status: bool, + verbose: bool, command: Vec, } @@ -74,6 +76,7 @@ impl Config { let preserve_status: bool = options.is_present(options::PRESERVE_STATUS); let foreground = options.is_present(options::FOREGROUND); + let verbose = options.is_present(options::VERBOSE); let command = options .values_of(options::COMMAND) @@ -88,6 +91,7 @@ impl Config { duration, preserve_status, command, + verbose, } } } @@ -124,6 +128,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals") .takes_value(true) ) + .arg( + Arg::with_name(options::VERBOSE) + .short("v") + .long(options::VERBOSE) + .help("diagnose to stderr any signal sent upon timeout") + ) .arg( Arg::with_name(options::DURATION) .index(1) @@ -147,6 +157,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { config.kill_after, config.foreground, config.preserve_status, + config.verbose, ) } @@ -159,6 +170,7 @@ fn timeout( kill_after: Duration, foreground: bool, preserve_status: bool, + verbose: bool, ) -> i32 { if !foreground { unsafe { libc::setpgid(0, 0) }; @@ -185,6 +197,13 @@ fn timeout( match process.wait_or_timeout(duration) { Ok(Some(status)) => status.code().unwrap_or_else(|| status.signal().unwrap()), Ok(None) => { + if verbose { + show_error!( + "sending signal {} to command '{}'", + signal_name_by_value(signal).unwrap(), + cmd[0] + ); + } return_if_err!(ERR_EXIT_STATUS, process.send_signal(signal)); match process.wait_or_timeout(kill_after) { Ok(Some(status)) => { @@ -199,6 +218,9 @@ fn timeout( // XXX: this may not be right return 124; } + if verbose { + show_error!("sending signal KILL to command '{}'", cmd[0]); + } return_if_err!( ERR_EXIT_STATUS, process diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index 2346a17aa..4d2451c7e 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -17,3 +17,17 @@ fn test_command_with_args() { .succeeds() .stdout_only("abcd"); } + +#[test] +fn test_verbose() { + for &verbose_flag in &["-v", "--verbose"] { + new_ucmd!() + .args(&[verbose_flag, ".1", "sleep", "10"]) + .fails() + .stderr_only("timeout: sending signal TERM to command 'sleep'"); + new_ucmd!() + .args(&[verbose_flag, "-s0", "-k.1", ".1", "sleep", "10"]) + .fails() + .stderr_only("timeout: sending signal EXIT to command 'sleep'\ntimeout: sending signal KILL to command 'sleep'"); + } +} From 0f9bc8e9746809e1fe9b59e656e62f395e347610 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 10 Jun 2021 18:51:41 +0200 Subject: [PATCH 153/320] timeout: disable timeout if it is set to zero --- src/uu/timeout/src/timeout.rs | 4 ---- src/uucore/src/lib/features/process.rs | 6 ++++++ tests/by-util/test_timeout.rs | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 3c82c4be2..b671d9d3e 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -214,10 +214,6 @@ fn timeout( } } Ok(None) => { - if kill_after == Duration::new(0, 0) { - // XXX: this may not be right - return 124; - } if verbose { show_error!("sending signal KILL to command '{}'", cmd[0]); } diff --git a/src/uucore/src/lib/features/process.rs b/src/uucore/src/lib/features/process.rs index 975123cf7..cda41bb4f 100644 --- a/src/uucore/src/lib/features/process.rs +++ b/src/uucore/src/lib/features/process.rs @@ -93,6 +93,7 @@ pub trait ChildExt { fn send_signal(&mut self, signal: usize) -> io::Result<()>; /// Wait for a process to finish or return after the specified duration. + /// A `timeout` of zero disables the timeout. fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result>; } @@ -106,6 +107,11 @@ impl ChildExt for Child { } fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result> { + if timeout == Duration::from_micros(0) { + return self + .wait() + .map(|status| Some(ExitStatus::from_std_status(status))); + } // .try_wait() doesn't drop stdin, so we do it manually drop(self.stdin.take()); diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index 4d2451c7e..9be29065a 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -31,3 +31,17 @@ fn test_verbose() { .stderr_only("timeout: sending signal EXIT to command 'sleep'\ntimeout: sending signal KILL to command 'sleep'"); } } + +#[test] +fn test_zero_timeout() { + new_ucmd!() + .args(&["-v", "0", "sleep", ".1"]) + .succeeds() + .no_stderr() + .no_stdout(); + new_ucmd!() + .args(&["-v", "0", "-s0", "-k0", "sleep", ".1"]) + .succeeds() + .no_stderr() + .no_stdout(); +} From b4efd5a749592758bf0d813f6bd2668b813a1e66 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 10 Jun 2021 20:03:33 +0200 Subject: [PATCH 154/320] timeout: disable pre-existing SIGCHLD handlers Needed to make a GNU test pass --- Cargo.lock | 3 +++ src/uu/timeout/Cargo.toml | 1 + src/uu/timeout/src/timeout.rs | 14 +++++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c2c3a6242..6e9c22049 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "Inflector" version = "0.11.4" @@ -2606,6 +2608,7 @@ version = "0.0.6" dependencies = [ "clap", "libc", + "nix 0.20.0", "uucore", "uucore_procs", ] diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index d16559858..a09342c0a 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -17,6 +17,7 @@ path = "src/timeout.rs" [dependencies] clap = "2.33" libc = "0.2.42" +nix = "0.20.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time", "process", "signals"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index b671d9d3e..eabf0192a 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -5,7 +5,7 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid +// spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid sigchld #[macro_use] extern crate uucore; @@ -161,6 +161,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) } +/// Remove pre-existing SIGCHLD handlers that would make waiting for the child's exit code fail. +fn unblock_sigchld() { + unsafe { + nix::sys::signal::signal( + nix::sys::signal::Signal::SIGCHLD, + nix::sys::signal::SigHandler::SigDfl, + ) + .unwrap(); + } +} + /// TODO: Improve exit codes, and make them consistent with the GNU Coreutils exit codes. fn timeout( @@ -194,6 +205,7 @@ fn timeout( } } }; + unblock_sigchld(); match process.wait_or_timeout(duration) { Ok(Some(status)) => status.code().unwrap_or_else(|| status.signal().unwrap()), Ok(None) => { From 0a1dcc27bb4e1ac0c1b4116d5059c81b9c5df884 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 04:29:56 +0900 Subject: [PATCH 155/320] prevent utf8 iteration for ascii str from shred (#2389) --- src/uu/shred/src/shred.rs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 964e68a9e..177143811 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -24,7 +24,7 @@ extern crate uucore; static NAME: &str = "shred"; const BLOCK_SIZE: usize = 512; -const NAME_CHARSET: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_."; +const NAME_CHARSET: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_."; // Patterns as shown in the GNU coreutils shred implementation const PATTERNS: [&[u8]; 22] = [ @@ -89,7 +89,7 @@ impl Iterator for FilenameGenerator { // Make the return value, then increment let mut ret = String::new(); for i in name_charset_indices.iter() { - let c: char = NAME_CHARSET.chars().nth(*i).unwrap(); + let c = char::from(NAME_CHARSET[*i]); ret.push(c); } @@ -163,16 +163,14 @@ impl<'a> BytesGenerator<'a> { return None; } - let this_block_size = { - if !self.exact { + let this_block_size = if !self.exact { + self.block_size + } else { + let bytes_left = self.total_bytes - self.bytes_generated.get(); + if bytes_left >= self.block_size as u64 { self.block_size } else { - let bytes_left = self.total_bytes - self.bytes_generated.get(); - if bytes_left >= self.block_size as u64 { - self.block_size - } else { - (bytes_left % self.block_size as u64) as usize - } + (bytes_left % self.block_size as u64) as usize } }; @@ -184,12 +182,10 @@ impl<'a> BytesGenerator<'a> { rng.fill(bytes); } PassType::Pattern(pattern) => { - let skip = { - if self.bytes_generated.get() == 0 { - 0 - } else { - (pattern.len() as u64 % self.bytes_generated.get()) as usize - } + let skip = if self.bytes_generated.get() == 0 { + 0 + } else { + (pattern.len() as u64 % self.bytes_generated.get()) as usize }; // Copy the pattern in chunks rather than simply one byte at a time From 3347dacfc825fda8e6ce43378f6098847744b259 Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Thu, 10 Jun 2021 22:46:17 +0300 Subject: [PATCH 156/320] chroot: refactor undocumented features (#2365) --- src/uu/chroot/src/chroot.rs | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 8e23d8227..86d4a4900 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -28,6 +28,7 @@ mod options { pub const GROUP: &str = "group"; pub const GROUPS: &str = "groups"; pub const USERSPEC: &str = "userspec"; + pub const COMMAND: &str = "command"; } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -39,7 +40,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .version(crate_version!()) .about(ABOUT) .usage(SYNTAX) - .arg(Arg::with_name(options::NEWROOT).hidden(true).required(true)) + .arg( + Arg::with_name(options::NEWROOT) + .hidden(true) + .required(true) + .index(1), + ) .arg( Arg::with_name(options::USER) .short("u") @@ -71,6 +77,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) .value_name("USER:GROUP"), ) + .arg( + Arg::with_name(options::COMMAND) + .hidden(true) + .multiple(true) + .index(2), + ) .get_matches_from(args); let default_shell: &'static str = "/bin/sh"; @@ -94,7 +106,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ); } - let command: Vec<&str> = match matches.args.len() { + let commands = match matches.values_of(options::COMMAND) { + Some(v) => v.collect(), + None => vec![], + }; + + // TODO: refactor the args and command matching + // See: https://github.com/uutils/coreutils/pull/2365#discussion_r647849967 + let command: Vec<&str> = match commands.len() { 1 => { let shell: &str = match user_shell { Err(_) => default_shell, @@ -102,14 +121,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; vec![shell, default_option] } - _ => { - let mut vector: Vec<&str> = Vec::new(); - for (&k, v) in matches.args.iter() { - vector.push(k); - vector.push(v.vals[0].to_str().unwrap()); - } - vector - } + _ => commands, }; set_context(newroot, &matches); From cff75f242afa013f58f98b1bac32f9f2a59f831e Mon Sep 17 00:00:00 2001 From: Walter Scheper Date: Wed, 19 May 2021 22:38:51 -0400 Subject: [PATCH 157/320] chgrp: replace getopts with clap (#2118) --- Cargo.lock | 1 + src/uu/chgrp/Cargo.toml | 1 + src/uu/chgrp/src/chgrp.rs | 261 +++++++++++++++++++-------- src/uucore/src/lib/features/perms.rs | 2 +- tests/by-util/test_chgrp.rs | 76 +++++++- tests/fixtures/chgrp/file1 | 1 + tests/fixtures/chgrp/file2 | 1 + tests/fixtures/chgrp/file3 | 1 + tests/fixtures/chgrp/ref_file | 1 + 9 files changed, 266 insertions(+), 79 deletions(-) create mode 100644 tests/fixtures/chgrp/file1 create mode 100644 tests/fixtures/chgrp/file2 create mode 100644 tests/fixtures/chgrp/file3 create mode 100644 tests/fixtures/chgrp/ref_file diff --git a/Cargo.lock b/Cargo.lock index 17fa9e2b7..9f4ed26b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1772,6 +1772,7 @@ dependencies = [ name = "uu_chgrp" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", "walkdir", diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 9424ad35e..0e43f7c02 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/chgrp.rs" [dependencies] +clap = "2.33" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } walkdir = "2.2" diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index f6afc2805..c0dc2daf3 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -14,6 +14,8 @@ use uucore::fs::resolve_relative_path; use uucore::libc::gid_t; use uucore::perms::{wrap_chgrp, Verbosity}; +use clap::{App, Arg}; + extern crate walkdir; use walkdir::WalkDir; @@ -24,76 +26,194 @@ use std::os::unix::fs::MetadataExt; use std::path::Path; use uucore::InvalidEncodingHandling; -static SYNTAX: &str = - "chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE..."; -static SUMMARY: &str = "Change the group of each FILE to GROUP."; +static ABOUT: &str = "Change the group of each FILE to GROUP."; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub mod options { + pub mod verbosity { + pub static CHANGES: &str = "changes"; + pub static QUIET: &str = "quiet"; + pub static SILENT: &str = "silent"; + pub static VERBOSE: &str = "verbose"; + } + pub mod preserve_root { + pub static PRESERVE: &str = "preserve-root"; + pub static NO_PRESERVE: &str = "no-preserve-root"; + } + pub mod dereference { + pub static DEREFERENCE: &str = "dereference"; + pub static NO_DEREFERENCE: &str = "no-dereference"; + } + pub static RECURSIVE: &str = "recursive"; + pub mod traverse { + pub static TRAVERSE: &str = "H"; + pub static NO_TRAVERSE: &str = "P"; + pub static EVERY: &str = "L"; + } + pub static REFERENCE: &str = "reference"; + pub static ARG_GROUP: &str = "GROUP"; + pub static ARG_FILES: &str = "FILE"; +} const FTS_COMFOLLOW: u8 = 1; const FTS_PHYSICAL: u8 = 1 << 1; const FTS_LOGICAL: u8 = 1 << 2; +fn get_usage() -> String { + format!( + "{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...", + executable!() + ) +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let mut opts = app!(SYNTAX, SUMMARY, ""); - opts.optflag("c", - "changes", - "like verbose but report only when a change is made") - .optflag("f", "silent", "") - .optflag("", "quiet", "suppress most error messages") - .optflag("v", - "verbose", - "output a diagnostic for every file processed") - .optflag("", "dereference", "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself") - .optflag("h", "no-dereference", "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)") - .optflag("", - "no-preserve-root", - "do not treat '/' specially (the default)") - .optflag("", "preserve-root", "fail to operate recursively on '/'") - .optopt("", - "reference", - "use RFILE's owner and group rather than specifying OWNER:GROUP values", - "RFILE") - .optflag("R", - "recursive", - "operate on files and directories recursively") - .optflag("H", - "", - "if a command line argument is a symbolic link to a directory, traverse it") - .optflag("L", - "", - "traverse every symbolic link to a directory encountered") - .optflag("P", "", "do not traverse any symbolic links (default)"); + let usage = get_usage(); - let mut bit_flag = FTS_PHYSICAL; - let mut preserve_root = false; - let mut derefer = -1; - let flags: &[char] = &['H', 'L', 'P']; - for opt in &args { - match opt.as_str() { - // If more than one is specified, only the final one takes effect. - s if s.contains(flags) => { - if let Some(idx) = s.rfind(flags) { - match s.chars().nth(idx).unwrap() { - 'H' => bit_flag = FTS_COMFOLLOW | FTS_PHYSICAL, - 'L' => bit_flag = FTS_LOGICAL, - 'P' => bit_flag = FTS_PHYSICAL, - _ => (), - } - } - } - "--no-preserve-root" => preserve_root = false, - "--preserve-root" => preserve_root = true, - "--dereference" => derefer = 1, - "--no-dereference" => derefer = 0, - _ => (), + let mut app = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .arg( + Arg::with_name(options::verbosity::CHANGES) + .short("c") + .long(options::verbosity::CHANGES) + .help("like verbose but report only when a change is made"), + ) + .arg( + Arg::with_name(options::verbosity::SILENT) + .short("f") + .long(options::verbosity::SILENT), + ) + .arg( + Arg::with_name(options::verbosity::QUIET) + .long(options::verbosity::QUIET) + .help("suppress most error messages"), + ) + .arg( + Arg::with_name(options::verbosity::VERBOSE) + .short("v") + .long(options::verbosity::VERBOSE) + .help("output a diagnostic for every file processed"), + ) + .arg( + Arg::with_name(options::dereference::DEREFERENCE) + .long(options::dereference::DEREFERENCE), + ) + .arg( + Arg::with_name(options::dereference::NO_DEREFERENCE) + .short("h") + .long(options::dereference::NO_DEREFERENCE) + .help( + "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", + ), + ) + .arg( + Arg::with_name(options::preserve_root::PRESERVE) + .long(options::preserve_root::PRESERVE) + .help("fail to operate recursively on '/'"), + ) + .arg( + Arg::with_name(options::preserve_root::NO_PRESERVE) + .long(options::preserve_root::NO_PRESERVE) + .help("do not treat '/' specially (the default)"), + ) + .arg( + Arg::with_name(options::REFERENCE) + .long(options::REFERENCE) + .value_name("RFILE") + .help("use RFILE's group rather than specifying GROUP values") + .takes_value(true) + .multiple(false), + ) + .arg( + Arg::with_name(options::RECURSIVE) + .short("R") + .long(options::RECURSIVE) + .help("operate on files and directories recursively"), + ) + .arg( + Arg::with_name(options::traverse::TRAVERSE) + .short(options::traverse::TRAVERSE) + .help("if a command line argument is a symbolic link to a directory, traverse it"), + ) + .arg( + Arg::with_name(options::traverse::NO_TRAVERSE) + .short(options::traverse::NO_TRAVERSE) + .help("do not traverse any symbolic links (default)") + .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]), + ) + .arg( + Arg::with_name(options::traverse::EVERY) + .short(options::traverse::EVERY) + .help("traverse every symbolic link to a directory encountered"), + ); + + // we change the positional args based on whether + // --reference was used. + let mut reference = false; + let mut help = false; + // stop processing options on -- + for arg in args.iter().take_while(|s| *s != "--") { + if arg.starts_with("--reference=") || arg == "--reference" { + reference = true; + } else if arg == "--help" { + // we stop processing once we see --help, + // as it doesn't matter if we've seen reference or not + help = true; + break; } } - let matches = opts.parse(args); - let recursive = matches.opt_present("recursive"); + if help || !reference { + // add both positional arguments + app = app.arg( + Arg::with_name(options::ARG_GROUP) + .value_name(options::ARG_GROUP) + .required(true) + .takes_value(true) + .multiple(false), + ) + } + app = app.arg( + Arg::with_name(options::ARG_FILES) + .value_name(options::ARG_FILES) + .multiple(true) + .takes_value(true) + .required(true) + .min_values(1), + ); + + let matches = app.get_matches_from(args); + + /* Get the list of files */ + let files: Vec = matches + .values_of(options::ARG_FILES) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + let preserve_root = matches.is_present(options::preserve_root::PRESERVE); + + let mut derefer = if matches.is_present(options::dereference::DEREFERENCE) { + 1 + } else if matches.is_present(options::dereference::NO_DEREFERENCE) { + 0 + } else { + -1 + }; + + let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) { + FTS_COMFOLLOW | FTS_PHYSICAL + } else if matches.is_present(options::traverse::EVERY) { + FTS_LOGICAL + } else { + FTS_PHYSICAL + }; + + let recursive = matches.is_present(options::RECURSIVE); if recursive { if bit_flag == FTS_PHYSICAL { if derefer == 1 { @@ -106,27 +226,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { bit_flag = FTS_PHYSICAL; } - let verbosity = if matches.opt_present("changes") { + let verbosity = if matches.is_present(options::verbosity::CHANGES) { Verbosity::Changes - } else if matches.opt_present("silent") || matches.opt_present("quiet") { + } else if matches.is_present(options::verbosity::SILENT) + || matches.is_present(options::verbosity::QUIET) + { Verbosity::Silent - } else if matches.opt_present("verbose") { + } else if matches.is_present(options::verbosity::VERBOSE) { Verbosity::Verbose } else { Verbosity::Normal }; - if matches.free.is_empty() { - show_usage_error!("missing operand"); - return 1; - } else if matches.free.len() < 2 && !matches.opt_present("reference") { - show_usage_error!("missing operand after ‘{}’", matches.free[0]); - return 1; - } - - let dest_gid: gid_t; - let mut files; - if let Some(file) = matches.opt_str("reference") { + let dest_gid: u32; + if let Some(file) = matches.value_of(options::REFERENCE) { match fs::metadata(&file) { Ok(meta) => { dest_gid = meta.gid(); @@ -136,19 +249,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return 1; } } - files = matches.free; } else { - match entries::grp2gid(&matches.free[0]) { + let group = matches.value_of(options::ARG_GROUP).unwrap_or_default(); + match entries::grp2gid(&group) { Ok(g) => { dest_gid = g; } _ => { - show_error!("invalid group: {}", matches.free[0].as_str()); + show_error!("invalid group: {}", group); return 1; } } - files = matches.free; - files.remove(0); } let executor = Chgrper { diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index eb6cca102..89c30b53b 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -92,7 +92,7 @@ pub fn wrap_chgrp>( out = format!( "group of '{}' retained as {}", path.display(), - entries::gid2grp(dest_gid).unwrap() + entries::gid2grp(dest_gid).unwrap_or_default() ); } } diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index 45380b80b..d886d674b 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -10,6 +10,33 @@ fn test_invalid_option() { static DIR: &str = "/tmp"; +// we should always get both arguments, regardless of whether --refernce was used +#[test] +fn test_help() { + new_ucmd!() + .arg("--help") + .succeeds() + .stdout_contains("ARGS:\n \n ... "); +} + +#[test] +fn test_help_ref() { + new_ucmd!() + .arg("--help") + .arg("--reference=ref_file") + .succeeds() + .stdout_contains("ARGS:\n \n ... "); +} + +#[test] +fn test_ref_help() { + new_ucmd!() + .arg("--reference=ref_file") + .arg("--help") + .succeeds() + .stdout_contains("ARGS:\n \n ... "); +} + #[test] fn test_invalid_group() { new_ucmd!() @@ -121,9 +148,52 @@ fn test_reference() { fn test_reference() { new_ucmd!() .arg("-v") - .arg("--reference=/etc/passwd") + .arg("--reference=ref_file") .arg("/etc") - .succeeds(); + .fails() + // group name can differ, so just check the first part of the message + .stderr_contains("chgrp: changing group of '/etc': Operation not permitted (os error 1)\nfailed to change group of '/etc' from "); +} + +#[test] +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +fn test_reference_multi_no_equal() { + new_ucmd!() + .arg("-v") + .arg("--reference") + .arg("ref_file") + .arg("file1") + .arg("file2") + .succeeds() + .stderr_contains("chgrp: group of 'file1' retained as ") + .stderr_contains("\nchgrp: group of 'file2' retained as "); +} + +#[test] +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +fn test_reference_last() { + new_ucmd!() + .arg("-v") + .arg("file1") + .arg("file2") + .arg("file3") + .arg("--reference") + .arg("ref_file") + .succeeds() + .stderr_contains("chgrp: group of 'file1' retained as ") + .stderr_contains("\nchgrp: group of 'file2' retained as ") + .stderr_contains("\nchgrp: group of 'file3' retained as "); +} + +#[test] +fn test_missing_files() { + new_ucmd!() + .arg("-v") + .arg("groupname") + .fails() + .stderr_contains( + "error: The following required arguments were not provided:\n ...\n", + ); } #[test] @@ -135,7 +205,7 @@ fn test_big_p() { .arg("bin") .arg("/proc/self/cwd") .fails() - .stderr_is( + .stderr_contains( "chgrp: changing group of '/proc/self/cwd': Operation not permitted (os error 1)\n", ); } diff --git a/tests/fixtures/chgrp/file1 b/tests/fixtures/chgrp/file1 new file mode 100644 index 000000000..73b6f48ab --- /dev/null +++ b/tests/fixtures/chgrp/file1 @@ -0,0 +1 @@ +target file 1 diff --git a/tests/fixtures/chgrp/file2 b/tests/fixtures/chgrp/file2 new file mode 100644 index 000000000..7ecd32965 --- /dev/null +++ b/tests/fixtures/chgrp/file2 @@ -0,0 +1 @@ +target file 2 diff --git a/tests/fixtures/chgrp/file3 b/tests/fixtures/chgrp/file3 new file mode 100644 index 000000000..73d293aba --- /dev/null +++ b/tests/fixtures/chgrp/file3 @@ -0,0 +1 @@ +target file 3 diff --git a/tests/fixtures/chgrp/ref_file b/tests/fixtures/chgrp/ref_file new file mode 100644 index 000000000..aba32d56e --- /dev/null +++ b/tests/fixtures/chgrp/ref_file @@ -0,0 +1 @@ +Reference file From 46981a69f9ea29d631a5182e4f77fc7218b186a2 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 11 Jun 2021 07:33:11 +0200 Subject: [PATCH 158/320] util: fix path (#2396) suggest to clone into the path that `util/run-gnu-test.sh` will be using --- util/build-gnu.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 44ecd2044..798a33456 100644 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -5,12 +5,12 @@ set -e if test ! -d ../gnu; then echo "Could not find ../gnu" - echo "git clone git@github.com:coreutils/coreutils.git ../gnu" + echo "git clone git@github.com:coreutils/coreutils.git gnu" exit 1 fi if test ! -d ../gnulib; then echo "Could not find ../gnulib" - echo "git clone git@github.com:coreutils/gnulib.git ../gnulib" + echo "git clone git@github.com:coreutils/gnulib.git gnulib" exit 1 fi From ae03b09c6de85df72dad1ae3415b67edc0fc91c7 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 10 Jun 2021 11:01:44 +0200 Subject: [PATCH 159/320] tests/util: add CmdResult::new() --- tests/common/util.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/common/util.rs b/tests/common/util.rs index 11425e9b8..922d2ba36 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -69,6 +69,22 @@ pub struct CmdResult { } impl CmdResult { + pub fn new( + tmpd: Option>, + code: Option, + success: bool, + stdout: &[u8], + stderr: &[u8], + ) -> CmdResult { + CmdResult { + tmpd, + code, + success, + stdout: stdout.to_vec(), + stderr: stderr.to_vec(), + } + } + /// Returns a reference to the program's standard output as a slice of bytes pub fn stdout(&self) -> &[u8] { &self.stdout From 0c364e635bf44330e728ef957f72d586d951c5fd Mon Sep 17 00:00:00 2001 From: Syukron Rifail M Date: Sun, 6 Jun 2021 20:34:40 +0700 Subject: [PATCH 160/320] du: add --one-file-system --- src/uu/du/src/du.rs | 24 ++++++++++++++++++------ tests/by-util/test_du.rs | 17 +++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index bf1272a44..a1c2182c2 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -57,6 +57,7 @@ mod options { pub const SI: &str = "si"; pub const TIME: &str = "time"; pub const TIME_STYLE: &str = "time-style"; + pub const ONE_FILE_SYSTEM: &str = "one-file-system"; pub const FILE: &str = "FILE"; } @@ -81,6 +82,7 @@ struct Options { max_depth: Option, total: bool, separate_dirs: bool, + one_file_system: bool, } #[derive(PartialEq, Eq, Hash, Clone, Copy)] @@ -317,6 +319,15 @@ fn du( Ok(entry) => match Stat::new(entry.path()) { Ok(this_stat) => { if this_stat.is_dir { + if options.one_file_system { + if let (Some(this_inode), Some(my_inode)) = + (this_stat.inode, my_stat.inode) + { + if this_inode.dev_id != my_inode.dev_id { + continue; + } + } + } futures.push(du(this_stat, options, depth + 1, inodes)); } else { if let Some(inode) = this_stat.inode { @@ -532,12 +543,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::SI) .help("like -h, but use powers of 1000 not 1024") ) - // .arg( - // Arg::with_name("one-file-system") - // .short("x") - // .long("one-file-system") - // .help("skip directories on different file systems") - // ) + .arg( + Arg::with_name(options::ONE_FILE_SYSTEM) + .short("x") + .long(options::ONE_FILE_SYSTEM) + .help("skip directories on different file systems") + ) // .arg( // Arg::with_name("") // .short("x") @@ -602,6 +613,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { max_depth, total: matches.is_present(options::TOTAL), separate_dirs: matches.is_present(options::SEPARATE_DIRS), + one_file_system: matches.is_present(options::ONE_FILE_SYSTEM), }; let files = match matches.value_of(options::FILE) { diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 3c177c6bf..231451b95 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -312,3 +312,20 @@ fn _du_no_permission(s: &str) { fn _du_no_permission(s: &str) { assert_eq!(s, "4\tsubdir/links\n"); } + +#[test] +fn test_du_one_file_system() { + let scene = TestScenario::new(util_name!()); + + let result = scene.ucmd().arg("-x").arg(SUB_DIR).succeeds(); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg("-x").arg(SUB_DIR).run(); + if result_reference.succeeded() { + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + return; + } + } + _du_basics_subdir(result.stdout_str()); +} From d99385e58520722f32741b3df92c299bd7dd89e3 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 11 Jun 2021 12:00:26 +0200 Subject: [PATCH 161/320] id: add more tests for multiple and/or invalid usernames --- tests/by-util/test_id.rs | 210 ++++++++++++++++++++++----------------- 1 file changed, 120 insertions(+), 90 deletions(-) diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index bfed78886..4823cf6d0 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -1,8 +1,6 @@ use crate::common::util::*; // Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. -// If we are running inside the CI and "needle" is in "stderr" skipping this test is -// considered okay. If we are not inside the CI this calls assert!(result.success). // // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" // whoami: cannot find name for user ID 1001 @@ -13,97 +11,99 @@ use crate::common::util::*; // id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)" // whoami: "runner" // -fn skipping_test_is_okay(result: &CmdResult, needle: &str) -> bool { - if !result.succeeded() { - println!("result.stdout = {}", result.stdout_str()); - println!("result.stderr = {}", result.stderr_str()); - if is_ci() && result.stderr_str().contains(needle) { - println!("test skipped:"); - return true; - } else { - result.success(); - } - } - false -} -fn return_whoami_username() -> String { - let scene = TestScenario::new("whoami"); - let result = scene.cmd("whoami").run(); - if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { - println!("test skipped:"); - return String::from(""); - } - - result.stdout_str().trim().to_string() +fn whoami() -> String { + std::env::var("USER").unwrap_or_else(|e| { + println!("warning: {}, using \"nobody\" instead", e); + "nobody".to_string() + }) } #[test] -fn test_id() { - let scene = TestScenario::new(util_name!()); +#[cfg(unix)] +fn test_id_no_argument() { + let result = new_ucmd!().run(); + let expected_result = expected_result(&[]); + let mut exp_stdout = expected_result.stdout_str().to_string(); - let result = scene.ucmd().arg("-u").succeeds(); - let uid = result.stdout_str().trim(); - - let result = scene.ucmd().run(); - if skipping_test_is_okay(&result, "Could not find uid") { - return; - } - - // Verify that the id found by --user/-u exists in the list - result.stdout_contains(uid); -} - -#[test] -fn test_id_from_name() { - let username = return_whoami_username(); - if username.is_empty() { - return; - } - - let scene = TestScenario::new(util_name!()); - let result = scene.ucmd().arg(&username).run(); - if skipping_test_is_okay(&result, "Could not find uid") { - return; - } - - let uid = result.stdout_str().trim(); - - let result = scene.ucmd().run(); - if skipping_test_is_okay(&result, "Could not find uid") { - return; - } + // uu_stid does not support selinux context. Remove 'context' part from exp_stdout: + let context_offset = expected_result + .stdout_str() + .find(" context") + .unwrap_or(exp_stdout.len()); + exp_stdout.replace_range(context_offset.., "\n"); result - // Verify that the id found by --user/-u exists in the list - .stdout_contains(uid) - // Verify that the username found by whoami exists in the list - .stdout_contains(username); + .stdout_is(exp_stdout) + .stderr_is(expected_result.stderr_str()) + .code_is(expected_result.code()); } #[test] -fn test_id_name_from_id() { - let result = new_ucmd!().arg("-nu").run(); - - let username_id = result.stdout_str().trim(); - - let username_whoami = return_whoami_username(); - if username_whoami.is_empty() { - return; - } - - assert_eq!(username_id, username_whoami); +#[cfg(unix)] +fn test_id_single_user() { + let args = &[&whoami()[..]]; + let result = new_ucmd!().args(args).run(); + let expected_result = expected_result(args); + result + .stdout_is(expected_result.stdout_str()) + .stderr_is(expected_result.stderr_str()) + .code_is(expected_result.code()); } #[test] -fn test_id_pretty_print() { - let username = return_whoami_username(); - if username.is_empty() { - return; - } +#[cfg(unix)] +fn test_id_single_invalid_user() { + let args = &["hopefully_non_existing_username"]; + let result = new_ucmd!().args(args).run(); + let expected_result = expected_result(args); + result + .stdout_is(expected_result.stdout_str()) + .stderr_is(expected_result.stderr_str()) + .code_is(expected_result.code()); +} +#[test] +#[cfg(unix)] +fn test_id_name() { let scene = TestScenario::new(util_name!()); - let result = scene.ucmd().arg("-p").run(); + for &opt in &["--user", "--group", "--groups"] { + let args = [opt, "--name"]; + let result = scene.ucmd().args(&args).run(); + let expected_result = expected_result(&args); + result + .stdout_is(expected_result.stdout_str()) + .stderr_is(expected_result.stderr_str()) + .code_is(expected_result.code()); + + if opt == "--user" { + assert_eq!(result.stdout_str().trim_end(), whoami()); + } + } +} + +#[test] +#[cfg(unix)] +fn test_id_real() { + let scene = TestScenario::new(util_name!()); + for &opt in &["--user", "--group", "--groups"] { + let args = [opt, "--real"]; + let result = scene.ucmd().args(&args).run(); + let expected_result = expected_result(&args); + result + .stdout_is(expected_result.stdout_str()) + .stderr_is(expected_result.stderr_str()) + .code_is(expected_result.code()); + } +} + +#[test] +#[cfg(all(unix, not(target_os = "linux")))] +fn test_id_pretty_print() { + // `-p` is BSD only and not supported on GNU's `id` + let username = whoami(); + + let result = new_ucmd!().arg("-p").run(); if result.stdout_str().trim().is_empty() { // this fails only on: "MinRustV (ubuntu-latest, feat_os_unix)" // `rustc 1.40.0 (73528e339 2019-12-16)` @@ -113,20 +113,17 @@ fn test_id_pretty_print() { // stderr = ', tests/common/util.rs:157:13 println!("test skipped:"); return; + } else { + result.success().stdout_contains(username); } - - result.success().stdout_contains(username); } #[test] +#[cfg(all(unix, not(target_os = "linux")))] fn test_id_password_style() { - let username = return_whoami_username(); - if username.is_empty() { - return; - } - - let result = new_ucmd!().arg("-P").succeeds(); - + // `-P` is BSD only and not supported on GNU's `id` + let username = whoami(); + let result = new_ucmd!().arg("-P").arg(&username).succeeds(); assert!(result.stdout_str().starts_with(&username)); } @@ -136,6 +133,39 @@ fn test_id_default_format() { // TODO: These are the same tests like in test_id_zero but without --zero flag. } +#[test] +#[cfg(unix)] +fn test_id_multiple_users() { + // Same typical users that GNU testsuite is using. + let test_users = ["root", "man", "postfix", "sshd", &whoami()]; + + let result = new_ucmd!().args(&test_users).run(); + let expected_result = expected_result(&test_users); + result + .stdout_is(expected_result.stdout_str()) + .stderr_is(expected_result.stderr_str()); +} + +#[test] +#[cfg(unix)] +fn test_id_multiple_invalid_users() { + let test_users = [ + "root", + "hopefully_non_existing_username1", + "man", + "postfix", + "sshd", + "hopefully_non_existing_username2", + &whoami(), + ]; + + let result = new_ucmd!().args(&test_users).run(); + let expected_result = expected_result(&test_users); + result + .stdout_is(expected_result.stdout_str()) + .stderr_is(expected_result.stderr_str()); +} + #[test] #[cfg(unix)] fn test_id_zero() { @@ -155,8 +185,8 @@ fn test_id_zero() { let result = scene.ucmd().args(&args).run(); let expected_result = expected_result(&args); result - .stdout_is_bytes(expected_result.stdout()) - .stderr_is_bytes(expected_result.stderr()); + .stdout_is(expected_result.stdout_str()) + .stderr_is(expected_result.stderr_str()); } } // u/g/G z @@ -166,7 +196,7 @@ fn test_id_zero() { .ucmd() .args(&args) .succeeds() - .stdout_only_bytes(expected_result(&args).stdout()); + .stdout_only(expected_result(&args).stdout_str()); } } } From c5594bc9bc4783932024128f7d407ae47852c4bb Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:21:32 +0900 Subject: [PATCH 162/320] base32: clean up returning Err --- src/uu/base32/src/base_common.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index ee5fe8675..256b674e2 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -54,15 +54,13 @@ impl Config { None => None, }; - let cols = match options.value_of(options::WRAP) { - Some(num) => match num.parse::() { - Ok(n) => Some(n), - Err(e) => { - return Err(format!("Invalid wrap size: ‘{}’: {}", num, e)); - } - }, - None => None, - }; + let cols = options + .value_of(options::WRAP) + .map(|num| { + num.parse::() + .map_err(|e| format!("Invalid wrap size: ‘{}’: {}", num, e)) + }) + .transpose()?; Ok(Config { decode: options.is_present(options::DECODE), From a197d35039908b28d0cfea453e911b0e542d35fc Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:22:11 +0900 Subject: [PATCH 163/320] chown: clean up returning Err --- src/uu/chown/src/chown.rs | 55 +++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 649300d83..166ad72b8 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -279,36 +279,41 @@ fn parse_spec(spec: &str) -> Result<(Option, Option), String> { let grp_only = args.len() == 2 && args[0].is_empty(); let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty(); - if usr_only { - Ok(( - Some(match Passwd::locate(args[0]) { - Ok(v) => v.uid(), - _ => return Err(format!("invalid user: ‘{}’", spec)), - }), + let r = if usr_only { + ( + Some( + Passwd::locate(args[0]) + .map_err(|_| format!("invalid user: ‘{}’", spec))? + .uid(), + ), None, - )) + ) } else if grp_only { - Ok(( + ( None, - Some(match Group::locate(args[1]) { - Ok(v) => v.gid(), - _ => return Err(format!("invalid group: ‘{}’", spec)), - }), - )) + Some( + Group::locate(args[1]) + .map_err(|_| format!("invalid group: ‘{}’", spec))? + .gid(), + ), + ) } else if usr_grp { - Ok(( - Some(match Passwd::locate(args[0]) { - Ok(v) => v.uid(), - _ => return Err(format!("invalid user: ‘{}’", spec)), - }), - Some(match Group::locate(args[1]) { - Ok(v) => v.gid(), - _ => return Err(format!("invalid group: ‘{}’", spec)), - }), - )) + ( + Some( + Passwd::locate(args[0]) + .map_err(|_| format!("invalid user: ‘{}’", spec))? + .uid(), + ), + Some( + Group::locate(args[1]) + .map_err(|_| format!("invalid group: ‘{}’", spec))? + .gid(), + ), + ) } else { - Ok((None, None)) - } + (None, None) + }; + Ok(r) } enum IfFrom { From 526ed7afdcaa8e5654cbf629d8d94bb28d44fd1a Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:22:21 +0900 Subject: [PATCH 164/320] cksum: clean up returning Err --- src/uu/cksum/src/cksum.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 49c0536f5..6a812c186 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -160,18 +160,14 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> { let mut bytes = init_byte_array(); loop { - match rd.read(&mut bytes) { - Ok(num_bytes) => { - if num_bytes == 0 { - return Ok((crc_final(crc, size), size)); - } - for &b in bytes[..num_bytes].iter() { - crc = crc_update(crc, b); - } - size += num_bytes; - } - Err(err) => return Err(err), + let num_bytes = rd.read(&mut bytes)?; + if num_bytes == 0 { + return Ok((crc_final(crc, size), size)); } + for &b in bytes[..num_bytes].iter() { + crc = crc_update(crc, b); + } + size += num_bytes; } } From 7cc17c15c202b0c987ab70fc07619b4fe55aa8ed Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:22:32 +0900 Subject: [PATCH 165/320] cp: clean up returning Err --- src/uu/cp/src/cp.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index cc0103044..a87e86b98 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -709,27 +709,26 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec { // All path args are sources, and the target dir was // specified separately - (paths, PathBuf::from(target)) + PathBuf::from(target) } None => { // If there was no explicit target-dir, then use the last // path_arg - let target = paths.pop().unwrap(); - (paths, target) + paths.pop().unwrap() } }; if options.strip_trailing_slashes { - for source in sources.iter_mut() { + for source in paths.iter_mut() { *source = source.components().as_path().to_owned() } } - Ok((sources, target)) + Ok((paths, target)) } fn preserve_hardlinks( @@ -1271,15 +1270,15 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes ReflinkMode::Always => unsafe { let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32); if result != 0 { - return Err(format!( + Err(format!( "failed to clone {:?} from {:?}: {}", source, dest, std::io::Error::last_os_error() ) - .into()); + .into()) } else { - return Ok(()); + Ok(()) } }, ReflinkMode::Auto => unsafe { @@ -1287,11 +1286,10 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes if result != 0 { fs::copy(source, dest).context(&*context_for(source, dest))?; } + Ok(()) }, ReflinkMode::Never => unreachable!(), } - - Ok(()) } /// Copies `source` to `dest` using copy-on-write if possible. From 6736faec4a054630f6a0b093cbe7c1fb44c5211c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:22:44 +0900 Subject: [PATCH 166/320] csplit: clean up returning Err --- src/uu/csplit/src/patterns.rs | 16 ++++------------ src/uu/csplit/src/split_name.rs | 14 +++++++------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/uu/csplit/src/patterns.rs b/src/uu/csplit/src/patterns.rs index 5621d18a3..4ab7862ac 100644 --- a/src/uu/csplit/src/patterns.rs +++ b/src/uu/csplit/src/patterns.rs @@ -133,20 +133,12 @@ fn extract_patterns(args: &[String]) -> Result, CsplitError> { Some(m) => m.as_str().parse().unwrap(), }; if let Some(up_to_match) = captures.name("UPTO") { - let pattern = match Regex::new(up_to_match.as_str()) { - Err(_) => { - return Err(CsplitError::InvalidPattern(arg.to_string())); - } - Ok(reg) => reg, - }; + let pattern = Regex::new(up_to_match.as_str()) + .map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?; patterns.push(Pattern::UpToMatch(pattern, offset, execute_ntimes)); } else if let Some(skip_to_match) = captures.name("SKIPTO") { - let pattern = match Regex::new(skip_to_match.as_str()) { - Err(_) => { - return Err(CsplitError::InvalidPattern(arg.to_string())); - } - Ok(reg) => reg, - }; + let pattern = Regex::new(skip_to_match.as_str()) + .map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?; patterns.push(Pattern::SkipToMatch(pattern, offset, execute_ntimes)); } } else if let Ok(line_number) = arg.parse::() { diff --git a/src/uu/csplit/src/split_name.rs b/src/uu/csplit/src/split_name.rs index 6db781e9b..758216414 100644 --- a/src/uu/csplit/src/split_name.rs +++ b/src/uu/csplit/src/split_name.rs @@ -33,13 +33,13 @@ impl SplitName { // get the prefix let prefix = prefix_opt.unwrap_or_else(|| "xx".to_string()); // the width for the split offset - let n_digits = match n_digits_opt { - None => 2, - Some(opt) => match opt.parse::() { - Ok(digits) => digits, - Err(_) => return Err(CsplitError::InvalidNumber(opt)), - }, - }; + let n_digits = n_digits_opt + .map(|opt| { + opt.parse::() + .map_err(|_| CsplitError::InvalidNumber(opt)) + }) + .transpose()? + .unwrap_or(2); // translate the custom format into a function let fn_split_name: Box String> = match format_opt { None => Box::new(move |n: usize| -> String { From f01121f5b7ae678725410724c4e6cef4ebc403a9 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:23:14 +0900 Subject: [PATCH 167/320] env: clean up returning Err --- src/uu/env/src/env.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index e20f047b7..0ea66d7e9 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -82,13 +82,10 @@ fn load_config_file(opts: &mut Options) -> Result<(), i32> { Ini::load_from_file(file) }; - let conf = match conf { - Ok(config) => config, - Err(error) => { - eprintln!("env: error: \"{}\": {}", file, error); - return Err(1); - } - }; + let conf = conf.map_err(|error| { + eprintln!("env: error: \"{}\": {}", file, error); + 1 + })?; for (_, prop) in &conf { // ignore all INI section lines (treat them as comments) @@ -256,13 +253,10 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> { // FIXME: this should just use execvp() (no fork()) on Unix-like systems match Command::new(&*prog).args(args).status() { - Ok(exit) => { - if !exit.success() { - return Err(exit.code().unwrap()); - } - } + Ok(exit) if !exit.success() => return Err(exit.code().unwrap()), Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127), Err(_) => return Err(126), + Ok(_) => (), } } else { // no program provided, so just dump all env vars to stdout From bbae78db67cb15dc229826bcb3107714408528ce Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:23:24 +0900 Subject: [PATCH 168/320] expr: clean up returning Err --- src/uu/expr/src/syntax_tree.rs | 47 +++++++++++++++------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index ba477414e..ff49ea57e 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -160,10 +160,8 @@ impl AstNode { if let AstNode::Node { operands, .. } = self { let mut out = Vec::with_capacity(operands.len()); for operand in operands { - match operand.evaluate() { - Ok(value) => out.push(value), - Err(reason) => return Err(reason), - } + let value = operand.evaluate()?; + out.push(value); } Ok(out) } else { @@ -252,10 +250,8 @@ fn maybe_ast_node( ) -> Result, String> { let mut operands = Vec::with_capacity(arity); for _ in 0..arity { - match ast_from_rpn(rpn) { - Err(reason) => return Err(reason), - Ok(operand) => operands.push(operand), - } + let operand = ast_from_rpn(rpn)?; + operands.push(operand); } operands.reverse(); Ok(AstNode::new_node(token_idx, op_type, operands)) @@ -399,10 +395,12 @@ fn move_till_match_paren( op_stack: &mut TokenStack, ) -> Result<(), String> { loop { - match op_stack.pop() { - None => return Err("syntax error (Mismatched close-parenthesis)".to_string()), - Some((_, Token::ParOpen)) => return Ok(()), - Some(other) => out_stack.push(other), + let op = op_stack + .pop() + .ok_or_else(|| "syntax error (Mismatched close-parenthesis)".to_string())?; + match op { + (_, Token::ParOpen) => return Ok(()), + other => out_stack.push(other), } } } @@ -462,22 +460,17 @@ fn infix_operator_and(values: &[String]) -> String { fn operator_match(values: &[String]) -> Result { assert!(values.len() == 2); - let re = match Regex::with_options(&values[1], RegexOptions::REGEX_OPTION_NONE, Syntax::grep()) - { - Ok(m) => m, - Err(err) => return Err(err.description().to_string()), - }; - if re.captures_len() > 0 { - Ok(match re.captures(&values[0]) { - Some(captures) => captures.at(1).unwrap().to_string(), - None => "".to_string(), - }) + let re = Regex::with_options(&values[1], RegexOptions::REGEX_OPTION_NONE, Syntax::grep()) + .map_err(|err| err.description().to_string())?; + Ok(if re.captures_len() > 0 { + re.captures(&values[0]) + .map(|captures| captures.at(1).unwrap()) + .unwrap_or("") + .to_string() } else { - Ok(match re.find(&values[0]) { - Some((start, end)) => (end - start).to_string(), - None => "0".to_string(), - }) - } + re.find(&values[0]) + .map_or("0".to_string(), |(start, end)| (end - start).to_string()) + }) } fn prefix_operator_length(values: &[String]) -> String { From 27ce4bb0a4773f08fc149df5287efd109bc60ea2 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:23:46 +0900 Subject: [PATCH 169/320] head: clean up returning Err --- src/uu/head/src/head.rs | 16 ++++------------ src/uu/head/src/parse.rs | 32 ++++++++++++++------------------ 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 28710e1fe..c6d2f98bd 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -176,19 +176,11 @@ impl HeadOptions { options.zeroed = matches.is_present(options::ZERO_NAME); let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) { - match parse_mode(v, Modes::Bytes) { - Ok(v) => v, - Err(err) => { - return Err(format!("invalid number of bytes: {}", err)); - } - } + parse_mode(v, Modes::Bytes) + .map_err(|err| format!("invalid number of bytes: {}", err))? } else if let Some(v) = matches.value_of(options::LINES_NAME) { - match parse_mode(v, Modes::Lines) { - Ok(v) => v, - Err(err) => { - return Err(format!("invalid number of lines: {}", err)); - } - } + parse_mode(v, Modes::Lines) + .map_err(|err| format!("invalid number of lines: {}", err))? } else { (Modes::Lines(10), false) }; diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index f1c97561d..c04962f1b 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -94,17 +94,15 @@ pub fn parse_obsolete(src: &str) -> Option /// the bool specifies whether to read from the end pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> { let mut num_start = 0; - let mut chars = src.char_indices(); - let (mut chars, all_but_last) = match chars.next() { - Some((_, c)) => { - if c == '-' { - num_start += 1; - (chars, true) - } else { - (src.char_indices(), false) - } + let (mut chars, all_but_last) = { + let mut chars = src.char_indices(); + let (_, c) = chars.next().ok_or(ParseError::Syntax)?; + if c == '-' { + num_start += 1; + (chars, true) + } else { + (src.char_indices(), false) } - None => return Err(ParseError::Syntax), }; let mut num_end = 0usize; let mut last_char = 0 as char; @@ -120,10 +118,11 @@ pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> { } let num = if num_count > 0 { - match src[num_start..=num_end].parse::() { - Ok(n) => Some(n), - Err(_) => return Err(ParseError::Overflow), - } + Some( + src[num_start..=num_end] + .parse::() + .map_err(|_| ParseError::Overflow)?, + ) } else { None }; @@ -168,10 +167,7 @@ pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> { 'y' => base.pow(8), _ => return Err(ParseError::Syntax), }; - let mul = match usize::try_from(mul) { - Ok(n) => n, - Err(_) => return Err(ParseError::Overflow), - }; + let mul = usize::try_from(mul).map_err(|_| ParseError::Overflow)?; match num.unwrap_or(1).checked_mul(mul) { Some(n) => Ok((n, all_but_last)), None => Err(ParseError::Overflow), From d329c7c864691854a7b3b9e6c233ea1718732e87 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:23:53 +0900 Subject: [PATCH 170/320] install : clean up returning Err --- src/uu/install/src/install.rs | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index bcfe1a396..ad5ea694c 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -299,29 +299,17 @@ fn behavior(matches: &ArgMatches) -> Result { let considering_dir: bool = MainFunction::Directory == main_function; let specified_mode: Option = if matches.is_present(OPT_MODE) { - match matches.value_of(OPT_MODE) { - Some(x) => match mode::parse(x, considering_dir) { - Ok(y) => Some(y), - Err(err) => { - show_error!("Invalid mode string: {}", err); - return Err(1); - } - }, - None => { - return Err(1); - } - } + let x = matches.value_of(OPT_MODE).ok_or(1)?; + Some(mode::parse(x, considering_dir).map_err(|err| { + show_error!("Invalid mode string: {}", err); + 1 + })?) } else { None }; let backup_suffix = if matches.is_present(OPT_SUFFIX) { - match matches.value_of(OPT_SUFFIX) { - Some(x) => x, - None => { - return Err(1); - } - } + matches.value_of(OPT_SUFFIX).ok_or(1)? } else { "~" }; From 3ea18173cb8fbe326504bb92a7855d022e078025 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:24:09 +0900 Subject: [PATCH 171/320] od: clean up returning Err --- src/uu/od/src/od.rs | 48 +++++++++++++--------------------- src/uu/od/src/parse_formats.rs | 18 +++++-------- src/uu/od/src/partialreader.rs | 7 +++-- 3 files changed, 27 insertions(+), 46 deletions(-) diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 1e7b4533a..642618cac 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -128,36 +128,27 @@ impl OdOptions { } }; - let mut skip_bytes = match matches.value_of(options::SKIP_BYTES) { - None => 0, - Some(s) => match parse_number_of_bytes(s) { - Ok(i) => i, - Err(_) => { - return Err(format!("Invalid argument --skip-bytes={}", s)); - } - }, - }; + let mut skip_bytes = matches + .value_of(options::SKIP_BYTES) + .map(|s| { + parse_number_of_bytes(s).map_err(|_| format!("Invalid argument --skip-bytes={}", s)) + }) + .transpose()? + .unwrap_or(0); let mut label: Option = None; - let input_strings = match parse_inputs(&matches) { - Ok(CommandLineInputs::FileNames(v)) => v, - Ok(CommandLineInputs::FileAndOffset((f, s, l))) => { + let parsed_input = parse_inputs(&matches).map_err(|e| format!("Invalid inputs: {}", e))?; + let input_strings = match parsed_input { + CommandLineInputs::FileNames(v) => v, + CommandLineInputs::FileAndOffset((f, s, l)) => { skip_bytes = s; label = l; vec![f] } - Err(e) => { - return Err(format!("Invalid inputs: {}", e)); - } }; - let formats = match parse_format_flags(&args) { - Ok(f) => f, - Err(e) => { - return Err(e); - } - }; + let formats = parse_format_flags(&args)?; let mut line_bytes = match matches.value_of(options::WIDTH) { None => 16, @@ -174,15 +165,12 @@ impl OdOptions { let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES); - let read_bytes = match matches.value_of(options::READ_BYTES) { - None => None, - Some(s) => match parse_number_of_bytes(s) { - Ok(i) => Some(i), - Err(_) => { - return Err(format!("Invalid argument --read-bytes={}", s)); - } - }, - }; + let read_bytes = matches + .value_of(options::READ_BYTES) + .map(|s| { + parse_number_of_bytes(s).map_err(|_| format!("Invalid argument --read-bytes={}", s)) + }) + .transpose()?; let radix = match matches.value_of(options::ADDRESS_RADIX) { None => Radix::Octal, diff --git a/src/uu/od/src/parse_formats.rs b/src/uu/od/src/parse_formats.rs index fca908016..f5b150d61 100644 --- a/src/uu/od/src/parse_formats.rs +++ b/src/uu/od/src/parse_formats.rs @@ -108,10 +108,8 @@ pub fn parse_format_flags(args: &[String]) -> Result formats.extend(v.into_iter()), - Err(e) => return Err(e), - } + let v = parse_type_string(arg)?; + formats.extend(v.into_iter()); expect_type_string = false; } else if arg.starts_with("--") { if arg.len() == 2 { @@ -119,10 +117,8 @@ pub fn parse_format_flags(args: &[String]) -> Result Result formats.extend(v.into_iter()), - Err(e) => return Err(e), - } + let v = parse_type_string(&format_spec)?; + formats.extend(v.into_iter()); expect_type_string = false; } } diff --git a/src/uu/od/src/partialreader.rs b/src/uu/od/src/partialreader.rs index ee3588830..f155a7bd2 100644 --- a/src/uu/od/src/partialreader.rs +++ b/src/uu/od/src/partialreader.rs @@ -36,16 +36,15 @@ impl Read for PartialReader { while self.skip > 0 { let skip_count = cmp::min(self.skip, MAX_SKIP_BUFFER); - match self.inner.read(&mut bytes[..skip_count]) { - Ok(0) => { + match self.inner.read(&mut bytes[..skip_count])? { + 0 => { // this is an error as we still have more to skip return Err(io::Error::new( io::ErrorKind::UnexpectedEof, "tried to skip past end of input", )); } - Ok(n) => self.skip -= n, - Err(e) => return Err(e), + n => self.skip -= n, } } } From e45f5404db398ff6beedbb2c7ef297ec13ef9a2b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:24:17 +0900 Subject: [PATCH 172/320] nl: fix clippy error --- src/uu/nl/src/nl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 41750259f..a3181e11f 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -247,7 +247,7 @@ fn nl(reader: &mut BufReader, settings: &Settings) { let mut line_filter: fn(&str, ®ex::Regex) -> bool = pass_regex; for mut l in reader.lines().map(|r| r.unwrap()) { // Sanitize the string. We want to print the newline ourselves. - if l.chars().last() == Some('\n') { + if l.ends_with('\n') { l.pop(); } // Next we iterate through the individual chars to see if this From 7cc4bf6e369c972e27454a0d92450a539e5574cb Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:24:52 +0900 Subject: [PATCH 173/320] pr: clean up returning Err --- src/uu/pr/src/pr.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index d7b95d215..239a0970f 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -671,8 +671,7 @@ fn build_options( if start_page > end_page { return Err(PrError::EncounteredErrors(format!( "invalid --pages argument '{}:{}'", - start_page, - end_page + start_page, end_page ))); } } @@ -999,8 +998,8 @@ fn mpr(paths: &[String], options: &OutputOptions) -> Result { for (_key, file_line_group) in file_line_groups.into_iter() { for file_line in file_line_group { - if file_line.line_content.is_err() { - return Err(file_line.line_content.unwrap_err().into()); + if let Err(e) = file_line.line_content { + return Err(e.into()); } let new_page_number = file_line.page_number; if page_counter != new_page_number { From 2dd9822d5709b0635ce0de836663e1d302c7ef17 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:25:17 +0900 Subject: [PATCH 174/320] rmdir: clean up returning Err --- src/uu/rmdir/src/rmdir.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 05cc66d51..fc22cca09 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -109,17 +109,14 @@ fn remove(dirs: Vec, ignore: bool, parents: bool, verbose: bool) -> Resu } fn remove_dir(path: &Path, ignore: bool, verbose: bool) -> Result<(), i32> { - let mut read_dir = match fs::read_dir(path) { - Ok(m) => m, - Err(e) if e.raw_os_error() == Some(ENOTDIR) => { + let mut read_dir = fs::read_dir(path).map_err(|e| { + if e.raw_os_error() == Some(ENOTDIR) { show_error!("failed to remove '{}': Not a directory", path.display()); - return Err(1); - } - Err(e) => { + } else { show_error!("reading directory '{}': {}", path.display(), e); - return Err(1); } - }; + 1 + })?; let mut r = Ok(()); From 9c56a40bcbff23c3d673784a0d4072f5c84eb058 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:25:30 +0900 Subject: [PATCH 175/320] shuf: clean up returning Err --- src/uu/shuf/src/shuf.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 88a47585f..2d1f558de 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -285,14 +285,12 @@ fn parse_range(input_range: &str) -> Result<(usize, usize), String> { if split.len() != 2 { Err(format!("invalid input range: '{}'", input_range)) } else { - let begin = match split[0].parse::() { - Ok(m) => m, - Err(_) => return Err(format!("invalid input range: '{}'", split[0])), - }; - let end = match split[1].parse::() { - Ok(m) => m, - Err(_) => return Err(format!("invalid input range: '{}'", split[1])), - }; + let begin = split[0] + .parse::() + .map_err(|_| format!("invalid input range: '{}'", split[0]))?; + let end = split[1] + .parse::() + .map_err(|_| format!("invalid input range: '{}'", split[1]))?; Ok((begin, end + 1)) } } From b59c1dae59ffdb60c37dc6abcc62e72665e8280e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:25:40 +0900 Subject: [PATCH 176/320] stdbuf: clean up returning Err --- src/uu/stdbuf/src/stdbuf.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 5baff4825..a69e0c2c5 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -152,10 +152,8 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result { - let size = match parse_size(x) { - Some(m) => m, - None => return Err(ProgramOptionsError(format!("invalid mode {}", x))), - }; + let size = parse_size(x) + .ok_or_else(|| ProgramOptionsError(format!("invalid mode {}", x)))?; Ok(BufferType::Size(size)) } }, From fb67e54e20543b3365879333a5e7dc348fd49610 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:26:22 +0900 Subject: [PATCH 177/320] uucore: clean up returning Err --- src/uucore/src/lib/features/fs.rs | 35 ++++++++--------------- src/uucore/src/lib/features/mode.rs | 26 ++++++++--------- src/uucore/src/lib/features/parse_time.rs | 14 ++++----- src/uucore/src/lib/mods/ranges.rs | 7 ++--- 4 files changed, 34 insertions(+), 48 deletions(-) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 525f305e3..36bdbfed0 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -113,22 +113,14 @@ fn resolve>(original: P) -> IOResult { )); } - match fs::symlink_metadata(&result) { - Err(e) => return Err(e), - Ok(ref m) if !m.file_type().is_symlink() => break, - Ok(..) => { - followed += 1; - match fs::read_link(&result) { - Ok(path) => { - result.pop(); - result.push(path); - } - Err(e) => { - return Err(e); - } - } - } + if !fs::symlink_metadata(&result)?.file_type().is_symlink() { + break; } + + followed += 1; + let path = fs::read_link(&result)?; + result.pop(); + result.push(path); } Ok(result) } @@ -193,10 +185,8 @@ pub fn canonicalize>(original: P, can_mode: CanonicalizeMode) -> } match resolve(&result) { - Err(e) => match can_mode { - CanonicalizeMode::Missing => continue, - _ => return Err(e), - }, + Err(_) if can_mode == CanonicalizeMode::Missing => continue, + Err(e) => return Err(e), Ok(path) => { result.pop(); result.push(path); @@ -211,15 +201,14 @@ pub fn canonicalize>(original: P, can_mode: CanonicalizeMode) -> } match resolve(&result) { - Err(e) => { - if can_mode == CanonicalizeMode::Existing { - return Err(e); - } + Err(e) if can_mode == CanonicalizeMode::Existing => { + return Err(e); } Ok(path) => { result.pop(); result.push(path); } + Err(_) => (), } } Ok(result) diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index 4fb5a6509..fe109d73d 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -89,19 +89,19 @@ fn parse_levels(mode: &str) -> (u32, usize) { } fn parse_op(mode: &str, default: Option) -> Result<(char, usize), String> { - match mode.chars().next() { - Some(ch) => match ch { - '+' | '-' | '=' => Ok((ch, 1)), - _ => match default { - Some(ch) => Ok((ch, 0)), - None => Err(format!( - "invalid operator (expected +, -, or =, but found {})", - ch - )), - }, - }, - None => Err("unexpected end of mode".to_owned()), - } + let ch = mode + .chars() + .next() + .ok_or_else(|| "unexpected end of mode".to_owned())?; + Ok(match ch { + '+' | '-' | '=' => (ch, 1), + _ => { + let ch = default.ok_or_else(|| { + format!("invalid operator (expected +, -, or =, but found {})", ch) + })?; + (ch, 0) + } + }) } fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) { diff --git a/src/uucore/src/lib/features/parse_time.rs b/src/uucore/src/lib/features/parse_time.rs index 8e822685b..fdf43b727 100644 --- a/src/uucore/src/lib/features/parse_time.rs +++ b/src/uucore/src/lib/features/parse_time.rs @@ -20,20 +20,18 @@ pub fn from_str(string: &str) -> Result { 'm' | 'M' => (slice, 60), 'h' | 'H' => (slice, 60 * 60), 'd' | 'D' => (slice, 60 * 60 * 24), - val => { - if !val.is_alphabetic() { - (string, 1) - } else if string == "inf" || string == "infinity" { + val if !val.is_alphabetic() => (string, 1), + _ => { + if string == "inf" || string == "infinity" { ("inf", 1) } else { return Err(format!("invalid time interval '{}'", string)); } } }; - let num = match numstr.parse::() { - Ok(m) => m, - Err(e) => return Err(format!("invalid time interval '{}': {}", string, e)), - }; + let num = numstr + .parse::() + .map_err(|e| format!("invalid time interval '{}': {}", string, e))?; const NANOS_PER_SEC: u32 = 1_000_000_000; let whole_secs = num.trunc(); diff --git a/src/uucore/src/lib/mods/ranges.rs b/src/uucore/src/lib/mods/ranges.rs index d4a6bf601..9e1e67d5a 100644 --- a/src/uucore/src/lib/mods/ranges.rs +++ b/src/uucore/src/lib/mods/ranges.rs @@ -85,10 +85,9 @@ impl Range { let mut ranges: Vec = vec![]; for item in list.split(',') { - match FromStr::from_str(item) { - Ok(range_item) => ranges.push(range_item), - Err(e) => return Err(format!("range '{}' was invalid: {}", item, e)), - } + let range_item = FromStr::from_str(item) + .map_err(|e| format!("range '{}' was invalid: {}", item, e))?; + ranges.push(range_item); } ranges.sort(); From e985131c832b72a1c874be3a326316cbc66010fd Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 14:27:14 +0900 Subject: [PATCH 178/320] uucore: remove unused warning of sort_groups --- src/uucore/src/lib/features/entries.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index 477fac470..bc4166346 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -101,6 +101,7 @@ pub fn get_groups_gnu(arg_id: Option) -> IOResult> { Ok(sort_groups(groups, egid)) } +#[cfg(all(unix, feature = "process"))] fn sort_groups(mut groups: Vec, egid: gid_t) -> Vec { if let Some(index) = groups.iter().position(|&x| x == egid) { groups[..=index].rotate_right(1); From 9e8be3093fa430e1a48d740574a89d1fcf319e50 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 18:44:28 +0900 Subject: [PATCH 179/320] chown: clean up parse_spec --- src/uu/chown/src/chown.rs | 63 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 166ad72b8..ab9f10dba 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -278,42 +278,25 @@ fn parse_spec(spec: &str) -> Result<(Option, Option), String> { let usr_only = args.len() == 1 && !args[0].is_empty(); let grp_only = args.len() == 2 && args[0].is_empty(); let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty(); - - let r = if usr_only { - ( - Some( - Passwd::locate(args[0]) - .map_err(|_| format!("invalid user: ‘{}’", spec))? - .uid(), - ), - None, - ) - } else if grp_only { - ( - None, - Some( - Group::locate(args[1]) - .map_err(|_| format!("invalid group: ‘{}’", spec))? - .gid(), - ), - ) - } else if usr_grp { - ( - Some( - Passwd::locate(args[0]) - .map_err(|_| format!("invalid user: ‘{}’", spec))? - .uid(), - ), - Some( - Group::locate(args[1]) - .map_err(|_| format!("invalid group: ‘{}’", spec))? - .gid(), - ), + let uid = if usr_only || usr_grp { + Some( + Passwd::locate(args[0]) + .map_err(|_| format!("invalid user: ‘{}’", spec))? + .uid(), ) } else { - (None, None) + None }; - Ok(r) + let gid = if grp_only || usr_grp { + Some( + Group::locate(args[1]) + .map_err(|_| format!("invalid group: ‘{}’", spec))? + .gid(), + ) + } else { + None + }; + Ok((uid, gid)) } enum IfFrom { @@ -502,3 +485,17 @@ impl Chowner { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse_spec() { + assert_eq!(parse_spec(":"), Ok((None, None))); + assert!(parse_spec("::") + .err() + .unwrap() + .starts_with("invalid group: ")); + } +} From 6003d959746a6dadecbd1c6479baa9fff73d1d39 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 21:59:16 +0900 Subject: [PATCH 180/320] comm: clean up line-end check --- src/uu/comm/src/comm.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index f7190fb73..7a6086bb5 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -50,9 +50,8 @@ fn mkdelim(col: usize, opts: &ArgMatches) -> String { } fn ensure_nl(line: &mut String) { - match line.chars().last() { - Some('\n') => (), - _ => line.push('\n'), + if !line.ends_with('\n') { + line.push('\n'); } } From 6734d5df932f07b764c0c50640688e08adc5482a Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jun 2021 19:51:55 +0900 Subject: [PATCH 181/320] basename: trim separators with function --- src/uu/basename/src/basename.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 47ad3117f..098a3e2b2 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -118,14 +118,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fn basename(fullname: &str, suffix: &str) -> String { // Remove all platform-specific path separators from the end - let mut path: String = fullname - .chars() - .rev() - .skip_while(|&ch| is_separator(ch)) - .collect(); - - // Undo reverse - path = path.chars().rev().collect(); + let path = fullname.trim_end_matches(is_separator); // Convert to path buffer and get last path component let pb = PathBuf::from(path); From ddb196dd1d8ee141d5bdc2dd48c6b4a354a84296 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 11 Jun 2021 17:24:12 +0200 Subject: [PATCH 182/320] chgrp: fix clippy and spell-check warnings --- src/uu/chgrp/src/chgrp.rs | 2 +- tests/by-util/test_chgrp.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index c0dc2daf3..454a0386c 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -251,7 +251,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } else { let group = matches.value_of(options::ARG_GROUP).unwrap_or_default(); - match entries::grp2gid(&group) { + match entries::grp2gid(group) { Ok(g) => { dest_gid = g; } diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index d886d674b..762e922c4 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore (words) nosuchgroup +// spell-checker:ignore (words) nosuchgroup groupname use crate::common::util::*; use rust_users::*; @@ -10,7 +10,7 @@ fn test_invalid_option() { static DIR: &str = "/tmp"; -// we should always get both arguments, regardless of whether --refernce was used +// we should always get both arguments, regardless of whether --reference was used #[test] fn test_help() { new_ucmd!() From f90975115538ba4d2043113f4cecf39bd17af94c Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 11 Jun 2021 20:44:25 +0200 Subject: [PATCH 183/320] timeout: don't kill the process if -k is not set `timeout` used to set the timeout to 0 when -k was not set. This collided with the behavior of 0 timeouts, which disable the timeout. When -k is not set the process should not be killed. --- src/uu/timeout/src/timeout.rs | 52 +++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index eabf0192a..bc92157ca 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -42,7 +42,7 @@ pub mod options { struct Config { foreground: bool, - kill_after: Duration, + kill_after: Option, signal: usize, duration: Duration, preserve_status: bool, @@ -66,10 +66,9 @@ impl Config { _ => uucore::signals::signal_by_name_or_value("TERM").unwrap(), }; - let kill_after: Duration = match options.value_of(options::KILL_AFTER) { - Some(time) => uucore::parse_time::from_str(time).unwrap(), - None => Duration::new(0, 0), - }; + let kill_after = options + .value_of(options::KILL_AFTER) + .map(|time| uucore::parse_time::from_str(time).unwrap()); let duration: Duration = uucore::parse_time::from_str(options.value_of(options::DURATION).unwrap()).unwrap(); @@ -178,7 +177,7 @@ fn timeout( cmd: &[String], duration: Duration, signal: usize, - kill_after: Duration, + kill_after: Option, foreground: bool, preserve_status: bool, verbose: bool, @@ -217,27 +216,32 @@ fn timeout( ); } return_if_err!(ERR_EXIT_STATUS, process.send_signal(signal)); - match process.wait_or_timeout(kill_after) { - Ok(Some(status)) => { - if preserve_status { - status.code().unwrap_or_else(|| status.signal().unwrap()) - } else { - 124 + if let Some(kill_after) = kill_after { + match process.wait_or_timeout(kill_after) { + Ok(Some(status)) => { + if preserve_status { + status.code().unwrap_or_else(|| status.signal().unwrap()) + } else { + 124 + } } - } - Ok(None) => { - if verbose { - show_error!("sending signal KILL to command '{}'", cmd[0]); + Ok(None) => { + if verbose { + show_error!("sending signal KILL to command '{}'", cmd[0]); + } + return_if_err!( + ERR_EXIT_STATUS, + process.send_signal( + uucore::signals::signal_by_name_or_value("KILL").unwrap() + ) + ); + return_if_err!(ERR_EXIT_STATUS, process.wait()); + 137 } - return_if_err!( - ERR_EXIT_STATUS, - process - .send_signal(uucore::signals::signal_by_name_or_value("KILL").unwrap()) - ); - return_if_err!(ERR_EXIT_STATUS, process.wait()); - 137 + Err(_) => 124, } - Err(_) => 124, + } else { + 124 } } Err(_) => { From cc0df6ea43621856d884248e72ee4e088ef49c0f Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 10 Jun 2021 22:21:45 +0200 Subject: [PATCH 184/320] sort: move options to the `options` module Be more consistent with other utilities --- src/uu/sort/src/sort.rs | 287 ++++++++++++++++++++++------------------ 1 file changed, 156 insertions(+), 131 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 53619d0d6..11fd05c4e 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -45,8 +45,8 @@ use std::path::PathBuf; use unicode_width::UnicodeWidthStr; use uucore::InvalidEncodingHandling; -static NAME: &str = "sort"; -static ABOUT: &str = "Display sorted concatenation of all FILE(s)."; +const NAME: &str = "sort"; +const ABOUT: &str = "Display sorted concatenation of all FILE(s)."; const LONG_HELP_KEYS: &str = "The key format is FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]][OPTIONS]. @@ -58,49 +58,53 @@ If CHAR is set 0, it means the end of the field. CHAR defaults to 1 for the star Valid options are: MbdfhnRrV. They override the global options for this key."; -static OPT_HUMAN_NUMERIC_SORT: &str = "human-numeric-sort"; -static OPT_MONTH_SORT: &str = "month-sort"; -static OPT_NUMERIC_SORT: &str = "numeric-sort"; -static OPT_GENERAL_NUMERIC_SORT: &str = "general-numeric-sort"; -static OPT_VERSION_SORT: &str = "version-sort"; +mod options { + pub mod modes { + pub const SORT: &str = "sort"; -static OPT_SORT: &str = "sort"; + pub const HUMAN_NUMERIC: &str = "human-numeric-sort"; + pub const MONTH: &str = "month-sort"; + pub const NUMERIC: &str = "numeric-sort"; + pub const GENERAL_NUMERIC: &str = "general-numeric-sort"; + pub const VERSION: &str = "version-sort"; + pub const RANDOM: &str = "random-sort"; -static ALL_SORT_MODES: &[&str] = &[ - OPT_GENERAL_NUMERIC_SORT, - OPT_HUMAN_NUMERIC_SORT, - OPT_MONTH_SORT, - OPT_NUMERIC_SORT, - OPT_VERSION_SORT, - OPT_RANDOM, -]; + pub const ALL_SORT_MODES: [&str; 6] = [ + GENERAL_NUMERIC, + HUMAN_NUMERIC, + MONTH, + NUMERIC, + VERSION, + RANDOM, + ]; + } -static OPT_DICTIONARY_ORDER: &str = "dictionary-order"; -static OPT_MERGE: &str = "merge"; -static OPT_CHECK: &str = "check"; -static OPT_CHECK_SILENT: &str = "check-silent"; -static OPT_DEBUG: &str = "debug"; -static OPT_IGNORE_CASE: &str = "ignore-case"; -static OPT_IGNORE_BLANKS: &str = "ignore-blanks"; -static OPT_IGNORE_NONPRINTING: &str = "ignore-nonprinting"; -static OPT_OUTPUT: &str = "output"; -static OPT_REVERSE: &str = "reverse"; -static OPT_STABLE: &str = "stable"; -static OPT_UNIQUE: &str = "unique"; -static OPT_KEY: &str = "key"; -static OPT_SEPARATOR: &str = "field-separator"; -static OPT_RANDOM: &str = "random-sort"; -static OPT_ZERO_TERMINATED: &str = "zero-terminated"; -static OPT_PARALLEL: &str = "parallel"; -static OPT_FILES0_FROM: &str = "files0-from"; -static OPT_BUF_SIZE: &str = "buffer-size"; -static OPT_TMP_DIR: &str = "temporary-directory"; -static OPT_COMPRESS_PROG: &str = "compress-program"; -static OPT_BATCH_SIZE: &str = "batch-size"; + pub const DICTIONARY_ORDER: &str = "dictionary-order"; + pub const MERGE: &str = "merge"; + pub const CHECK: &str = "check"; + pub const CHECK_SILENT: &str = "check-silent"; + pub const DEBUG: &str = "debug"; + pub const IGNORE_CASE: &str = "ignore-case"; + pub const IGNORE_BLANKS: &str = "ignore-blanks"; + pub const IGNORE_NONPRINTING: &str = "ignore-nonprinting"; + pub const OUTPUT: &str = "output"; + pub const REVERSE: &str = "reverse"; + pub const STABLE: &str = "stable"; + pub const UNIQUE: &str = "unique"; + pub const KEY: &str = "key"; + pub const SEPARATOR: &str = "field-separator"; + pub const ZERO_TERMINATED: &str = "zero-terminated"; + pub const PARALLEL: &str = "parallel"; + pub const FILES0_FROM: &str = "files0-from"; + pub const BUF_SIZE: &str = "buffer-size"; + pub const TMP_DIR: &str = "temporary-directory"; + pub const COMPRESS_PROG: &str = "compress-program"; + pub const BATCH_SIZE: &str = "batch-size"; -static ARG_FILES: &str = "files"; + pub const FILES: &str = "files"; +} -static DECIMAL_PT: char = '.'; +const DECIMAL_PT: char = '.'; const NEGATIVE: char = '-'; const POSITIVE: char = '+'; @@ -108,7 +112,7 @@ const POSITIVE: char = '+'; // Choosing a higher buffer size does not result in performance improvements // (at least not on my machine). TODO: In the future, we should also take the amount of // available memory into consideration, instead of relying on this constant only. -static DEFAULT_BUF_SIZE: usize = 1_000_000_000; // 1 GB +const DEFAULT_BUF_SIZE: usize = 1_000_000_000; // 1 GB #[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy, Debug)] enum SortMode { @@ -889,9 +893,10 @@ With no FILE, or when FILE is -, read standard input.", ) } +/// Creates an `Arg` that conflicts with all other sort modes. fn make_sort_mode_arg<'a, 'b>(mode: &'a str, short: &'b str, help: &'b str) -> Arg<'a, 'b> { let mut arg = Arg::with_name(mode).short(short).long(mode).help(help); - for possible_mode in ALL_SORT_MODES { + for possible_mode in &options::modes::ALL_SORT_MODES { if *possible_mode != mode { arg = arg.conflicts_with(possible_mode); } @@ -911,8 +916,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .about(ABOUT) .usage(&usage[..]) .arg( - Arg::with_name(OPT_SORT) - .long(OPT_SORT) + Arg::with_name(options::modes::SORT) + .long(options::modes::SORT) .takes_value(true) .possible_values( &[ @@ -924,199 +929,213 @@ pub fn uumain(args: impl uucore::Args) -> i32 { "random", ] ) - .conflicts_with_all(ALL_SORT_MODES) + .conflicts_with_all(&options::modes::ALL_SORT_MODES) ) .arg( make_sort_mode_arg( - OPT_HUMAN_NUMERIC_SORT, + options::modes::HUMAN_NUMERIC, "h", "compare according to human readable sizes, eg 1M > 100k" ), ) .arg( make_sort_mode_arg( - OPT_MONTH_SORT, + options::modes::MONTH, "M", "compare according to month name abbreviation" ), ) .arg( make_sort_mode_arg( - OPT_NUMERIC_SORT, + options::modes::NUMERIC, "n", "compare according to string numerical value" ), ) .arg( make_sort_mode_arg( - OPT_GENERAL_NUMERIC_SORT, + options::modes::GENERAL_NUMERIC, "g", "compare according to string general numerical value" ), ) .arg( make_sort_mode_arg( - OPT_VERSION_SORT, + options::modes::VERSION, "V", "Sort by SemVer version number, eg 1.12.2 > 1.1.2", ), ) .arg( make_sort_mode_arg( - OPT_RANDOM, + options::modes::RANDOM, "R", "shuffle in random order", ), ) .arg( - Arg::with_name(OPT_DICTIONARY_ORDER) + Arg::with_name(options::DICTIONARY_ORDER) .short("d") - .long(OPT_DICTIONARY_ORDER) + .long(options::DICTIONARY_ORDER) .help("consider only blanks and alphanumeric characters") - .conflicts_with_all(&[OPT_NUMERIC_SORT, OPT_GENERAL_NUMERIC_SORT, OPT_HUMAN_NUMERIC_SORT, OPT_MONTH_SORT]), + .conflicts_with_all( + &[ + options::modes::NUMERIC, + options::modes::GENERAL_NUMERIC, + options::modes::HUMAN_NUMERIC, + options::modes::MONTH, + ] + ), ) .arg( - Arg::with_name(OPT_MERGE) + Arg::with_name(options::MERGE) .short("m") - .long(OPT_MERGE) + .long(options::MERGE) .help("merge already sorted files; do not sort"), ) .arg( - Arg::with_name(OPT_CHECK) + Arg::with_name(options::CHECK) .short("c") - .long(OPT_CHECK) + .long(options::CHECK) .help("check for sorted input; do not sort"), ) .arg( - Arg::with_name(OPT_CHECK_SILENT) + Arg::with_name(options::CHECK_SILENT) .short("C") - .long(OPT_CHECK_SILENT) + .long(options::CHECK_SILENT) .help("exit successfully if the given file is already sorted, and exit with status 1 otherwise."), ) .arg( - Arg::with_name(OPT_IGNORE_CASE) + Arg::with_name(options::IGNORE_CASE) .short("f") - .long(OPT_IGNORE_CASE) + .long(options::IGNORE_CASE) .help("fold lower case to upper case characters"), ) .arg( - Arg::with_name(OPT_IGNORE_NONPRINTING) + Arg::with_name(options::IGNORE_NONPRINTING) .short("i") - .long(OPT_IGNORE_NONPRINTING) + .long(options::IGNORE_NONPRINTING) .help("ignore nonprinting characters") - .conflicts_with_all(&[OPT_NUMERIC_SORT, OPT_GENERAL_NUMERIC_SORT, OPT_HUMAN_NUMERIC_SORT, OPT_MONTH_SORT]), + .conflicts_with_all( + &[ + options::modes::NUMERIC, + options::modes::GENERAL_NUMERIC, + options::modes::HUMAN_NUMERIC, + options::modes::MONTH + ] + ), ) .arg( - Arg::with_name(OPT_IGNORE_BLANKS) + Arg::with_name(options::IGNORE_BLANKS) .short("b") - .long(OPT_IGNORE_BLANKS) + .long(options::IGNORE_BLANKS) .help("ignore leading blanks when finding sort keys in each line"), ) .arg( - Arg::with_name(OPT_OUTPUT) + Arg::with_name(options::OUTPUT) .short("o") - .long(OPT_OUTPUT) + .long(options::OUTPUT) .help("write output to FILENAME instead of stdout") .takes_value(true) .value_name("FILENAME"), ) .arg( - Arg::with_name(OPT_REVERSE) + Arg::with_name(options::REVERSE) .short("r") - .long(OPT_REVERSE) + .long(options::REVERSE) .help("reverse the output"), ) .arg( - Arg::with_name(OPT_STABLE) + Arg::with_name(options::STABLE) .short("s") - .long(OPT_STABLE) + .long(options::STABLE) .help("stabilize sort by disabling last-resort comparison"), ) .arg( - Arg::with_name(OPT_UNIQUE) + Arg::with_name(options::UNIQUE) .short("u") - .long(OPT_UNIQUE) + .long(options::UNIQUE) .help("output only the first of an equal run"), ) .arg( - Arg::with_name(OPT_KEY) + Arg::with_name(options::KEY) .short("k") - .long(OPT_KEY) + .long(options::KEY) .help("sort by a key") .long_help(LONG_HELP_KEYS) .multiple(true) .takes_value(true), ) .arg( - Arg::with_name(OPT_SEPARATOR) + Arg::with_name(options::SEPARATOR) .short("t") - .long(OPT_SEPARATOR) + .long(options::SEPARATOR) .help("custom separator for -k") .takes_value(true)) .arg( - Arg::with_name(OPT_ZERO_TERMINATED) + Arg::with_name(options::ZERO_TERMINATED) .short("z") - .long(OPT_ZERO_TERMINATED) + .long(options::ZERO_TERMINATED) .help("line delimiter is NUL, not newline"), ) .arg( - Arg::with_name(OPT_PARALLEL) - .long(OPT_PARALLEL) + Arg::with_name(options::PARALLEL) + .long(options::PARALLEL) .help("change the number of threads running concurrently to NUM_THREADS") .takes_value(true) .value_name("NUM_THREADS"), ) .arg( - Arg::with_name(OPT_BUF_SIZE) + Arg::with_name(options::BUF_SIZE) .short("S") - .long(OPT_BUF_SIZE) + .long(options::BUF_SIZE) .help("sets the maximum SIZE of each segment in number of sorted items") .takes_value(true) .value_name("SIZE"), ) .arg( - Arg::with_name(OPT_TMP_DIR) + Arg::with_name(options::TMP_DIR) .short("T") - .long(OPT_TMP_DIR) + .long(options::TMP_DIR) .help("use DIR for temporaries, not $TMPDIR or /tmp") .takes_value(true) .value_name("DIR"), ) .arg( - Arg::with_name(OPT_COMPRESS_PROG) - .long(OPT_COMPRESS_PROG) + Arg::with_name(options::COMPRESS_PROG) + .long(options::COMPRESS_PROG) .help("compress temporary files with PROG, decompress with PROG -d") .long_help("PROG has to take input from stdin and output to stdout") .value_name("PROG") ) .arg( - Arg::with_name(OPT_BATCH_SIZE) - .long(OPT_BATCH_SIZE) + Arg::with_name(options::BATCH_SIZE) + .long(options::BATCH_SIZE) .help("Merge at most N_MERGE inputs at once.") .value_name("N_MERGE") ) .arg( - Arg::with_name(OPT_FILES0_FROM) - .long(OPT_FILES0_FROM) + Arg::with_name(options::FILES0_FROM) + .long(options::FILES0_FROM) .help("read input from the files specified by NUL-terminated NUL_FILES") .takes_value(true) .value_name("NUL_FILES") .multiple(true), ) .arg( - Arg::with_name(OPT_DEBUG) - .long(OPT_DEBUG) + Arg::with_name(options::DEBUG) + .long(options::DEBUG) .help("underline the parts of the line that are actually used for sorting"), ) - .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) + .arg(Arg::with_name(options::FILES).multiple(true).takes_value(true)) .get_matches_from(args); - settings.debug = matches.is_present(OPT_DEBUG); + settings.debug = matches.is_present(options::DEBUG); // check whether user specified a zero terminated list of files for input, otherwise read files from args - let mut files: Vec = if matches.is_present(OPT_FILES0_FROM) { + let mut files: Vec = if matches.is_present(options::FILES0_FROM) { let files0_from: Vec = matches - .values_of(OPT_FILES0_FROM) + .values_of(options::FILES0_FROM) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); @@ -1135,80 +1154,86 @@ pub fn uumain(args: impl uucore::Args) -> i32 { files } else { matches - .values_of(ARG_FILES) + .values_of(options::FILES) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default() }; - settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT) - || matches.value_of(OPT_SORT) == Some("human-numeric") + settings.mode = if matches.is_present(options::modes::HUMAN_NUMERIC) + || matches.value_of(options::modes::SORT) == Some("human-numeric") { SortMode::HumanNumeric - } else if matches.is_present(OPT_MONTH_SORT) || matches.value_of(OPT_SORT) == Some("month") { + } else if matches.is_present(options::modes::MONTH) + || matches.value_of(options::modes::SORT) == Some("month") + { SortMode::Month - } else if matches.is_present(OPT_GENERAL_NUMERIC_SORT) - || matches.value_of(OPT_SORT) == Some("general-numeric") + } else if matches.is_present(options::modes::GENERAL_NUMERIC) + || matches.value_of(options::modes::SORT) == Some("general-numeric") { SortMode::GeneralNumeric - } else if matches.is_present(OPT_NUMERIC_SORT) || matches.value_of(OPT_SORT) == Some("numeric") + } else if matches.is_present(options::modes::NUMERIC) + || matches.value_of(options::modes::SORT) == Some("numeric") { SortMode::Numeric - } else if matches.is_present(OPT_VERSION_SORT) || matches.value_of(OPT_SORT) == Some("version") + } else if matches.is_present(options::modes::VERSION) + || matches.value_of(options::modes::SORT) == Some("version") { SortMode::Version - } else if matches.is_present(OPT_RANDOM) || matches.value_of(OPT_SORT) == Some("random") { + } else if matches.is_present(options::modes::RANDOM) + || matches.value_of(options::modes::SORT) == Some("random") + { settings.salt = get_rand_string(); SortMode::Random } else { SortMode::Default }; - settings.dictionary_order = matches.is_present(OPT_DICTIONARY_ORDER); - settings.ignore_non_printing = matches.is_present(OPT_IGNORE_NONPRINTING); - if matches.is_present(OPT_PARALLEL) { + settings.dictionary_order = matches.is_present(options::DICTIONARY_ORDER); + settings.ignore_non_printing = matches.is_present(options::IGNORE_NONPRINTING); + if matches.is_present(options::PARALLEL) { // "0" is default - threads = num of cores settings.threads = matches - .value_of(OPT_PARALLEL) + .value_of(options::PARALLEL) .map(String::from) .unwrap_or_else(|| "0".to_string()); env::set_var("RAYON_NUM_THREADS", &settings.threads); } settings.buffer_size = matches - .value_of(OPT_BUF_SIZE) + .value_of(options::BUF_SIZE) .map(GlobalSettings::parse_byte_count) .unwrap_or(DEFAULT_BUF_SIZE); settings.tmp_dir = matches - .value_of(OPT_TMP_DIR) + .value_of(options::TMP_DIR) .map(PathBuf::from) .unwrap_or_else(env::temp_dir); - settings.compress_prog = matches.value_of(OPT_COMPRESS_PROG).map(String::from); + settings.compress_prog = matches.value_of(options::COMPRESS_PROG).map(String::from); - if let Some(n_merge) = matches.value_of(OPT_BATCH_SIZE) { + if let Some(n_merge) = matches.value_of(options::BATCH_SIZE) { settings.merge_batch_size = n_merge .parse() .unwrap_or_else(|_| crash!(2, "invalid --batch-size argument '{}'", n_merge)); } - settings.zero_terminated = matches.is_present(OPT_ZERO_TERMINATED); - settings.merge = matches.is_present(OPT_MERGE); + settings.zero_terminated = matches.is_present(options::ZERO_TERMINATED); + settings.merge = matches.is_present(options::MERGE); - settings.check = matches.is_present(OPT_CHECK); - if matches.is_present(OPT_CHECK_SILENT) { - settings.check_silent = matches.is_present(OPT_CHECK_SILENT); + settings.check = matches.is_present(options::CHECK); + if matches.is_present(options::CHECK_SILENT) { + settings.check_silent = matches.is_present(options::CHECK_SILENT); settings.check = true; }; - settings.ignore_case = matches.is_present(OPT_IGNORE_CASE); + settings.ignore_case = matches.is_present(options::IGNORE_CASE); - settings.ignore_blanks = matches.is_present(OPT_IGNORE_BLANKS); + settings.ignore_blanks = matches.is_present(options::IGNORE_BLANKS); - settings.output_file = matches.value_of(OPT_OUTPUT).map(String::from); - settings.reverse = matches.is_present(OPT_REVERSE); - settings.stable = matches.is_present(OPT_STABLE); - settings.unique = matches.is_present(OPT_UNIQUE); + settings.output_file = matches.value_of(options::OUTPUT).map(String::from); + settings.reverse = matches.is_present(options::REVERSE); + settings.stable = matches.is_present(options::STABLE); + settings.unique = matches.is_present(options::UNIQUE); if files.is_empty() { /* if no file, default to stdin */ @@ -1217,7 +1242,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!(1, "extra operand `{}' not allowed with -c", files[1]) } - if let Some(arg) = matches.args.get(OPT_SEPARATOR) { + if let Some(arg) = matches.args.get(options::SEPARATOR) { let separator = arg.vals[0].to_string_lossy(); let separator = separator; if separator.len() != 1 { @@ -1226,15 +1251,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.separator = Some(separator.chars().next().unwrap()) } - if matches.is_present(OPT_KEY) { - for key in &matches.args[OPT_KEY].vals { + if matches.is_present(options::KEY) { + for key in &matches.args[options::KEY].vals { settings .selectors .push(FieldSelector::parse(&key.to_string_lossy(), &settings)); } } - if !matches.is_present(OPT_KEY) { + if !matches.is_present(options::KEY) { // add a default selector matching the whole line let key_settings = KeySettings::from(&settings); settings.selectors.push( From fb035aa049d241ce88291944ba6cdee5a96c7a8c Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 10 Jun 2021 22:38:43 +0200 Subject: [PATCH 185/320] sort: allow --check= syntax * --check=silent and --check=quiet, which are equivalent to -C. * --check=diagnose-first, which is the same as --check We also allow -c=, which confuses GNU sort. --- src/uu/sort/src/sort.rs | 37 ++++++++++++++++++++++++++++--------- tests/by-util/test_sort.rs | 34 +++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 11fd05c4e..1156a9437 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -79,10 +79,16 @@ mod options { ]; } + pub mod check { + pub const CHECK: &str = "check"; + pub const CHECK_SILENT: &str = "check-silent"; + pub const SILENT: &str = "silent"; + pub const QUIET: &str = "quiet"; + pub const DIAGNOSE_FIRST: &str = "diagnose-first"; + } + pub const DICTIONARY_ORDER: &str = "dictionary-order"; pub const MERGE: &str = "merge"; - pub const CHECK: &str = "check"; - pub const CHECK_SILENT: &str = "check-silent"; pub const DEBUG: &str = "debug"; pub const IGNORE_CASE: &str = "ignore-case"; pub const IGNORE_BLANKS: &str = "ignore-blanks"; @@ -994,15 +1000,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("merge already sorted files; do not sort"), ) .arg( - Arg::with_name(options::CHECK) + Arg::with_name(options::check::CHECK) .short("c") - .long(options::CHECK) + .long(options::check::CHECK) + .takes_value(true) + .require_equals(true) + .min_values(0) + .possible_values(&[ + options::check::SILENT, + options::check::QUIET, + options::check::DIAGNOSE_FIRST, + ]) .help("check for sorted input; do not sort"), ) .arg( - Arg::with_name(options::CHECK_SILENT) + Arg::with_name(options::check::CHECK_SILENT) .short("C") - .long(options::CHECK_SILENT) + .long(options::check::CHECK_SILENT) .help("exit successfully if the given file is already sorted, and exit with status 1 otherwise."), ) .arg( @@ -1220,9 +1234,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.zero_terminated = matches.is_present(options::ZERO_TERMINATED); settings.merge = matches.is_present(options::MERGE); - settings.check = matches.is_present(options::CHECK); - if matches.is_present(options::CHECK_SILENT) { - settings.check_silent = matches.is_present(options::CHECK_SILENT); + settings.check = matches.is_present(options::check::CHECK); + if matches.is_present(options::check::CHECK_SILENT) + || matches!( + matches.value_of(options::check::CHECK), + Some(options::check::SILENT) | Some(options::check::QUIET) + ) + { + settings.check_silent = true; settings.check = true; }; diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 7a0143b43..1624c2caf 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -717,26 +717,30 @@ fn test_pipe() { #[test] fn test_check() { - new_ucmd!() - .arg("-c") - .arg("check_fail.txt") - .fails() - .stdout_is("sort: check_fail.txt:6: disorder: 5\n"); + for diagnose_arg in &["-c", "--check", "--check=diagnose-first"] { + new_ucmd!() + .arg(diagnose_arg) + .arg("check_fail.txt") + .fails() + .stdout_is("sort: check_fail.txt:6: disorder: 5\n"); - new_ucmd!() - .arg("-c") - .arg("multiple_files.expected") - .succeeds() - .stdout_is(""); + new_ucmd!() + .arg(diagnose_arg) + .arg("multiple_files.expected") + .succeeds() + .stdout_is(""); + } } #[test] fn test_check_silent() { - new_ucmd!() - .arg("-C") - .arg("check_fail.txt") - .fails() - .stdout_is(""); + for silent_arg in &["-C", "--check=silent", "--check=quiet"] { + new_ucmd!() + .arg(silent_arg) + .arg("check_fail.txt") + .fails() + .stdout_is(""); + } } #[test] From b8d44112918421a0cc2c5b923d2d5eb03ce090b8 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 10 Jun 2021 22:49:17 +0200 Subject: [PATCH 186/320] sort: fix ignore-leading-blanks long option --- src/uu/sort/src/sort.rs | 24 +++++++++++++----------- tests/by-util/test_sort.rs | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 1156a9437..934a4da49 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -91,7 +91,7 @@ mod options { pub const MERGE: &str = "merge"; pub const DEBUG: &str = "debug"; pub const IGNORE_CASE: &str = "ignore-case"; - pub const IGNORE_BLANKS: &str = "ignore-blanks"; + pub const IGNORE_LEADING_BLANKS: &str = "ignore-leading-blanks"; pub const IGNORE_NONPRINTING: &str = "ignore-nonprinting"; pub const OUTPUT: &str = "output"; pub const REVERSE: &str = "reverse"; @@ -149,7 +149,7 @@ impl SortMode { pub struct GlobalSettings { mode: SortMode, debug: bool, - ignore_blanks: bool, + ignore_leading_blanks: bool, ignore_case: bool, dictionary_order: bool, ignore_non_printing: bool, @@ -219,7 +219,7 @@ impl Default for GlobalSettings { GlobalSettings { mode: SortMode::Default, debug: false, - ignore_blanks: false, + ignore_leading_blanks: false, ignore_case: false, dictionary_order: false, ignore_non_printing: false, @@ -310,7 +310,7 @@ impl From<&GlobalSettings> for KeySettings { fn from(settings: &GlobalSettings) -> Self { Self { mode: settings.mode, - ignore_blanks: settings.ignore_blanks, + ignore_blanks: settings.ignore_leading_blanks, ignore_case: settings.ignore_case, ignore_non_printing: settings.ignore_non_printing, reverse: settings.reverse, @@ -517,7 +517,7 @@ impl<'a> Line<'a> { && !settings.stable && !settings.unique && (settings.dictionary_order - || settings.ignore_blanks + || settings.ignore_leading_blanks || settings.ignore_case || settings.ignore_non_printing || settings.mode != SortMode::Default @@ -681,9 +681,11 @@ impl FieldSelector { // This would be ideal for a try block, I think. In the meantime this closure allows // to use the `?` operator here. Self::new( - KeyPosition::new(from, 1, global_settings.ignore_blanks)?, - to.map(|(to, _)| KeyPosition::new(to, 0, global_settings.ignore_blanks)) - .transpose()?, + KeyPosition::new(from, 1, global_settings.ignore_leading_blanks)?, + to.map(|(to, _)| { + KeyPosition::new(to, 0, global_settings.ignore_leading_blanks) + }) + .transpose()?, KeySettings::from(global_settings), ) })() @@ -1040,9 +1042,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ), ) .arg( - Arg::with_name(options::IGNORE_BLANKS) + Arg::with_name(options::IGNORE_LEADING_BLANKS) .short("b") - .long(options::IGNORE_BLANKS) + .long(options::IGNORE_LEADING_BLANKS) .help("ignore leading blanks when finding sort keys in each line"), ) .arg( @@ -1247,7 +1249,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.ignore_case = matches.is_present(options::IGNORE_CASE); - settings.ignore_blanks = matches.is_present(options::IGNORE_BLANKS); + settings.ignore_leading_blanks = matches.is_present(options::IGNORE_LEADING_BLANKS); settings.output_file = matches.value_of(options::OUTPUT).map(String::from); settings.reverse = matches.is_present(options::REVERSE); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 1624c2caf..c1d1a85b5 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -794,7 +794,7 @@ fn test_nonexistent_file() { #[test] fn test_blanks() { - test_helper("blanks", &["-b", "--ignore-blanks"]); + test_helper("blanks", &["-b", "--ignore-leading-blanks"]); } #[test] From b335e7f2aeb6125e068d614affeed96fa2f11e6c Mon Sep 17 00:00:00 2001 From: Hanif Bin Ariffin Date: Sat, 12 Jun 2021 17:57:35 +0800 Subject: [PATCH 187/320] Now stops at the last line first. Press down again to go to next file or quit Signed-off-by: Hanif Bin Ariffin --- src/uu/more/src/more.rs | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 206cebbc2..d83961d2c 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -239,7 +239,11 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bo code: KeyCode::Char(' '), modifiers: KeyModifiers::NONE, }) => { - pager.page_down(); + if pager.should_close() { + return; + } else { + pager.page_down(); + } } Event::Key(KeyEvent { code: KeyCode::Up, @@ -253,9 +257,6 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bo } pager.draw(stdout, wrong_key); - if pager.should_close() { - return; - } } } } @@ -268,7 +269,6 @@ struct Pager<'a> { lines: Vec, next_file: Option<&'a str>, line_count: usize, - close_on_down: bool, silent: bool, } @@ -277,33 +277,25 @@ impl<'a> Pager<'a> { let line_count = lines.len(); Self { upper_mark: 0, - content_rows: rows - 1, + content_rows: rows.saturating_sub(1), lines, next_file, line_count, - close_on_down: false, silent, } } fn should_close(&mut self) -> bool { - if self.upper_mark + self.content_rows >= self.line_count { - if self.close_on_down { - return true; - } - if self.next_file.is_none() { - return true; - } else { - self.close_on_down = true; - } - } else { - self.close_on_down = false; - } - false + self.upper_mark + .saturating_add(self.content_rows) + .eq(&self.line_count) } fn page_down(&mut self) { - self.upper_mark += self.content_rows; + self.upper_mark = self + .upper_mark + .saturating_add(self.content_rows) + .min(self.line_count.saturating_sub(self.content_rows)); } fn page_up(&mut self) { @@ -364,7 +356,7 @@ impl<'a> Pager<'a> { // Break the lines on the cols of the terminal fn break_buff(buff: &str, cols: usize) -> Vec { - let mut lines = Vec::new(); + let mut lines = Vec::with_capacity(buff.lines().count()); for l in buff.lines() { lines.append(&mut break_line(l, cols)); From 63ee42826b017ac4d0075dbdfc373535e731d3af Mon Sep 17 00:00:00 2001 From: Hanif Bin Ariffin Date: Sat, 12 Jun 2021 18:02:31 +0800 Subject: [PATCH 188/320] Fixed numeric type 1. Its better to bump u16 to usize than the other way round. 2. Highly unlikely to have a terminal with usize rows...makes making sense of the code easier. Signed-off-by: Hanif Bin Ariffin --- src/uu/more/src/more.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index d83961d2c..90ef07e99 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -210,7 +210,7 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bo let (cols, rows) = terminal::size().unwrap(); let lines = break_buff(buff, usize::from(cols)); - let mut pager = Pager::new(rows as usize, lines, next_file, silent); + let mut pager = Pager::new(rows, lines, next_file, silent); pager.draw(stdout, false); if pager.should_close() { return; @@ -265,7 +265,7 @@ struct Pager<'a> { // The current line at the top of the screen upper_mark: usize, // The number of rows that fit on the screen - content_rows: usize, + content_rows: u16, lines: Vec, next_file: Option<&'a str>, line_count: usize, @@ -273,7 +273,7 @@ struct Pager<'a> { } impl<'a> Pager<'a> { - fn new(rows: usize, lines: Vec, next_file: Option<&'a str>, silent: bool) -> Self { + fn new(rows: u16, lines: Vec, next_file: Option<&'a str>, silent: bool) -> Self { let line_count = lines.len(); Self { upper_mark: 0, @@ -287,23 +287,25 @@ impl<'a> Pager<'a> { fn should_close(&mut self) -> bool { self.upper_mark - .saturating_add(self.content_rows) + .saturating_add(self.content_rows.into()) .eq(&self.line_count) } fn page_down(&mut self) { self.upper_mark = self .upper_mark - .saturating_add(self.content_rows) - .min(self.line_count.saturating_sub(self.content_rows)); + .saturating_add(self.content_rows.into()) + .min(self.line_count.saturating_sub(self.content_rows.into())); } fn page_up(&mut self) { - self.upper_mark = self.upper_mark.saturating_sub(self.content_rows); + self.upper_mark = self.upper_mark.saturating_sub(self.content_rows.into()); } fn draw(&self, stdout: &mut std::io::Stdout, wrong_key: bool) { - let lower_mark = self.line_count.min(self.upper_mark + self.content_rows); + let lower_mark = self + .line_count + .min(self.upper_mark.saturating_add(self.content_rows.into())); self.draw_lines(stdout); self.draw_prompt(stdout, lower_mark, wrong_key); stdout.flush().unwrap(); @@ -315,7 +317,7 @@ impl<'a> Pager<'a> { .lines .iter() .skip(self.upper_mark) - .take(self.content_rows); + .take(self.content_rows.into()); for line in displayed_lines { stdout From ee6419f11c85056682f4ba190cd9f6329f04043c Mon Sep 17 00:00:00 2001 From: Hanif Bin Ariffin Date: Sat, 12 Jun 2021 18:10:38 +0800 Subject: [PATCH 189/320] Fixing display when resizing terminal Signed-off-by: Hanif Bin Ariffin --- src/uu/more/src/more.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 90ef07e99..dac48cea7 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -251,6 +251,10 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bo }) => { pager.page_up(); } + Event::Resize(col, row) => { + pager.page_resize(col, row); + } + // FIXME: Need to fix, there are more than just unknown keys. _ => { wrong_key = true; } @@ -302,6 +306,11 @@ impl<'a> Pager<'a> { self.upper_mark = self.upper_mark.saturating_sub(self.content_rows.into()); } + // TODO: Deal with column size changes. + fn page_resize(&mut self, _: u16, row: u16) { + self.content_rows = row.saturating_sub(1); + } + fn draw(&self, stdout: &mut std::io::Stdout, wrong_key: bool) { let lower_mark = self .line_count From 28c6fad6e3855176ea77b5899be225d9f2eff5ee Mon Sep 17 00:00:00 2001 From: Hanif Bin Ariffin Date: Sat, 12 Jun 2021 18:25:14 +0800 Subject: [PATCH 190/320] Now displays the unknown key entered Signed-off-by: Hanif Bin Ariffin --- src/uu/more/src/more.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index dac48cea7..3724cd801 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -211,13 +211,13 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bo let lines = break_buff(buff, usize::from(cols)); let mut pager = Pager::new(rows, lines, next_file, silent); - pager.draw(stdout, false); + pager.draw(stdout, None); if pager.should_close() { return; } loop { - let mut wrong_key = false; + let mut wrong_key = None; if event::poll(Duration::from_millis(10)).unwrap() { match event::read().unwrap() { Event::Key(KeyEvent { @@ -254,10 +254,11 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bo Event::Resize(col, row) => { pager.page_resize(col, row); } - // FIXME: Need to fix, there are more than just unknown keys. - _ => { - wrong_key = true; - } + Event::Key(KeyEvent { + code: KeyCode::Char(k), + .. + }) => wrong_key = Some(k), + _ => continue, } pager.draw(stdout, wrong_key); @@ -311,7 +312,7 @@ impl<'a> Pager<'a> { self.content_rows = row.saturating_sub(1); } - fn draw(&self, stdout: &mut std::io::Stdout, wrong_key: bool) { + fn draw(&self, stdout: &mut std::io::Stdout, wrong_key: Option) { let lower_mark = self .line_count .min(self.upper_mark.saturating_add(self.content_rows.into())); @@ -335,7 +336,7 @@ impl<'a> Pager<'a> { } } - fn draw_prompt(&self, stdout: &mut Stdout, lower_mark: usize, wrong_key: bool) { + fn draw_prompt(&self, stdout: &mut Stdout, lower_mark: usize, wrong_key: Option) { let status_inner = if lower_mark == self.line_count { format!("Next file: {}", self.next_file.unwrap_or_default()) } else { @@ -348,10 +349,15 @@ impl<'a> Pager<'a> { let status = format!("--More--({})", status_inner); let banner = match (self.silent, wrong_key) { - (true, true) => "[Press 'h' for instructions. (unimplemented)]".to_string(), - (true, false) => format!("{}[Press space to continue, 'q' to quit.]", status), - (false, true) => format!("{}{}", status, BELL), - (false, false) => status, + (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, }; write!( From a390383d2d71f51a4512abb641e61655ac280484 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 12 Jun 2021 13:01:42 +0200 Subject: [PATCH 191/320] core: represent signal values by their index --- src/uucore/src/lib/features/signals.rs | 326 ++----------------------- 1 file changed, 23 insertions(+), 303 deletions(-) diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 3c52a9158..e6d2e7763 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -10,11 +10,6 @@ pub static DEFAULT_SIGNAL: usize = 15; -pub struct Signal<'a> { - pub name: &'a str, - pub value: usize, -} - /* Linux Programmer's Manual @@ -29,135 +24,10 @@ Linux Programmer's Manual */ #[cfg(target_os = "linux")] -pub static ALL_SIGNALS: [Signal<'static>; 32] = [ - Signal { - name: "EXIT", - value: 0, - }, - Signal { - name: "HUP", - value: 1, - }, - Signal { - name: "INT", - value: 2, - }, - Signal { - name: "QUIT", - value: 3, - }, - Signal { - name: "ILL", - value: 4, - }, - Signal { - name: "TRAP", - value: 5, - }, - Signal { - name: "ABRT", - value: 6, - }, - Signal { - name: "BUS", - value: 7, - }, - Signal { - name: "FPE", - value: 8, - }, - Signal { - name: "KILL", - value: 9, - }, - Signal { - name: "USR1", - value: 10, - }, - Signal { - name: "SEGV", - value: 11, - }, - Signal { - name: "USR2", - value: 12, - }, - Signal { - name: "PIPE", - value: 13, - }, - Signal { - name: "ALRM", - value: 14, - }, - Signal { - name: "TERM", - value: 15, - }, - Signal { - name: "STKFLT", - value: 16, - }, - Signal { - name: "CHLD", - value: 17, - }, - Signal { - name: "CONT", - value: 18, - }, - Signal { - name: "STOP", - value: 19, - }, - Signal { - name: "TSTP", - value: 20, - }, - Signal { - name: "TTIN", - value: 21, - }, - Signal { - name: "TTOU", - value: 22, - }, - Signal { - name: "URG", - value: 23, - }, - Signal { - name: "XCPU", - value: 24, - }, - Signal { - name: "XFSZ", - value: 25, - }, - Signal { - name: "VTALRM", - value: 26, - }, - Signal { - name: "PROF", - value: 27, - }, - Signal { - name: "WINCH", - value: 28, - }, - Signal { - name: "POLL", - value: 29, - }, - Signal { - name: "PWR", - value: 30, - }, - Signal { - name: "SYS", - value: 31, - }, +pub static ALL_SIGNALS: [&str; 32] = [ + "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", "KILL", "USR1", "SEGV", + "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN", "TTOU", + "URG", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "POLL", "PWR", "SYS", ]; /* @@ -202,135 +72,10 @@ No Name Default Action Description */ #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -pub static ALL_SIGNALS: [Signal<'static>; 32] = [ - Signal { - name: "EXIT", - value: 0, - }, - Signal { - name: "HUP", - value: 1, - }, - Signal { - name: "INT", - value: 2, - }, - Signal { - name: "QUIT", - value: 3, - }, - Signal { - name: "ILL", - value: 4, - }, - Signal { - name: "TRAP", - value: 5, - }, - Signal { - name: "ABRT", - value: 6, - }, - Signal { - name: "EMT", - value: 7, - }, - Signal { - name: "FPE", - value: 8, - }, - Signal { - name: "KILL", - value: 9, - }, - Signal { - name: "BUS", - value: 10, - }, - Signal { - name: "SEGV", - value: 11, - }, - Signal { - name: "SYS", - value: 12, - }, - Signal { - name: "PIPE", - value: 13, - }, - Signal { - name: "ALRM", - value: 14, - }, - Signal { - name: "TERM", - value: 15, - }, - Signal { - name: "URG", - value: 16, - }, - Signal { - name: "STOP", - value: 17, - }, - Signal { - name: "TSTP", - value: 18, - }, - Signal { - name: "CONT", - value: 19, - }, - Signal { - name: "CHLD", - value: 20, - }, - Signal { - name: "TTIN", - value: 21, - }, - Signal { - name: "TTOU", - value: 22, - }, - Signal { - name: "IO", - value: 23, - }, - Signal { - name: "XCPU", - value: 24, - }, - Signal { - name: "XFSZ", - value: 25, - }, - Signal { - name: "VTALRM", - value: 26, - }, - Signal { - name: "PROF", - value: 27, - }, - Signal { - name: "WINCH", - value: 28, - }, - Signal { - name: "INFO", - value: 29, - }, - Signal { - name: "USR1", - value: 30, - }, - Signal { - name: "USR2", - value: 31, - }, +pub static ALL_SIGNALS: [&str; 32] = [ + "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", "BUS", "SEGV", + "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO", + "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1", "USR2", ]; pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option { @@ -343,70 +88,45 @@ pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option { } let signal_name = signal_name_or_value.trim_start_matches("SIG"); - ALL_SIGNALS - .iter() - .find(|s| s.name == signal_name) - .map(|s| s.value) + ALL_SIGNALS.iter().position(|&s| s == signal_name) +} + +pub fn is_signal(num: usize) -> bool { + num < ALL_SIGNALS.len() } pub fn signal_name_by_value(signal_value: usize) -> Option<&'static str> { - ALL_SIGNALS - .iter() - .find(|signal| signal.value == signal_value) - .map(|signal| signal.name) -} - -#[inline(always)] -pub fn is_signal(num: usize) -> bool { - // Named signals start at 1 - num <= ALL_SIGNALS.len() -} - -#[test] -fn signals_all_contiguous() { - for (i, signal) in ALL_SIGNALS.iter().enumerate() { - assert_eq!(signal.value, i); - } -} - -#[test] -fn signals_all_are_signal() { - for signal in &ALL_SIGNALS { - assert!(is_signal(signal.value)); - } + ALL_SIGNALS.get(signal_value).copied() } #[test] fn signal_by_value() { assert_eq!(signal_by_name_or_value("0"), Some(0)); - for signal in &ALL_SIGNALS { - assert_eq!( - signal_by_name_or_value(&signal.value.to_string()), - Some(signal.value) - ); + for (value, _signal) in ALL_SIGNALS.iter().enumerate() { + assert_eq!(signal_by_name_or_value(&value.to_string()), Some(value)); } } #[test] fn signal_by_short_name() { - for signal in &ALL_SIGNALS { - assert_eq!(signal_by_name_or_value(signal.name), Some(signal.value)); + for (value, signal) in ALL_SIGNALS.iter().enumerate() { + assert_eq!(signal_by_name_or_value(signal), Some(value)); } } #[test] fn signal_by_long_name() { - for signal in &ALL_SIGNALS { + for (value, signal) in ALL_SIGNALS.iter().enumerate() { assert_eq!( - signal_by_name_or_value(&format!("SIG{}", signal.name)), - Some(signal.value) + signal_by_name_or_value(&format!("SIG{}", signal)), + Some(value) ); } } #[test] fn name() { - for signal in &ALL_SIGNALS { - assert_eq!(signal_name_by_value(signal.value), Some(signal.name)); + for (value, signal) in ALL_SIGNALS.iter().enumerate() { + assert_eq!(signal_name_by_value(value), Some(*signal)); } } From a57313f01b29f2b363eea3dcace5d2ceeaa02754 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 12 Jun 2021 13:01:42 +0200 Subject: [PATCH 192/320] core: represent signal values by their index --- src/uu/kill/src/kill.rs | 22 +- src/uucore/src/lib/features/signals.rs | 326 ++----------------------- 2 files changed, 33 insertions(+), 315 deletions(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index a49acaa05..7965c40a9 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -132,13 +132,13 @@ fn table() { let mut name_width = 0; /* Compute the maximum width of a signal name. */ for s in &ALL_SIGNALS { - if s.name.len() > name_width { - name_width = s.name.len() + if s.len() > name_width { + name_width = s.len() } } for (idx, signal) in ALL_SIGNALS.iter().enumerate() { - print!("{0: >#2} {1: <#8}", idx + 1, signal.name); + print!("{0: >#2} {1: <#8}", idx + 1, signal); //TODO: obtain max signal width here if (idx + 1) % 7 == 0 { @@ -148,14 +148,12 @@ fn table() { } fn print_signal(signal_name_or_value: &str) { - for signal in &ALL_SIGNALS { - if signal.name == signal_name_or_value - || (format!("SIG{}", signal.name)) == signal_name_or_value - { - println!("{}", signal.value); + for (value, &signal) in ALL_SIGNALS.iter().enumerate() { + if signal == signal_name_or_value || (format!("SIG{}", signal)) == signal_name_or_value { + println!("{}", value); exit!(EXIT_OK as i32) - } else if signal_name_or_value == signal.value.to_string() { - println!("{}", signal.name); + } else if signal_name_or_value == value.to_string() { + println!("{}", signal); exit!(EXIT_OK as i32) } } @@ -165,8 +163,8 @@ fn print_signal(signal_name_or_value: &str) { fn print_signals() { let mut pos = 0; for (idx, signal) in ALL_SIGNALS.iter().enumerate() { - pos += signal.name.len(); - print!("{}", signal.name); + pos += signal.len(); + print!("{}", signal); if idx > 0 && pos > 73 { println!(); pos = 0; diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 3c52a9158..e6d2e7763 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -10,11 +10,6 @@ pub static DEFAULT_SIGNAL: usize = 15; -pub struct Signal<'a> { - pub name: &'a str, - pub value: usize, -} - /* Linux Programmer's Manual @@ -29,135 +24,10 @@ Linux Programmer's Manual */ #[cfg(target_os = "linux")] -pub static ALL_SIGNALS: [Signal<'static>; 32] = [ - Signal { - name: "EXIT", - value: 0, - }, - Signal { - name: "HUP", - value: 1, - }, - Signal { - name: "INT", - value: 2, - }, - Signal { - name: "QUIT", - value: 3, - }, - Signal { - name: "ILL", - value: 4, - }, - Signal { - name: "TRAP", - value: 5, - }, - Signal { - name: "ABRT", - value: 6, - }, - Signal { - name: "BUS", - value: 7, - }, - Signal { - name: "FPE", - value: 8, - }, - Signal { - name: "KILL", - value: 9, - }, - Signal { - name: "USR1", - value: 10, - }, - Signal { - name: "SEGV", - value: 11, - }, - Signal { - name: "USR2", - value: 12, - }, - Signal { - name: "PIPE", - value: 13, - }, - Signal { - name: "ALRM", - value: 14, - }, - Signal { - name: "TERM", - value: 15, - }, - Signal { - name: "STKFLT", - value: 16, - }, - Signal { - name: "CHLD", - value: 17, - }, - Signal { - name: "CONT", - value: 18, - }, - Signal { - name: "STOP", - value: 19, - }, - Signal { - name: "TSTP", - value: 20, - }, - Signal { - name: "TTIN", - value: 21, - }, - Signal { - name: "TTOU", - value: 22, - }, - Signal { - name: "URG", - value: 23, - }, - Signal { - name: "XCPU", - value: 24, - }, - Signal { - name: "XFSZ", - value: 25, - }, - Signal { - name: "VTALRM", - value: 26, - }, - Signal { - name: "PROF", - value: 27, - }, - Signal { - name: "WINCH", - value: 28, - }, - Signal { - name: "POLL", - value: 29, - }, - Signal { - name: "PWR", - value: 30, - }, - Signal { - name: "SYS", - value: 31, - }, +pub static ALL_SIGNALS: [&str; 32] = [ + "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", "KILL", "USR1", "SEGV", + "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN", "TTOU", + "URG", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "POLL", "PWR", "SYS", ]; /* @@ -202,135 +72,10 @@ No Name Default Action Description */ #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -pub static ALL_SIGNALS: [Signal<'static>; 32] = [ - Signal { - name: "EXIT", - value: 0, - }, - Signal { - name: "HUP", - value: 1, - }, - Signal { - name: "INT", - value: 2, - }, - Signal { - name: "QUIT", - value: 3, - }, - Signal { - name: "ILL", - value: 4, - }, - Signal { - name: "TRAP", - value: 5, - }, - Signal { - name: "ABRT", - value: 6, - }, - Signal { - name: "EMT", - value: 7, - }, - Signal { - name: "FPE", - value: 8, - }, - Signal { - name: "KILL", - value: 9, - }, - Signal { - name: "BUS", - value: 10, - }, - Signal { - name: "SEGV", - value: 11, - }, - Signal { - name: "SYS", - value: 12, - }, - Signal { - name: "PIPE", - value: 13, - }, - Signal { - name: "ALRM", - value: 14, - }, - Signal { - name: "TERM", - value: 15, - }, - Signal { - name: "URG", - value: 16, - }, - Signal { - name: "STOP", - value: 17, - }, - Signal { - name: "TSTP", - value: 18, - }, - Signal { - name: "CONT", - value: 19, - }, - Signal { - name: "CHLD", - value: 20, - }, - Signal { - name: "TTIN", - value: 21, - }, - Signal { - name: "TTOU", - value: 22, - }, - Signal { - name: "IO", - value: 23, - }, - Signal { - name: "XCPU", - value: 24, - }, - Signal { - name: "XFSZ", - value: 25, - }, - Signal { - name: "VTALRM", - value: 26, - }, - Signal { - name: "PROF", - value: 27, - }, - Signal { - name: "WINCH", - value: 28, - }, - Signal { - name: "INFO", - value: 29, - }, - Signal { - name: "USR1", - value: 30, - }, - Signal { - name: "USR2", - value: 31, - }, +pub static ALL_SIGNALS: [&str; 32] = [ + "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", "BUS", "SEGV", + "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO", + "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1", "USR2", ]; pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option { @@ -343,70 +88,45 @@ pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option { } let signal_name = signal_name_or_value.trim_start_matches("SIG"); - ALL_SIGNALS - .iter() - .find(|s| s.name == signal_name) - .map(|s| s.value) + ALL_SIGNALS.iter().position(|&s| s == signal_name) +} + +pub fn is_signal(num: usize) -> bool { + num < ALL_SIGNALS.len() } pub fn signal_name_by_value(signal_value: usize) -> Option<&'static str> { - ALL_SIGNALS - .iter() - .find(|signal| signal.value == signal_value) - .map(|signal| signal.name) -} - -#[inline(always)] -pub fn is_signal(num: usize) -> bool { - // Named signals start at 1 - num <= ALL_SIGNALS.len() -} - -#[test] -fn signals_all_contiguous() { - for (i, signal) in ALL_SIGNALS.iter().enumerate() { - assert_eq!(signal.value, i); - } -} - -#[test] -fn signals_all_are_signal() { - for signal in &ALL_SIGNALS { - assert!(is_signal(signal.value)); - } + ALL_SIGNALS.get(signal_value).copied() } #[test] fn signal_by_value() { assert_eq!(signal_by_name_or_value("0"), Some(0)); - for signal in &ALL_SIGNALS { - assert_eq!( - signal_by_name_or_value(&signal.value.to_string()), - Some(signal.value) - ); + for (value, _signal) in ALL_SIGNALS.iter().enumerate() { + assert_eq!(signal_by_name_or_value(&value.to_string()), Some(value)); } } #[test] fn signal_by_short_name() { - for signal in &ALL_SIGNALS { - assert_eq!(signal_by_name_or_value(signal.name), Some(signal.value)); + for (value, signal) in ALL_SIGNALS.iter().enumerate() { + assert_eq!(signal_by_name_or_value(signal), Some(value)); } } #[test] fn signal_by_long_name() { - for signal in &ALL_SIGNALS { + for (value, signal) in ALL_SIGNALS.iter().enumerate() { assert_eq!( - signal_by_name_or_value(&format!("SIG{}", signal.name)), - Some(signal.value) + signal_by_name_or_value(&format!("SIG{}", signal)), + Some(value) ); } } #[test] fn name() { - for signal in &ALL_SIGNALS { - assert_eq!(signal_name_by_value(signal.value), Some(signal.name)); + for (value, signal) in ALL_SIGNALS.iter().enumerate() { + assert_eq!(signal_name_by_value(value), Some(*signal)); } } From 9ed5091be6bf4f0f9c13ec85a1b1ea9f72c4eccb Mon Sep 17 00:00:00 2001 From: Hanif Bin Ariffin Date: Sat, 12 Jun 2021 20:30:15 +0800 Subject: [PATCH 193/320] Fixed hanging with smaller content Using 'seq 10 | cargo run -- more' should no longer hangs. Signed-off-by: Hanif Bin Ariffin --- src/uu/more/src/more.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 3724cd801..93a2f0edf 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -293,7 +293,7 @@ impl<'a> Pager<'a> { fn should_close(&mut self) -> bool { self.upper_mark .saturating_add(self.content_rows.into()) - .eq(&self.line_count) + .ge(&self.line_count) } fn page_down(&mut self) { From 083e74597613d2edd1d17fce1bca282a8db0f315 Mon Sep 17 00:00:00 2001 From: Hanif Bin Ariffin Date: Sat, 12 Jun 2021 20:34:21 +0800 Subject: [PATCH 194/320] Simplified page down implementation Signed-off-by: Hanif Bin Ariffin --- src/uu/more/src/more.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 93a2f0edf..d7fba5080 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -297,10 +297,7 @@ impl<'a> Pager<'a> { } fn page_down(&mut self) { - self.upper_mark = self - .upper_mark - .saturating_add(self.content_rows.into()) - .min(self.line_count.saturating_sub(self.content_rows.into())); + self.upper_mark = self.upper_mark.saturating_add(self.content_rows.into()); } fn page_up(&mut self) { From d6181ce7d44e523c8400f4081bb1b16d3d97b0aa Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Sat, 5 Jun 2021 23:35:15 +0530 Subject: [PATCH 195/320] du: Add threshold argument support - Add --threshold parameter and corresponding logic to skip listing entires that don't adhere to the threshold --- src/uu/du/src/du.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index b7c53eb72..39af3d7e1 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -28,6 +28,7 @@ use std::os::windows::io::AsRawHandle; #[cfg(windows)] use std::path::Path; use std::path::PathBuf; +use std::str::FromStr; use std::time::{Duration, UNIX_EPOCH}; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::InvalidEncodingHandling; @@ -56,6 +57,7 @@ mod options { pub const BLOCK_SIZE_1M: &str = "m"; pub const SEPARATE_DIRS: &str = "S"; pub const SUMMARIZE: &str = "s"; + pub const THRESHOLD: &str = "threshold"; pub const SI: &str = "si"; pub const TIME: &str = "time"; pub const TIME_STYLE: &str = "time-style"; @@ -510,6 +512,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::ONE_FILE_SYSTEM) .help("skip directories on different file systems") ) + .arg( + Arg::with_name(options::THRESHOLD) + .short("t") + .long(options::THRESHOLD) + .alias("th") + .value_name("SIZE") + .number_of_values(1) + .allow_hyphen_values(true) + .help("exclude entries smaller than SIZE if positive, \ + or entries greater than SIZE if negative") + ) // .arg( // Arg::with_name("") // .short("x") @@ -586,6 +599,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let block_size = u64::try_from(read_block_size(matches.value_of(options::BLOCK_SIZE))).unwrap(); + let threshold = match matches.value_of(options::THRESHOLD) { + Some(s) => Threshold::from_str(s) + .unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::THRESHOLD))), + None => Threshold(None, false), + }; + let multiplier: u64 = if matches.is_present(options::SI) { 1000 } else { @@ -654,6 +673,10 @@ Try '{} --help' for more information.", // See: http://linux.die.net/man/2/stat stat.blocks * 512 }; + if threshold.should_exclude(size) { + continue; + } + if matches.is_present(options::TIME) { let tm = { let secs = { @@ -720,6 +743,35 @@ Try '{} --help' for more information.", 0 } +struct Threshold(Option, bool); + +impl FromStr for Threshold { + type Err = ParseSizeError; + + fn from_str(s: &str) -> std::result::Result { + let offset = if s.starts_with('-') || s.starts_with('+') { + 1 + } else { + 0 + }; + let sz = parse_size(&s[offset..])?; + + Ok(Threshold( + Some(u64::try_from(sz).unwrap()), + s.starts_with('-'), + )) + } +} + +impl Threshold { + fn should_exclude(&self, sz: u64) -> bool { + match *self { + Threshold(Some(th), is_upper) => (is_upper && sz > th) || (!is_upper && sz < th), + Threshold(None, _) => false, + } + } +} + fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String { // NOTE: // GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection From fa12b46c51268dd4c4f2db6fcaf93421c3330015 Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Sat, 12 Jun 2021 19:30:48 +0530 Subject: [PATCH 196/320] tests: Add test for du threshold feature --- tests/by-util/test_du.rs | 50 +++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 8d1267423..93875ae51 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -80,19 +80,22 @@ fn _du_basics_subdir(s: &str) { #[test] fn test_du_invalid_size() { - new_ucmd!() - .arg("--block-size=1fb4t") - .arg("/tmp") - .fails() - .code_is(1) - .stderr_only("du: invalid --block-size argument '1fb4t'"); - #[cfg(not(target_pointer_width = "128"))] - new_ucmd!() - .arg("--block-size=1Y") - .arg("/tmp") - .fails() - .code_is(1) - .stderr_only("du: --block-size argument '1Y' too large"); + let args = &["block-size", "threshold"]; + for s in args { + new_ucmd!() + .arg(format!("--{}=1fb4t", s)) + .arg("/tmp") + .fails() + .code_is(1) + .stderr_only(format!("du: invalid --{} argument '1fb4t'", s)); + #[cfg(not(target_pointer_width = "128"))] + new_ucmd!() + .arg(format!("--{}=1Y", s)) + .arg("/tmp") + .fails() + .code_is(1) + .stderr_only(format!("du: --{} argument '1Y' too large", s)); + } } #[test] @@ -351,3 +354,24 @@ fn test_du_one_file_system() { } _du_basics_subdir(result.stdout_str()); } + +#[test] +fn test_du_threshold() { + let scene = TestScenario::new(util_name!()); + + let threshold = if cfg!(windows) { "7K" } else { "10K" }; + + scene + .ucmd() + .arg(format!("--threshold={}", threshold)) + .succeeds() + .stdout_contains("links") + .stdout_does_not_contain("deeper"); + + scene + .ucmd() + .arg(format!("--threshold=-{}", threshold)) + .succeeds() + .stdout_does_not_contain("links") + .stdout_contains("deeper"); +} From 8e7eedebe7d3578289b54dce7f69668840b74a23 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 11 Jun 2021 22:00:26 +0200 Subject: [PATCH 197/320] tests: take slices in `stdout_is_fixture` --- tests/by-util/test_pr.rs | 132 +++++++++------------------------------ tests/common/util.rs | 2 +- 2 files changed, 31 insertions(+), 103 deletions(-) diff --git a/tests/by-util/test_pr.rs b/tests/by-util/test_pr.rs index def361fab..c1dee2a6c 100644 --- a/tests/by-util/test_pr.rs +++ b/tests/by-util/test_pr.rs @@ -33,10 +33,7 @@ fn test_without_any_options() { scenario .args(&[test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); } #[test] @@ -48,10 +45,7 @@ fn test_with_numbering_option_with_number_width() { scenario .args(&["-n", "2", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); } #[test] @@ -66,10 +60,7 @@ fn test_with_long_header_option() { .succeeds() .stdout_is_templated_fixture( expected_test_file_path, - vec![ - (&"{last_modified_time}".to_string(), &value), - (&"{header}".to_string(), &header.to_string()), - ], + &[("{last_modified_time}", &value), ("{header}", header)], ); new_ucmd!() @@ -77,10 +68,7 @@ fn test_with_long_header_option() { .succeeds() .stdout_is_templated_fixture( expected_test_file_path, - vec![ - (&"{last_modified_time}".to_string(), &value), - (&"{header}".to_string(), &header.to_string()), - ], + &[("{last_modified_time}", &value), ("{header}", header)], ); } @@ -93,18 +81,12 @@ fn test_with_double_space_option() { scenario .args(&["-d", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); new_ucmd!() .args(&["--double-space", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); } #[test] @@ -116,10 +98,7 @@ fn test_with_first_line_number_option() { scenario .args(&["-N", "5", "-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); } #[test] @@ -131,10 +110,7 @@ fn test_with_first_line_number_long_option() { scenario .args(&["--first-line-number=5", "-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); } #[test] @@ -146,10 +122,7 @@ fn test_with_number_option_with_custom_separator_char() { scenario .args(&["-nc", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); } #[test] @@ -161,10 +134,7 @@ fn test_with_number_option_with_custom_separator_char_and_width() { scenario .args(&["-nc1", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); } #[test] @@ -207,25 +177,19 @@ fn test_with_page_range() { scenario .args(&["--pages=15", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); new_ucmd!() .args(&["+15", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); new_ucmd!() .args(&["--pages=15:17", test_file_path]) .succeeds() .stdout_is_templated_fixture( expected_test_file_path1, - vec![(&"{last_modified_time}".to_string(), &value)], + &[("{last_modified_time}", &value)], ); new_ucmd!() @@ -233,7 +197,7 @@ fn test_with_page_range() { .succeeds() .stdout_is_templated_fixture( expected_test_file_path1, - vec![(&"{last_modified_time}".to_string(), &value)], + &[("{last_modified_time}", &value)], ); } @@ -246,10 +210,7 @@ fn test_with_no_header_trailer_option() { scenario .args(&["-t", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); } #[test] @@ -262,10 +223,7 @@ fn test_with_page_length_option() { scenario .args(&["--pages=2:3", "-l", "100", "-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); new_ucmd!() .args(&["--pages=2:3", "-l", "5", "-n", test_file_path]) @@ -293,10 +251,7 @@ fn test_with_stdin() { .pipe_in_fixture("stdin.log") .args(&["--pages=1:2", "-n", "-"]) .run() - .stdout_is_templated_fixture( - expected_file_path, - vec![(&"{last_modified_time}".to_string(), &now)], - ); + .stdout_is_templated_fixture(expected_file_path, &[("{last_modified_time}", &now)]); } #[test] @@ -308,18 +263,12 @@ fn test_with_column() { scenario .args(&["--pages=3:5", "--column=3", "-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); new_ucmd!() .args(&["--pages=3:5", "-3", "-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); } #[test] @@ -331,10 +280,7 @@ fn test_with_column_across_option() { scenario .args(&["--pages=3:5", "--column=3", "-a", "-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); } #[test] @@ -354,10 +300,7 @@ fn test_with_column_across_option_and_column_separator() { test_file_path, ]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); new_ucmd!() .args(&[ @@ -371,7 +314,7 @@ fn test_with_column_across_option_and_column_separator() { .succeeds() .stdout_is_templated_fixture( expected_test_file_path1, - vec![(&"{last_modified_time}".to_string(), &value)], + &[("{last_modified_time}", &value)], ); } @@ -386,19 +329,13 @@ fn test_with_mpr() { new_ucmd!() .args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &now)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &now)]); let now = now_time(); new_ucmd!() .args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path1, - vec![(&"{last_modified_time}".to_string(), &now)], - ); + .stdout_is_templated_fixture(expected_test_file_path1, &[("{last_modified_time}", &now)]); let now = now_time(); new_ucmd!() @@ -413,10 +350,7 @@ fn test_with_mpr() { test_file_path, ]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path2, - vec![(&"{last_modified_time}".to_string(), &now)], - ); + .stdout_is_templated_fixture(expected_test_file_path2, &[("{last_modified_time}", &now)]); } #[test] @@ -452,10 +386,7 @@ fn test_with_offset_space_option() { test_file_path, ]) .succeeds() - .stdout_is_templated_fixture( - expected_test_file_path, - vec![(&"{last_modified_time}".to_string(), &value)], - ); + .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); } #[test] @@ -497,9 +428,9 @@ fn test_with_pr_core_utils_tests() { scenario_with_expected_status.stdout_is_templated_fixture( test_file_path, - vec![ - (&"{last_modified_time}".to_string(), &value), - (&"{file_name}".to_string(), &input_file_path.to_string()), + &[ + ("{last_modified_time}", &value), + ("{file_name}", input_file_path), ], ); } @@ -515,8 +446,5 @@ fn test_with_join_lines_option() { scenario .args(&["+1:2", "-J", "-m", test_file_1, test_file_2]) .run() - .stdout_is_templated_fixture( - expected_file_path, - vec![(&"{last_modified_time}".to_string(), &now)], - ); + .stdout_is_templated_fixture(expected_file_path, &[("{last_modified_time}", &now)]); } diff --git a/tests/common/util.rs b/tests/common/util.rs index 922d2ba36..52911912e 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -247,7 +247,7 @@ impl CmdResult { pub fn stdout_is_templated_fixture>( &self, file_rel_path: T, - template_vars: Vec<(&String, &String)>, + template_vars: &[(&str, &str)], ) -> &CmdResult { let mut contents = String::from_utf8(read_scenario_fixture(&self.tmpd, file_rel_path)).unwrap(); From 98088db9ff09d40ee9f61aa697dedf41e2ae71ce Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 11 Jun 2021 22:02:10 +0200 Subject: [PATCH 198/320] tests: add `_any` functions This should make it easier to write tests that could have different valid outputs depending on timing. --- tests/common/util.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/common/util.rs b/tests/common/util.rs index 52911912e..f881cff21 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -223,6 +223,18 @@ impl CmdResult { self } + /// like `stdout_is`, but succeeds if any elements of `expected` matches stdout. + pub fn stdout_is_any + std::fmt::Debug>(&self, expected: Vec) -> &CmdResult { + if !expected.iter().any(|msg| self.stdout_str() == msg.as_ref()) { + panic!( + "stdout was {}\nExpected any of {:#?}", + self.stdout_str(), + expected + ) + } + self + } + /// Like `stdout_is` but newlines are normalized to `\n`. pub fn normalized_newlines_stdout_is>(&self, msg: T) -> &CmdResult { let msg = msg.as_ref().replace("\r\n", "\n"); @@ -257,6 +269,23 @@ impl CmdResult { self.stdout_is(contents) } + /// like `stdout_is_templated_fixture`, but succeeds if any replacement by `template_vars` results in the actual stdout. + pub fn stdout_is_templated_fixture_any>( + &self, + file_rel_path: T, + template_vars: &[Vec<(String, String)>], + ) { + let contents = String::from_utf8(read_scenario_fixture(&self.tmpd, file_rel_path)).unwrap(); + let possible_values = template_vars.iter().map(|vars| { + let mut contents = contents.clone(); + for kv in vars.iter() { + contents = contents.replace(&kv.0, &kv.1); + } + contents + }); + self.stdout_is_any(possible_values.collect()); + } + /// asserts that the command resulted in stderr stream output that equals the /// passed in value, when both are trimmed of trailing whitespace /// stderr_only is a better choice unless stdout may or will be non-empty From bb029193e2b281f07f5fae69985a42d028f4f6a6 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 11 Jun 2021 22:29:16 +0200 Subject: [PATCH 199/320] tests/pr: prevent races Allow any timestamp from the start of the command to its end to show up in stdout. --- tests/by-util/test_pr.rs | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/tests/by-util/test_pr.rs b/tests/by-util/test_pr.rs index c1dee2a6c..2391bc37a 100644 --- a/tests/by-util/test_pr.rs +++ b/tests/by-util/test_pr.rs @@ -3,6 +3,7 @@ use crate::common::util::*; use chrono::offset::Local; use chrono::DateTime; +use chrono::Duration; use std::fs::metadata; fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String { @@ -20,8 +21,22 @@ fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String { .unwrap_or_default() } -fn now_time() -> String { - Local::now().format("%b %d %H:%M %Y").to_string() +fn all_minutes(from: DateTime, to: DateTime) -> Vec { + const FORMAT: &str = "%b %d %H:%M %Y"; + let mut vec = vec![]; + let mut current = from; + while current < to { + vec.push(current.format(FORMAT).to_string()); + current = current + Duration::minutes(1); + } + vec +} + +fn valid_last_modified_template_vars(from: DateTime) -> Vec> { + all_minutes(from, Local::now()) + .into_iter() + .map(|time| vec![("{last_modified_time}".to_string(), time)]) + .collect() } #[test] @@ -246,12 +261,12 @@ fn test_with_suppress_error_option() { fn test_with_stdin() { let expected_file_path = "stdin.log.expected"; let mut scenario = new_ucmd!(); - let now = now_time(); + let start = Local::now(); scenario .pipe_in_fixture("stdin.log") .args(&["--pages=1:2", "-n", "-"]) .run() - .stdout_is_templated_fixture(expected_file_path, &[("{last_modified_time}", &now)]); + .stdout_is_templated_fixture_any(expected_file_path, &valid_last_modified_template_vars(start)); } #[test] @@ -325,19 +340,19 @@ fn test_with_mpr() { let expected_test_file_path = "mpr.log.expected"; let expected_test_file_path1 = "mpr1.log.expected"; let expected_test_file_path2 = "mpr2.log.expected"; - let now = now_time(); + let start = Local::now(); new_ucmd!() .args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &now)]); + .stdout_is_templated_fixture_any(expected_test_file_path, &valid_last_modified_template_vars(start)); - let now = now_time(); + let start = Local::now(); new_ucmd!() .args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path1, &[("{last_modified_time}", &now)]); + .stdout_is_templated_fixture_any(expected_test_file_path1, &valid_last_modified_template_vars(start)); - let now = now_time(); + let start = Local::now(); new_ucmd!() .args(&[ "--pages=1:2", @@ -350,7 +365,7 @@ fn test_with_mpr() { test_file_path, ]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path2, &[("{last_modified_time}", &now)]); + .stdout_is_templated_fixture_any(expected_test_file_path2, &valid_last_modified_template_vars(start)); } #[test] @@ -442,9 +457,9 @@ fn test_with_join_lines_option() { let test_file_2 = "test.log"; let expected_file_path = "joined.log.expected"; let mut scenario = new_ucmd!(); - let now = now_time(); + let start = Local::now(); scenario .args(&["+1:2", "-J", "-m", test_file_1, test_file_2]) .run() - .stdout_is_templated_fixture(expected_file_path, &[("{last_modified_time}", &now)]); + .stdout_is_templated_fixture_any(expected_file_path, &valid_last_modified_template_vars(start)); } From d8c8e6774ff5c66783ff875635282e7fb5468162 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 12 Jun 2021 12:35:50 +0200 Subject: [PATCH 200/320] tests/pr: formatting --- tests/by-util/test_pr.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/by-util/test_pr.rs b/tests/by-util/test_pr.rs index 2391bc37a..fb6703f28 100644 --- a/tests/by-util/test_pr.rs +++ b/tests/by-util/test_pr.rs @@ -266,7 +266,10 @@ fn test_with_stdin() { .pipe_in_fixture("stdin.log") .args(&["--pages=1:2", "-n", "-"]) .run() - .stdout_is_templated_fixture_any(expected_file_path, &valid_last_modified_template_vars(start)); + .stdout_is_templated_fixture_any( + expected_file_path, + &valid_last_modified_template_vars(start), + ); } #[test] @@ -344,13 +347,19 @@ fn test_with_mpr() { new_ucmd!() .args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1]) .succeeds() - .stdout_is_templated_fixture_any(expected_test_file_path, &valid_last_modified_template_vars(start)); + .stdout_is_templated_fixture_any( + expected_test_file_path, + &valid_last_modified_template_vars(start), + ); let start = Local::now(); new_ucmd!() .args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1]) .succeeds() - .stdout_is_templated_fixture_any(expected_test_file_path1, &valid_last_modified_template_vars(start)); + .stdout_is_templated_fixture_any( + expected_test_file_path1, + &valid_last_modified_template_vars(start), + ); let start = Local::now(); new_ucmd!() @@ -365,7 +374,10 @@ fn test_with_mpr() { test_file_path, ]) .succeeds() - .stdout_is_templated_fixture_any(expected_test_file_path2, &valid_last_modified_template_vars(start)); + .stdout_is_templated_fixture_any( + expected_test_file_path2, + &valid_last_modified_template_vars(start), + ); } #[test] @@ -461,5 +473,8 @@ fn test_with_join_lines_option() { scenario .args(&["+1:2", "-J", "-m", test_file_1, test_file_2]) .run() - .stdout_is_templated_fixture_any(expected_file_path, &valid_last_modified_template_vars(start)); + .stdout_is_templated_fixture_any( + expected_file_path, + &valid_last_modified_template_vars(start), + ); } From da7b02cf9d8b64d35de957124807f3a3b80c02ac Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Sat, 12 Jun 2021 21:31:56 +0530 Subject: [PATCH 201/320] du: Refactor threshold handling --- src/uu/du/src/du.rs | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 39af3d7e1..e466b8afe 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -599,11 +599,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let block_size = u64::try_from(read_block_size(matches.value_of(options::BLOCK_SIZE))).unwrap(); - let threshold = match matches.value_of(options::THRESHOLD) { - Some(s) => Threshold::from_str(s) - .unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::THRESHOLD))), - None => Threshold(None, false), - }; + let threshold = matches.value_of(options::THRESHOLD).map(|s| { + Threshold::from_str(s) + .unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::THRESHOLD))) + }); let multiplier: u64 = if matches.is_present(options::SI) { 1000 @@ -673,7 +672,8 @@ Try '{} --help' for more information.", // See: http://linux.die.net/man/2/stat stat.blocks * 512 }; - if threshold.should_exclude(size) { + + if threshold.map_or(false, |threshold| threshold.should_exclude(size)) { continue; } @@ -743,31 +743,33 @@ Try '{} --help' for more information.", 0 } -struct Threshold(Option, bool); +#[derive(Clone, Copy)] +enum Threshold { + Lower(u64), + Upper(u64), +} impl FromStr for Threshold { type Err = ParseSizeError; fn from_str(s: &str) -> std::result::Result { - let offset = if s.starts_with('-') || s.starts_with('+') { - 1 - } else { - 0 - }; - let sz = parse_size(&s[offset..])?; + let offset = if s.starts_with(&['-', '+'][..]) { 1 } else { 0 }; - Ok(Threshold( - Some(u64::try_from(sz).unwrap()), - s.starts_with('-'), - )) + let size = u64::try_from(parse_size(&s[offset..])?).unwrap(); + + if s.starts_with('-') { + Ok(Threshold::Upper(size)) + } else { + Ok(Threshold::Lower(size)) + } } } impl Threshold { - fn should_exclude(&self, sz: u64) -> bool { + fn should_exclude(&self, size: u64) -> bool { match *self { - Threshold(Some(th), is_upper) => (is_upper && sz > th) || (!is_upper && sz < th), - Threshold(None, _) => false, + Threshold::Upper(threshold) => size > threshold, + Threshold::Lower(threshold) => size < threshold, } } } From 60124b8fbd5e620aea227aeb09f85a1a4293705c Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 13 Jun 2021 10:09:24 +0200 Subject: [PATCH 202/320] CICD/GNU: only run/compile tests for `id` --- .github/workflows/CICD.yml | 3 ++- .github/workflows/GNU.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 32c3537c2..42c448561 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -564,7 +564,8 @@ jobs: run: | ## Dependent VARs setup # * determine sub-crate utility list - UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" + # UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" + UTILITY_LIST="id" # TODO: remove after debugging CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" echo set-output name=UTILITY_LIST::${UTILITY_LIST} echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS} diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 1f9250900..e9227e38e 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -45,7 +45,8 @@ jobs: - name: Run GNU tests shell: bash run: | - bash uutils/util/run-gnu-test.sh + bash uutils/util/run-gnu-test.sh tests/id/uid.sh # TODO: remove after debugging + bash uutils/util/run-gnu-test.sh tests/id/zero.sh # TODO: remove after debugging - name: Extract tests info shell: bash run: | From 9af93437456fda9e50a9d577315995900b868c92 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 13 Jun 2021 10:12:00 +0200 Subject: [PATCH 203/320] uucore: entries: add documentation --- src/uucore/src/lib/features/entries.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index b94abbe4f..2d5b44362 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -179,6 +179,13 @@ impl Passwd { self.inner } + /// This is a wrapper function for `libc::getgrouplist`. + /// + /// From: https://man7.org/linux/man-pages/man3/getgrouplist.3.html + /// If the user is a member of more than *ngroups groups, then + /// getgrouplist() returns -1. In this case, the value returned in + /// *ngroups can be used to resize the buffer passed to a further + /// call getgrouplist(). pub fn belongs_to(&self) -> Vec { let mut ngroups: c_int = 8; let mut groups = Vec::with_capacity(ngroups as usize); From 17c6f4c13a3d58a502b51d56bd3fc7416de0a998 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 13 Jun 2021 11:11:04 +0200 Subject: [PATCH 204/320] id: add more tests to be consistent with GNU testsuite tests (tests/id/zero.sh) --- tests/by-util/test_id.rs | 239 +++++++++++++++++++++++++++++++++------ 1 file changed, 207 insertions(+), 32 deletions(-) diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 4823cf6d0..9e1a218ea 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -13,6 +13,8 @@ use crate::common::util::*; // fn whoami() -> String { + // Use environment variable to get current user instead of invoking `whoami` + // and fall back to user "nobody" on error. std::env::var("USER").unwrap_or_else(|e| { println!("warning: {}, using \"nobody\" instead", e); "nobody".to_string() @@ -21,12 +23,12 @@ fn whoami() -> String { #[test] #[cfg(unix)] -fn test_id_no_argument() { +fn test_id_no_specified_user() { let result = new_ucmd!().run(); let expected_result = expected_result(&[]); let mut exp_stdout = expected_result.stdout_str().to_string(); - // uu_stid does not support selinux context. Remove 'context' part from exp_stdout: + // uu_id does not support selinux context. Remove 'context' part from exp_stdout: let context_offset = expected_result .stdout_str() .find(" context") @@ -42,18 +44,63 @@ fn test_id_no_argument() { #[test] #[cfg(unix)] fn test_id_single_user() { - let args = &[&whoami()[..]]; - let result = new_ucmd!().args(args).run(); - let expected_result = expected_result(args); - result - .stdout_is(expected_result.stdout_str()) - .stderr_is(expected_result.stderr_str()) - .code_is(expected_result.code()); + let test_users = [&whoami()[..]]; + + let scene = TestScenario::new(util_name!()); + let mut exp_result = expected_result(&test_users); + scene + .ucmd() + .args(&test_users) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + + // u/g/G z/n + for &opt in &["--user", "--group", "--groups"] { + let mut args = vec![opt]; + args.extend_from_slice(&test_users); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + args.push("--zero"); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + args.push("--name"); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + args.pop(); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + } } #[test] #[cfg(unix)] -fn test_id_single_invalid_user() { +fn test_id_single_user_non_existing() { let args = &["hopefully_non_existing_username"]; let result = new_ucmd!().args(args).run(); let expected_result = expected_result(args); @@ -127,43 +174,164 @@ fn test_id_password_style() { assert!(result.stdout_str().starts_with(&username)); } -#[test] -#[cfg(unix)] -fn test_id_default_format() { - // TODO: These are the same tests like in test_id_zero but without --zero flag. -} - #[test] #[cfg(unix)] fn test_id_multiple_users() { // Same typical users that GNU testsuite is using. let test_users = ["root", "man", "postfix", "sshd", &whoami()]; - let result = new_ucmd!().args(&test_users).run(); - let expected_result = expected_result(&test_users); - result - .stdout_is(expected_result.stdout_str()) - .stderr_is(expected_result.stderr_str()); + let scene = TestScenario::new(util_name!()); + let mut exp_result = expected_result(&test_users); + scene + .ucmd() + .args(&test_users) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + + // u/g/G z/n + for &opt in &["--user", "--group", "--groups"] { + let mut args = vec![opt]; + args.extend_from_slice(&test_users); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + args.push("--zero"); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + args.push("--name"); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + args.pop(); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + } } #[test] #[cfg(unix)] -fn test_id_multiple_invalid_users() { +fn test_id_multiple_users_non_existing() { let test_users = [ "root", "hopefully_non_existing_username1", + &whoami(), "man", + "hopefully_non_existing_username2", + "hopefully_non_existing_username3", "postfix", "sshd", - "hopefully_non_existing_username2", + "hopefully_non_existing_username4", &whoami(), ]; - let result = new_ucmd!().args(&test_users).run(); - let expected_result = expected_result(&test_users); - result - .stdout_is(expected_result.stdout_str()) - .stderr_is(expected_result.stderr_str()); + let scene = TestScenario::new(util_name!()); + let mut exp_result = expected_result(&test_users); + scene + .ucmd() + .args(&test_users) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + + // u/g/G z/n + for &opt in &["--user", "--group", "--groups"] { + let mut args = vec![opt]; + args.extend_from_slice(&test_users); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + args.push("--zero"); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + args.push("--name"); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + args.pop(); + exp_result = expected_result(&args); + scene + .ucmd() + .args(&args) + .run() + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + } +} + +#[test] +#[cfg(unix)] +fn test_id_default_format() { + let scene = TestScenario::new(util_name!()); + for &opt1 in &["--name", "--real"] { + // id: cannot print only names or real IDs in default format + let args = [opt1]; + scene + .ucmd() + .args(&args) + .fails() + .stderr_only(expected_result(&args).stderr_str()); + for &opt2 in &["--user", "--group", "--groups"] { + // u/g/G n/r + let args = [opt2, opt1]; + let result = scene.ucmd().args(&args).run(); + let exp_result = expected_result(&args); + result + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + } + } + for &opt2 in &["--user", "--group", "--groups"] { + // u/g/G + let args = [opt2]; + scene + .ucmd() + .args(&args) + .succeeds() + .stdout_only(expected_result(&args).stdout_str()); + } } #[test] @@ -171,6 +339,12 @@ fn test_id_multiple_invalid_users() { fn test_id_zero() { let scene = TestScenario::new(util_name!()); for z_flag in &["-z", "--zero"] { + // id: option --zero not permitted in default format + scene + .ucmd() + .args(&[z_flag]) + .fails() + .stderr_only(expected_result(&[z_flag]).stderr_str()); for &opt1 in &["--name", "--real"] { // id: cannot print only names or real IDs in default format let args = [opt1, z_flag]; @@ -183,14 +357,15 @@ fn test_id_zero() { // u/g/G n/r z let args = [opt2, z_flag, opt1]; let result = scene.ucmd().args(&args).run(); - let expected_result = expected_result(&args); + let exp_result = expected_result(&args); result - .stdout_is(expected_result.stdout_str()) - .stderr_is(expected_result.stderr_str()); + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); } } - // u/g/G z for &opt2 in &["--user", "--group", "--groups"] { + // u/g/G z let args = [opt2, z_flag]; scene .ucmd() From b4c47cc5bda717c60cf38250baea253c4eca13a2 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 13 Jun 2021 11:12:53 +0200 Subject: [PATCH 205/320] id: make `id` pass GNU's testssuite for "tests/id/uid.sh" and "tests/id/zero.sh" --- src/uu/id/src/id.rs | 479 ++++++++++++++++++++++++++++---------------- 1 file changed, 305 insertions(+), 174 deletions(-) diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 570a87790..6afb23d67 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -6,15 +6,23 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // -// Synced with: +// This was originally based on BSD's `id` +// (noticeable in functionality, usage text, options text, etc.) +// and synced with: // http://ftp-archive.freebsd.org/mirror/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/shellutils/src/id.c // http://www.opensource.apple.com/source/shell_cmds/shell_cmds-118/id/id.c // -// This is not based on coreutils (8.32) GNU's `id`. -// This is based on BSD's `id` (noticeable in functionality, usage text, options text, etc.) +// * This was partially rewritten in order for stdout/stderr/exit_code +// to be conform with GNU coreutils (8.32) testsuite for `id`. +// +// * This passes GNU's coreutils Testsuite (8.32.161-370c2-dirty) +// for "tests/id/uid.sh" and "tests/id/zero/sh". +// +// * Option '--zero' does not exist for BSD's `id`, therefor '--zero' is only +// allowed together with other options that are available on GNU's `id`. +// +// * Help text based on BSD's `id`. // -// Option '--zero' does not exist for BSD's `id`, therefor '--zero' is only allowed together -// with other options that are available on GNU's `id`. // spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag @@ -37,49 +45,12 @@ macro_rules! cstr2cow { }; } -#[cfg(not(target_os = "linux"))] -mod audit { - use super::libc::{c_int, c_uint, dev_t, pid_t, uid_t}; - - pub type au_id_t = uid_t; - pub type au_asid_t = pid_t; - pub type au_event_t = c_uint; - pub type au_emod_t = c_uint; - pub type au_class_t = c_int; - pub type au_flag_t = u64; - - #[repr(C)] - pub struct au_mask { - pub am_success: c_uint, - pub am_failure: c_uint, - } - pub type au_mask_t = au_mask; - - #[repr(C)] - pub struct au_tid_addr { - pub port: dev_t, - } - pub type au_tid_addr_t = au_tid_addr; - - #[repr(C)] - pub struct c_auditinfo_addr { - pub ai_auid: au_id_t, // Audit user ID - pub ai_mask: au_mask_t, // Audit masks. - pub ai_termid: au_tid_addr_t, // Terminal ID. - pub ai_asid: au_asid_t, // Audit session ID. - pub ai_flags: au_flag_t, // Audit session flags - } - pub type c_auditinfo_addr_t = c_auditinfo_addr; - - extern "C" { - pub fn getaudit(auditinfo_addr: *mut c_auditinfo_addr_t) -> c_int; - } -} - -static ABOUT: &str = "The id utility displays the user and group names and numeric IDs, of the calling process, to the standard output. If the real and effective IDs are different, both are displayed, otherwise only the real ID is displayed.\n\nIf a user (login name or user ID) is specified, the user and group IDs of that user are displayed. In this case, the real and effective IDs are assumed to be the same."; +static ABOUT: &str = "Print user and group information for each specified USER, +or (when USER omitted) for the current user."; mod options { pub const OPT_AUDIT: &str = "audit"; // GNU's id does not have this + pub const OPT_CONTEXT: &str = "context"; pub const OPT_EFFECTIVE_USER: &str = "user"; pub const OPT_GROUP: &str = "group"; pub const OPT_GROUPS: &str = "groups"; @@ -92,21 +63,76 @@ mod options { } fn get_usage() -> String { - format!("{0} [OPTION]... [USER]", executable!()) + format!("{0} [OPTION]... [USER]...", executable!()) +} + +fn get_description() -> String { + String::from( + "The id utility displays the user and group names and numeric IDs, of the \ + calling process, to the standard output. If the real and effective IDs are \ + different, both are displayed, otherwise only the real ID is displayed.\n\n\ + If a user (login name or user ID) is specified, the user and group IDs of \ + that user are displayed. In this case, the real and effective IDs are \ + assumed to be the same.", + ) +} + +struct Ids { + uid: u32, // user id + gid: u32, // group id + euid: u32, // effective uid + egid: u32, // effective gid +} + +struct State { + nflag: bool, // --name + uflag: bool, // --user + gflag: bool, // --group + gsflag: bool, // --groups + rflag: bool, // --real + zflag: bool, // --zero + ids: Option, + // The behaviour for calling GNU's `id` and calling GNU's `id $USER` is similar but different. + // * The SELinux context is only displayed without a specified user. + // * The `getgroups` system call is only used without a specified user, this causes + // the order of the displayed groups to be different between `id` and `id $USER`. + // + // Example: + // $ strace -e getgroups id -G $USER + // 1000 10 975 968 + // +++ exited with 0 +++ + // $ strace -e getgroups id -G + // getgroups(0, NULL) = 4 + // getgroups(4, [10, 968, 975, 1000]) = 4 + // 1000 10 968 975 + // +++ exited with 0 +++ + user_specified: bool, } pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); + let after_help = get_description(); let matches = App::new(executable!()) .version(crate_version!()) .about(ABOUT) .usage(&usage[..]) + .after_help(&after_help[..]) .arg( Arg::with_name(options::OPT_AUDIT) .short("A") - .conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER, options::OPT_HUMAN_READABLE, options::OPT_PASSWORD, options::OPT_GROUPS, options::OPT_ZERO]) - .help("Display the process audit user ID and other process audit properties, which requires privilege (not available on Linux)."), + .conflicts_with_all(&[ + options::OPT_GROUP, + options::OPT_EFFECTIVE_USER, + options::OPT_HUMAN_READABLE, + options::OPT_PASSWORD, + options::OPT_GROUPS, + options::OPT_ZERO, + ]) + .help( + "Display the process audit user ID and other process audit properties,\n\ + which requires privilege (not available on Linux).", + ), ) .arg( Arg::with_name(options::OPT_EFFECTIVE_USER) @@ -125,8 +151,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::OPT_GROUPS) .short("G") .long(options::OPT_GROUPS) - .conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER, options::OPT_HUMAN_READABLE, options::OPT_PASSWORD, options::OPT_AUDIT]) - .help("Display only the different group IDs as white-space separated numbers, in no particular order."), + .conflicts_with_all(&[ + options::OPT_GROUP, + options::OPT_EFFECTIVE_USER, + options::OPT_HUMAN_READABLE, + options::OPT_PASSWORD, + options::OPT_AUDIT, + ]) + .help( + "Display only the different group IDs as white-space separated numbers, \ + in no particular order.", + ), ) .arg( Arg::with_name(options::OPT_HUMAN_READABLE) @@ -137,7 +172,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::OPT_NAME) .short("n") .long(options::OPT_NAME) - .help("Display the name of the user or group ID for the -G, -g and -u options instead of the number. If any of the ID numbers cannot be mapped into names, the number will be displayed as usual."), + .help( + "Display the name of the user or group ID for the -G, -g and -u options \ + instead of the number.\nIf any of the ID numbers cannot be mapped into \ + names, the number will be displayed as usual.", + ), ) .arg( Arg::with_name(options::OPT_PASSWORD) @@ -148,13 +187,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::OPT_REAL_ID) .short("r") .long(options::OPT_REAL_ID) - .help("Display the real ID for the -G, -g and -u options instead of the effective ID."), + .help( + "Display the real ID for the -G, -g and -u options instead of \ + the effective ID.", + ), ) .arg( Arg::with_name(options::OPT_ZERO) .short("z") .long(options::OPT_ZERO) - .help("delimit entries with NUL characters, not whitespace;\nnot permitted in default format"), + .help( + "delimit entries with NUL characters, not whitespace;\n\ + not permitted in default format", + ), + ) + .arg( + Arg::with_name(options::OPT_CONTEXT) + .short("Z") + .long(options::OPT_CONTEXT) + .help("NotImplemented: print only the security context of the process"), ) .arg( Arg::with_name(options::ARG_USERS) @@ -164,129 +215,173 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) .get_matches_from(args); - let nflag = matches.is_present(options::OPT_NAME); - let uflag = matches.is_present(options::OPT_EFFECTIVE_USER); - let gflag = matches.is_present(options::OPT_GROUP); - let gsflag = matches.is_present(options::OPT_GROUPS); - let rflag = matches.is_present(options::OPT_REAL_ID); - let zflag = matches.is_present(options::OPT_ZERO); - - // "default format" is when none of '-ugG' was used - // could not implement these "required" rules with just clap - if (nflag || rflag) && !(uflag || gflag || gsflag) { - crash!(1, "cannot print only names or real IDs in default format"); - } - if (zflag) && !(uflag || gflag || gsflag) { - // GNU testsuite "id/zero.sh" needs this stderr output - crash!(1, "option --zero not permitted in default format"); - } - let users: Vec = matches .values_of(options::ARG_USERS) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - if matches.is_present(options::OPT_AUDIT) { - auditid(); - return 0; + let mut state = State { + nflag: matches.is_present(options::OPT_NAME), + uflag: matches.is_present(options::OPT_EFFECTIVE_USER), + gflag: matches.is_present(options::OPT_GROUP), + gsflag: matches.is_present(options::OPT_GROUPS), + rflag: matches.is_present(options::OPT_REAL_ID), + zflag: matches.is_present(options::OPT_ZERO), + user_specified: !users.is_empty(), + ids: None, + }; + + let default_format = { + // "default format" is when none of '-ugG' was used + !(state.uflag || state.gflag || state.gsflag) + }; + + if (state.nflag || state.rflag) && default_format { + crash!(1, "cannot print only names or real IDs in default format"); + } + if (state.zflag) && default_format { + // NOTE: GNU testsuite "id/zero.sh" needs this stderr output: + crash!(1, "option --zero not permitted in default format"); } - let possible_pw = if users.is_empty() { - None - } else { - match Passwd::locate(users[0].as_str()) { - Ok(p) => Some(p), - Err(_) => crash!(1, "No such user/group: {}", users[0]), + let delimiter = { + if state.zflag { + "\0".to_string() + } else { + " ".to_string() + } + }; + let line_ending = { + if state.zflag { + '\0' + } else { + '\n' } }; - - let line_ending = if zflag { '\0' } else { '\n' }; let mut exit_code = 0; - if gflag { - let id = possible_pw - .map(|p| p.gid()) - .unwrap_or(if rflag { getgid() } else { getegid() }); - print!( - "{}{}", - if nflag { - entries::gid2grp(id).unwrap_or_else(|_| { - show_error!("cannot find name for group ID {}", id); + for i in 0..=users.len() { + let possible_pw = if !state.user_specified { + None + } else { + match Passwd::locate(users[i].as_str()) { + Ok(p) => Some(p), + Err(_) => { + show_error!("‘{}’: no such user", users[i]); exit_code = 1; - id.to_string() - }) - } else { - id.to_string() - }, - line_ending - ); - return exit_code; - } + if i + 1 >= users.len() { + break; + } else { + continue; + } + } + } + }; - if uflag { - let id = possible_pw - .map(|p| p.uid()) - .unwrap_or(if rflag { getuid() } else { geteuid() }); - print!( - "{}{}", - if nflag { - entries::uid2usr(id).unwrap_or_else(|_| { - show_error!("cannot find name for user ID {}", id); - exit_code = 1; - id.to_string() - }) - } else { - id.to_string() - }, - line_ending - ); - return exit_code; - } + // GNU's `id` does not support the flags: -p/-P/-A. + if matches.is_present(options::OPT_PASSWORD) { + // BSD's `id` ignores all but the first specified user + pline(possible_pw.map(|v| v.uid())); + return exit_code; + }; + if matches.is_present(options::OPT_HUMAN_READABLE) { + // BSD's `id` ignores all but the first specified user + pretty(possible_pw); + return exit_code; + } + if matches.is_present(options::OPT_AUDIT) { + // BSD's `id` ignores specified users + auditid(); + return exit_code; + } - if gsflag { - let delimiter = if zflag { "\0" } else { " " }; - let id = possible_pw - .map(|p| p.gid()) - .unwrap_or(if rflag { getgid() } else { getegid() }); - print!( - "{}{}", - possible_pw - .map(|p| p.belongs_to()) - .unwrap_or_else(|| entries::get_groups_gnu(Some(id)).unwrap()) - .iter() - .map(|&id| if nflag { - entries::gid2grp(id).unwrap_or_else(|_| { - show_error!("cannot find name for group ID {}", id); + let (uid, gid) = possible_pw.map(|p| (p.uid(), p.gid())).unwrap_or(( + if state.rflag { getuid() } else { geteuid() }, + if state.rflag { getgid() } else { getegid() }, + )); + state.ids = Some(Ids { + uid, + gid, + euid: geteuid(), + egid: getegid(), + }); + + if state.gflag { + print!( + "{}", + if state.nflag { + entries::gid2grp(gid).unwrap_or_else(|_| { + show_error!("cannot find name for group ID {}", gid); exit_code = 1; - id.to_string() + gid.to_string() }) } else { - id.to_string() - }) - .collect::>() - .join(delimiter), - line_ending - ); - return exit_code; + gid.to_string() + } + ); + } + + if state.uflag { + print!( + "{}", + if state.nflag { + entries::uid2usr(uid).unwrap_or_else(|_| { + show_error!("cannot find name for user ID {}", uid); + exit_code = 1; + uid.to_string() + }) + } else { + uid.to_string() + } + ); + } + + let groups = if state.user_specified { + possible_pw + .map(|p| p.belongs_to()) + .unwrap_or_else(|| entries::get_groups_gnu(Some(gid)).unwrap()) + } else { + entries::get_groups_gnu(Some(gid)).unwrap() + }; + + if state.gsflag { + print!( + "{}{}", + groups + .iter() + .map(|&id| { + if state.nflag { + entries::gid2grp(id).unwrap_or_else(|_| { + show_error!("cannot find name for group ID {}", id); + exit_code = 1; + id.to_string() + }) + } else { + id.to_string() + } + }) + .collect::>() + .join(&delimiter), + // NOTE: this is necessary to pass GNU's "tests/id/zero.sh": + if state.zflag && state.user_specified && users.len() > 1 { + "\0" + } else { + "" + } + ); + } + + if default_format { + id_print(&state, groups); + } + print!("{}", line_ending); + + if i + 1 >= users.len() { + break; + } } - if matches.is_present(options::OPT_PASSWORD) { - pline(possible_pw.map(|v| v.uid())); - return 0; - }; - - if matches.is_present(options::OPT_HUMAN_READABLE) { - pretty(possible_pw); - return 0; - } - - if possible_pw.is_some() { - id_print(possible_pw, false, false) - } else { - id_print(possible_pw, true, true) - } - - 0 + exit_code } fn pretty(possible_pw: Option) { @@ -399,30 +494,21 @@ fn auditid() { println!("asid={}", auditinfo.ai_asid); } -fn id_print(possible_pw: Option, p_euid: bool, p_egid: bool) { - let (uid, gid) = possible_pw - .map(|p| (p.uid(), p.gid())) - .unwrap_or((getuid(), getgid())); - - let groups = match Passwd::locate(uid) { - Ok(p) => p.belongs_to(), - Err(e) => crash!(1, "Could not find uid {}: {}", uid, e), - }; +fn id_print(state: &State, groups: Vec) { + let uid = state.ids.as_ref().unwrap().uid; + let gid = state.ids.as_ref().unwrap().gid; + let euid = state.ids.as_ref().unwrap().euid; + let egid = state.ids.as_ref().unwrap().egid; print!("uid={}({})", uid, entries::uid2usr(uid).unwrap()); print!(" gid={}({})", gid, entries::gid2grp(gid).unwrap()); - - let euid = geteuid(); - if p_euid && (euid != uid) { + if !state.user_specified && (euid != uid) { print!(" euid={}({})", euid, entries::uid2usr(euid).unwrap()); } - - let egid = getegid(); - if p_egid && (egid != gid) { + if !state.user_specified && (egid != gid) { print!(" egid={}({})", euid, entries::gid2grp(egid).unwrap()); } - - println!( + print!( " groups={}", groups .iter() @@ -430,4 +516,49 @@ fn id_print(possible_pw: Option, p_euid: bool, p_egid: bool) { .collect::>() .join(",") ); + + // placeholder ("-Z" is NotImplemented): + // if !state.user_specified { + // // print SElinux context (does not depend on "-Z") + // print!(" context={}", get_selinux_contexts().join(":")); + // } +} + +#[cfg(not(target_os = "linux"))] +mod audit { + use super::libc::{c_int, c_uint, dev_t, pid_t, uid_t}; + + pub type au_id_t = uid_t; + pub type au_asid_t = pid_t; + pub type au_event_t = c_uint; + pub type au_emod_t = c_uint; + pub type au_class_t = c_int; + pub type au_flag_t = u64; + + #[repr(C)] + pub struct au_mask { + pub am_success: c_uint, + pub am_failure: c_uint, + } + pub type au_mask_t = au_mask; + + #[repr(C)] + pub struct au_tid_addr { + pub port: dev_t, + } + pub type au_tid_addr_t = au_tid_addr; + + #[repr(C)] + pub struct c_auditinfo_addr { + pub ai_auid: au_id_t, // Audit user ID + pub ai_mask: au_mask_t, // Audit masks. + pub ai_termid: au_tid_addr_t, // Terminal ID. + pub ai_asid: au_asid_t, // Audit session ID. + pub ai_flags: au_flag_t, // Audit session flags + } + pub type c_auditinfo_addr_t = c_auditinfo_addr; + + extern "C" { + pub fn getaudit(auditinfo_addr: *mut c_auditinfo_addr_t) -> c_int; + } } From 7acb9373a62270c5baee091019da88f43ebc16b8 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 14 Jun 2021 11:10:41 +0200 Subject: [PATCH 206/320] kill: fix signal table printing --- src/uu/kill/src/kill.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 7965c40a9..c48864564 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -129,22 +129,15 @@ fn handle_obsolete(mut args: Vec) -> (Vec, Option) { } fn table() { - let mut name_width = 0; - /* Compute the maximum width of a signal name. */ - for s in &ALL_SIGNALS { - if s.len() > name_width { - name_width = s.len() - } - } + let name_width = ALL_SIGNALS.iter().map(|n| n.len()).max().unwrap(); for (idx, signal) in ALL_SIGNALS.iter().enumerate() { - print!("{0: >#2} {1: <#8}", idx + 1, signal); - //TODO: obtain max signal width here - + print!("{0: >#2} {1: <#2$}", idx, signal, name_width + 2); if (idx + 1) % 7 == 0 { println!(); } } + println!() } fn print_signal(signal_name_or_value: &str) { From 13458b48066c5d8476a58b04685b56ff31925046 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 14 Jun 2021 11:39:26 +0200 Subject: [PATCH 207/320] sort: use values_of --- src/uu/sort/src/sort.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 006664193..bc5048e11 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1272,11 +1272,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.separator = Some(separator.chars().next().unwrap()) } - if matches.is_present(options::KEY) { - for key in &matches.args[options::KEY].vals { + if let Some(values) = matches.values_of(options::KEY) { + for value in values { settings .selectors - .push(FieldSelector::parse(&key.to_string_lossy(), &settings)); + .push(FieldSelector::parse(value, &settings)); } } From 25240ba61c1e3319840b72ef3be36fc295e13412 Mon Sep 17 00:00:00 2001 From: David Suilea Date: Sat, 12 Jun 2021 13:28:21 +0200 Subject: [PATCH 208/320] touch: change the error message to match the GNU error message #2346 --- src/uu/touch/src/touch.rs | 10 +++++++++- tests/by-util/test_touch.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index be4e51041..efa436c81 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -166,7 +166,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } if let Err(e) = File::create(path) { - show_warning!("cannot touch '{}': {}", path, e); + match e.kind() { + std::io::ErrorKind::NotFound => { + show_error!("cannot touch '{}': {}", path, "No such file or directory") + } + std::io::ErrorKind::PermissionDenied => { + show_error!("cannot touch '{}': {}", path, "Permission denied") + } + _ => show_error!("cannot touch '{}': {}", path, e), + } error_code = 1; continue; }; diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index c861a50dd..5e8114092 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -6,6 +6,7 @@ use self::touch::filetime::{self, FileTime}; extern crate time; use crate::common::util::*; +use std::path::PathBuf; fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) { let m = at.metadata(path); @@ -466,3 +467,37 @@ fn test_touch_trailing_slash() { let file = "no-file/"; ucmd.args(&[file]).fails(); } + +#[test] +fn test_touch_no_such_file_error_msg() { + let dirname = "nonexistent"; + let filename = "file"; + let path = PathBuf::from(dirname).join(filename); + let path_str = path.to_str().unwrap(); + + new_ucmd!().arg(&path).fails().stderr_only(format!( + "touch: cannot touch '{}': No such file or directory", + path_str + )); +} + +#[test] +#[cfg(unix)] +fn test_touch_permission_denied_error_msg() { + let (at, mut ucmd) = at_and_ucmd!(); + + let dirname = "dir_with_read_only_access"; + let filename = "file"; + let path = PathBuf::from(dirname).join(filename); + let path_str = path.to_str().unwrap(); + + // create dest without write permissions + at.mkdir(dirname); + at.set_readonly(dirname); + + let full_path = at.plus_as_string(path_str); + ucmd.arg(&full_path).fails().stderr_only(format!( + "touch: cannot touch '{}': Permission denied", + &full_path + )); +} From 22fbf16b2c62f7688f7ba1d51de7260ed179c3cb Mon Sep 17 00:00:00 2001 From: Daniel Rocco Date: Fri, 4 Jun 2021 23:16:48 -0400 Subject: [PATCH 209/320] test: implement user, group ownership checks closes #2337 --- src/uu/test/src/test.rs | 36 +++++++++++++++++++++------- tests/by-util/test_test.rs | 48 +++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index acf0f7eca..e30d7cf51 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -6,7 +6,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (vars) FiletestOp StrlenOp +// spell-checker:ignore (vars) egid euid FiletestOp StrlenOp mod parser; @@ -96,8 +96,10 @@ fn eval(stack: &mut Vec) -> Result { "-e" => path(&f, PathCondition::Exists), "-f" => path(&f, PathCondition::Regular), "-g" => path(&f, PathCondition::GroupIdFlag), + "-G" => path(&f, PathCondition::GroupOwns), "-h" => path(&f, PathCondition::SymLink), "-L" => path(&f, PathCondition::SymLink), + "-O" => path(&f, PathCondition::UserOwns), "-p" => path(&f, PathCondition::Fifo), "-r" => path(&f, PathCondition::Readable), "-S" => path(&f, PathCondition::Socket), @@ -166,7 +168,9 @@ enum PathCondition { Exists, Regular, GroupIdFlag, + GroupOwns, SymLink, + UserOwns, Fifo, Readable, Socket, @@ -190,18 +194,28 @@ fn path(path: &OsStr, condition: PathCondition) -> bool { Execute = 0o1, } - let perm = |metadata: Metadata, p: Permission| { + let geteuid = || { #[cfg(not(target_os = "redox"))] - let (uid, gid) = unsafe { (libc::getuid(), libc::getgid()) }; + let euid = unsafe { libc::geteuid() }; #[cfg(target_os = "redox")] - let (uid, gid) = ( - syscall::getuid().unwrap() as u32, - syscall::getgid().unwrap() as u32, - ); + let euid = syscall::geteuid().unwrap() as u32; - if uid == metadata.uid() { + euid + }; + + let getegid = || { + #[cfg(not(target_os = "redox"))] + let egid = unsafe { libc::getegid() }; + #[cfg(target_os = "redox")] + let egid = syscall::getegid().unwrap() as u32; + + egid + }; + + let perm = |metadata: Metadata, p: Permission| { + if geteuid() == metadata.uid() { metadata.mode() & ((p as u32) << 6) != 0 - } else if gid == metadata.gid() { + } else if getegid() == metadata.gid() { metadata.mode() & ((p as u32) << 3) != 0 } else { metadata.mode() & (p as u32) != 0 @@ -230,7 +244,9 @@ fn path(path: &OsStr, condition: PathCondition) -> bool { PathCondition::Exists => true, PathCondition::Regular => file_type.is_file(), PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0, + PathCondition::GroupOwns => metadata.gid() == getegid(), PathCondition::SymLink => metadata.file_type().is_symlink(), + PathCondition::UserOwns => metadata.uid() == geteuid(), PathCondition::Fifo => file_type.is_fifo(), PathCondition::Readable => perm(metadata, Permission::Read), PathCondition::Socket => file_type.is_socket(), @@ -257,7 +273,9 @@ fn path(path: &OsStr, condition: PathCondition) -> bool { PathCondition::Exists => true, PathCondition::Regular => stat.is_file(), PathCondition::GroupIdFlag => false, + PathCondition::GroupOwns => unimplemented!(), PathCondition::SymLink => false, + PathCondition::UserOwns => unimplemented!(), PathCondition::Fifo => false, PathCondition::Readable => false, // TODO PathCondition::Socket => false, diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index aaf09d657..8d41c5ead 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -8,7 +8,7 @@ // file that was distributed with this source code. // -// spell-checker:ignore (words) pseudofloat +// spell-checker:ignore (words) egid euid pseudofloat use crate::common::util::*; @@ -476,6 +476,52 @@ fn test_nonexistent_file_is_not_symlink() { .succeeds(); } +#[test] +#[cfg(not(windows))] +fn test_file_owned_by_euid() { + new_ucmd!().args(&["-O", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] +fn test_nonexistent_file_not_owned_by_euid() { + new_ucmd!() + .args(&["-O", "nonexistent_file"]) + .run() + .status_code(1); +} + +#[test] +#[cfg(all(not(windows), not(target_os = "freebsd")))] +fn test_file_not_owned_by_euid() { + new_ucmd!() + .args(&["-f", "/bin/sh", "-a", "!", "-O", "/bin/sh"]) + .succeeds(); +} + +#[test] +#[cfg(not(windows))] +fn test_file_owned_by_egid() { + new_ucmd!().args(&["-G", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] +fn test_nonexistent_file_not_owned_by_egid() { + new_ucmd!() + .args(&["-G", "nonexistent_file"]) + .run() + .status_code(1); +} + +#[test] +#[cfg(all(not(windows), not(target_os = "freebsd")))] +fn test_file_not_owned_by_egid() { + new_ucmd!() + .args(&["-f", "/bin/sh", "-a", "!", "-G", "/bin/sh"]) + .succeeds(); +} + #[test] fn test_op_precedence_and_or_1() { new_ucmd!().args(&[" ", "-o", "", "-a", ""]).succeeds(); From 956ff57e2e6b1e142e1047f001d856094bdb3401 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 9 Jun 2021 17:15:57 +0200 Subject: [PATCH 210/320] `sort`: delete temporary files as soon as possible - When we have finished reading from a temproary file, we can immediately delete it. - Use one single directory for all temporary files. - Only create the temporary directory when needed. - Also compress temporary files created by the merge step if requested. --- src/uu/sort/src/check.rs | 12 +- src/uu/sort/src/chunks.rs | 90 +++++----- src/uu/sort/src/ext_sort.rs | 228 ++++++++++++------------ src/uu/sort/src/merge.rs | 344 ++++++++++++++++++++++++++++++------ src/uu/sort/src/sort.rs | 2 +- 5 files changed, 449 insertions(+), 227 deletions(-) diff --git a/src/uu/sort/src/check.rs b/src/uu/sort/src/check.rs index a8e5a0d9b..8e37602e1 100644 --- a/src/uu/sort/src/check.rs +++ b/src/uu/sort/src/check.rs @@ -36,7 +36,7 @@ pub fn check(path: &str, settings: &GlobalSettings) -> i32 { for _ in 0..2 { recycled_sender .send(Chunk::new(vec![0; 100 * 1024], |_| Vec::new())) - .unwrap(); + .ok(); } let mut prev_chunk: Option = None; @@ -80,12 +80,11 @@ fn reader( sender: SyncSender, settings: &GlobalSettings, ) { - let mut sender = Some(sender); let mut carry_over = vec![]; for chunk in receiver.iter() { let (recycled_lines, recycled_buffer) = chunk.recycle(); - chunks::read( - &mut sender, + let should_continue = chunks::read( + &sender, recycled_buffer, None, &mut carry_over, @@ -98,6 +97,9 @@ fn reader( }, recycled_lines, settings, - ) + ); + if !should_continue { + break; + } } } diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index 3d996e6d6..d452401df 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -52,17 +52,17 @@ impl Chunk { /// Read a chunk, parse lines and send them. /// -/// No empty chunk will be sent. If we reach the end of the input, sender_option -/// is set to None. If this function however does not set sender_option to None, -/// it is not guaranteed that there is still input left: If the input fits _exactly_ -/// into a buffer, we will only notice that there's nothing more to read at the next -/// invocation. +/// No empty chunk will be sent. If we reach the end of the input, `false` is returned. +/// However, if this function returns `true`, it is not guaranteed that there is still +/// input left: If the input fits _exactly_ into a buffer, we will only notice that there's +/// nothing more to read at the next invocation. In case there is no input left, nothing will +/// be sent. /// /// # Arguments /// /// (see also `read_to_chunk` for a more detailed documentation) /// -/// * `sender_option`: The sender to send the lines to the sorter. If `None`, this function does nothing. +/// * `sender`: The sender to send the lines to the sorter. /// * `buffer`: The recycled buffer. All contents will be overwritten, but it must already be filled. /// (i.e. `buffer.len()` should be equal to `buffer.capacity()`) /// * `max_buffer_size`: How big `buffer` can be. @@ -73,52 +73,47 @@ impl Chunk { /// * `lines`: The recycled vector to fill with lines. Must be empty. /// * `settings`: The global settings. #[allow(clippy::too_many_arguments)] -#[allow(clippy::borrowed_box)] -pub fn read( - sender_option: &mut Option>, +pub fn read( + sender: &SyncSender, mut buffer: Vec, max_buffer_size: Option, carry_over: &mut Vec, - file: &mut Box, - next_files: &mut impl Iterator>, + file: &mut T, + next_files: &mut impl Iterator, separator: u8, lines: Vec>, settings: &GlobalSettings, -) { +) -> bool { assert!(lines.is_empty()); - if let Some(sender) = sender_option { - if buffer.len() < carry_over.len() { - buffer.resize(carry_over.len() + 10 * 1024, 0); - } - buffer[..carry_over.len()].copy_from_slice(carry_over); - let (read, should_continue) = read_to_buffer( - file, - next_files, - &mut buffer, - max_buffer_size, - carry_over.len(), - separator, - ); - carry_over.clear(); - carry_over.extend_from_slice(&buffer[read..]); - - if read != 0 { - let payload = Chunk::new(buffer, |buf| { - let mut lines = unsafe { - // SAFETY: It is safe to transmute to a vector of lines with shorter lifetime, - // because it was only temporarily transmuted to a Vec> to make recycling possible. - std::mem::transmute::>, Vec>>(lines) - }; - let read = crash_if_err!(1, std::str::from_utf8(&buf[..read])); - parse_lines(read, &mut lines, separator, settings); - lines - }); - sender.send(payload).unwrap(); - } - if !should_continue { - *sender_option = None; - } + if buffer.len() < carry_over.len() { + buffer.resize(carry_over.len() + 10 * 1024, 0); } + buffer[..carry_over.len()].copy_from_slice(carry_over); + let (read, should_continue) = read_to_buffer( + file, + next_files, + &mut buffer, + max_buffer_size, + carry_over.len(), + separator, + ); + carry_over.clear(); + carry_over.extend_from_slice(&buffer[read..]); + + if read != 0 { + let payload = Chunk::new(buffer, |buf| { + let mut lines = unsafe { + // SAFETY: It is safe to transmute to a vector of lines with shorter lifetime, + // because it was only temporarily transmuted to a Vec> to make recycling possible. + std::mem::transmute::>, Vec>>(lines) + }; + let read = crash_if_err!(1, std::str::from_utf8(&buf[..read])); + parse_lines(read, &mut lines, separator, settings); + lines + }); + sender.send(payload).unwrap(); + } + should_continue } /// Split `read` into `Line`s, and add them to `lines`. @@ -165,10 +160,9 @@ fn parse_lines<'a>( /// The remaining bytes must be copied to the start of the buffer for the next invocation, /// if another invocation is necessary, which is determined by the other return value. /// * Whether this function should be called again. -#[allow(clippy::borrowed_box)] -fn read_to_buffer( - file: &mut Box, - next_files: &mut impl Iterator>, +fn read_to_buffer( + file: &mut T, + next_files: &mut impl Iterator, buffer: &mut Vec, max_buffer_size: Option, start_offset: usize, diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index c439adcdc..7d39b13a2 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -12,14 +12,10 @@ //! The buffers for the individual chunks are recycled. There are two buffers. use std::cmp::Ordering; -use std::fs::File; -use std::io::BufReader; -use std::io::{BufWriter, Write}; +use std::io::Write; use std::path::Path; -use std::process::Child; -use std::process::{Command, Stdio}; +use std::path::PathBuf; use std::{ - fs::OpenOptions, io::Read, sync::mpsc::{Receiver, SyncSender}, thread, @@ -27,85 +23,91 @@ use std::{ use itertools::Itertools; -use tempfile::TempDir; - +use crate::merge::ClosedTmpFile; +use crate::merge::WriteableCompressedTmpFile; +use crate::merge::WriteablePlainTmpFile; +use crate::merge::WriteableTmpFile; use crate::Line; use crate::{ chunks::{self, Chunk}, compare_by, merge, output_sorted_lines, sort_by, GlobalSettings, }; +use tempfile::TempDir; const START_BUFFER_SIZE: usize = 8_000; /// Sort files by using auxiliary files for storing intermediate chunks (if needed), and output the result. pub fn ext_sort(files: &mut impl Iterator>, settings: &GlobalSettings) { - let tmp_dir = crash_if_err!( - 1, - tempfile::Builder::new() - .prefix("uutils_sort") - .tempdir_in(&settings.tmp_dir) - ); let (sorted_sender, sorted_receiver) = std::sync::mpsc::sync_channel(1); let (recycled_sender, recycled_receiver) = std::sync::mpsc::sync_channel(1); thread::spawn({ let settings = settings.clone(); move || sorter(recycled_receiver, sorted_sender, settings) }); - let read_result = reader_writer( + if settings.compress_prog.is_some() { + reader_writer::<_, WriteableCompressedTmpFile>( + files, + settings, + sorted_receiver, + recycled_sender, + ); + } else { + reader_writer::<_, WriteablePlainTmpFile>( + files, + settings, + sorted_receiver, + recycled_sender, + ); + } +} + +fn reader_writer>, Tmp: WriteableTmpFile + 'static>( + files: F, + settings: &GlobalSettings, + receiver: Receiver, + sender: SyncSender, +) { + let separator = if settings.zero_terminated { + b'\0' + } else { + b'\n' + }; + + // Heuristically chosen: Dividing by 10 seems to keep our memory usage roughly + // around settings.buffer_size as a whole. + let buffer_size = settings.buffer_size / 10; + let read_result: ReadResult = read_write_loop( files, - &tmp_dir, - if settings.zero_terminated { - b'\0' - } else { - b'\n' - }, + &settings.tmp_dir, + separator, // Heuristically chosen: Dividing by 10 seems to keep our memory usage roughly // around settings.buffer_size as a whole. - settings.buffer_size / 10, - settings.clone(), - sorted_receiver, - recycled_sender, + buffer_size, + &settings, + receiver, + sender, ); match read_result { - ReadResult::WroteChunksToFile { chunks_written } => { - let mut children = Vec::new(); - let files = (0..chunks_written).map(|chunk_num| { - let file_path = tmp_dir.path().join(chunk_num.to_string()); - let file = File::open(file_path).unwrap(); - if let Some(compress_prog) = &settings.compress_prog { - let mut command = Command::new(compress_prog); - command.stdin(file).stdout(Stdio::piped()).arg("-d"); - let mut child = crash_if_err!( - 2, - command.spawn().map_err(|err| format!( - "couldn't execute compress program: errno {}", - err.raw_os_error().unwrap() - )) - ); - let child_stdout = child.stdout.take().unwrap(); - children.push(child); - Box::new(BufReader::new(child_stdout)) as Box - } else { - Box::new(BufReader::new(file)) as Box - } - }); - let mut merger = merge::merge_with_file_limit(files, settings); - for child in children { - assert_child_success(child, settings.compress_prog.as_ref().unwrap()); - } - merger.write_all(settings); + ReadResult::WroteChunksToFile { tmp_files, tmp_dir } => { + let tmp_dir_size = tmp_files.len(); + let mut merger = merge::merge_with_file_limit::<_, _, Tmp>( + tmp_files.into_iter().map(|c| c.reopen()), + &settings, + Some((tmp_dir, tmp_dir_size)), + ); + merger.write_all(&settings); } ReadResult::SortedSingleChunk(chunk) => { - output_sorted_lines(chunk.borrow_lines().iter(), settings); + output_sorted_lines(chunk.borrow_lines().iter(), &settings); } ReadResult::SortedTwoChunks([a, b]) => { let merged_iter = a .borrow_lines() .iter() .merge_by(b.borrow_lines().iter(), |line_a, line_b| { - compare_by(line_a, line_b, settings) != Ordering::Greater + compare_by(line_a, line_b, &settings) != Ordering::Greater }); - output_sorted_lines(merged_iter, settings); + output_sorted_lines(merged_iter, &settings); } ReadResult::EmptyInput => { // don't output anything @@ -122,7 +124,7 @@ fn sorter(receiver: Receiver, sender: SyncSender, settings: Global } /// Describes how we read the chunks from the input. -enum ReadResult { +enum ReadResult { /// The input was empty. Nothing was read. EmptyInput, /// The input fits into a single Chunk, which was kept in memory. @@ -131,33 +133,27 @@ enum ReadResult { SortedTwoChunks([Chunk; 2]), /// The input was read into multiple chunks, which were written to auxiliary files. WroteChunksToFile { - /// The number of chunks written to auxiliary files. - chunks_written: usize, + tmp_files: Vec, + tmp_dir: TempDir, }, } - /// The function that is executed on the reader/writer thread. -/// -/// # Returns -/// * The number of chunks read. -fn reader_writer( +fn read_write_loop( mut files: impl Iterator>, - tmp_dir: &TempDir, + tmp_dir_parent: &Path, separator: u8, buffer_size: usize, - settings: GlobalSettings, + settings: &GlobalSettings, receiver: Receiver, sender: SyncSender, -) -> ReadResult { - let mut sender_option = Some(sender); - +) -> ReadResult { let mut file = files.next().unwrap(); let mut carry_over = vec![]; // kick things off with two reads for _ in 0..2 { - chunks::read( - &mut sender_option, + let should_continue = chunks::read( + &sender, vec![ 0; if START_BUFFER_SIZE < buffer_size { @@ -172,9 +168,11 @@ fn reader_writer( &mut files, separator, Vec::new(), - &settings, + settings, ); - if sender_option.is_none() { + + if !should_continue { + drop(sender); // We have already read the whole input. Since we are in our first two reads, // this means that we can fit the whole input into memory. Bypass writing below and // handle this case in a more straightforward way. @@ -190,68 +188,69 @@ fn reader_writer( } } + let tmp_dir = crash_if_err!( + 1, + tempfile::Builder::new() + .prefix("uutils_sort") + .tempdir_in(tmp_dir_parent) + ); + + let mut sender_option = Some(sender); let mut file_number = 0; + let mut tmp_files = vec![]; loop { let mut chunk = match receiver.recv() { Ok(it) => it, _ => { - return ReadResult::WroteChunksToFile { - chunks_written: file_number, - } + return ReadResult::WroteChunksToFile { tmp_files, tmp_dir }; } }; - write( + let tmp_file = write::( &mut chunk, - &tmp_dir.path().join(file_number.to_string()), + tmp_dir.path().join(file_number.to_string()), settings.compress_prog.as_deref(), separator, ); + tmp_files.push(tmp_file); file_number += 1; let (recycled_lines, recycled_buffer) = chunk.recycle(); - chunks::read( - &mut sender_option, - recycled_buffer, - None, - &mut carry_over, - &mut file, - &mut files, - separator, - recycled_lines, - &settings, - ); + if let Some(sender) = &sender_option { + let should_continue = chunks::read( + &sender, + recycled_buffer, + None, + &mut carry_over, + &mut file, + &mut files, + separator, + recycled_lines, + settings, + ); + if !should_continue { + sender_option = None; + } + } } } /// Write the lines in `chunk` to `file`, separated by `separator`. /// `compress_prog` is used to optionally compress file contents. -fn write(chunk: &mut Chunk, file: &Path, compress_prog: Option<&str>, separator: u8) { +fn write( + chunk: &mut Chunk, + file: PathBuf, + compress_prog: Option<&str>, + separator: u8, +) -> I::Closed { chunk.with_lines_mut(|lines| { // Write the lines to the file - let file = crash_if_err!(1, OpenOptions::new().create(true).write(true).open(file)); - if let Some(compress_prog) = compress_prog { - let mut command = Command::new(compress_prog); - command.stdin(Stdio::piped()).stdout(file); - let mut child = crash_if_err!( - 2, - command.spawn().map_err(|err| format!( - "couldn't execute compress program: errno {}", - err.raw_os_error().unwrap() - )) - ); - let mut writer = BufWriter::new(child.stdin.take().unwrap()); - write_lines(lines, &mut writer, separator); - writer.flush().unwrap(); - drop(writer); - assert_child_success(child, compress_prog); - } else { - let mut writer = BufWriter::new(file); - write_lines(lines, &mut writer, separator); - }; - }); + let mut tmp_file = I::create(file, compress_prog); + write_lines(lines, tmp_file.as_write(), separator); + tmp_file.finished_writing() + }) } fn write_lines<'a, T: Write>(lines: &[Line<'a>], writer: &mut T, separator: u8) { @@ -260,12 +259,3 @@ fn write_lines<'a, T: Write>(lines: &[Line<'a>], writer: &mut T, separator: u8) crash_if_err!(1, writer.write_all(&[separator])); } } - -fn assert_child_success(mut child: Child, program: &str) { - if !matches!( - child.wait().map(|e| e.code()), - Ok(Some(0)) | Ok(None) | Err(_) - ) { - crash!(2, "'{}' terminated abnormally", program) - } -} diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index 478b454b6..173faaffc 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -9,9 +9,11 @@ use std::{ cmp::Ordering, - fs::File, + fs::{self, File}, io::{BufWriter, Read, Write}, iter, + path::PathBuf, + process::{Child, ChildStdin, ChildStdout, Command, Stdio}, rc::Rc, sync::mpsc::{channel, sync_channel, Receiver, Sender, SyncSender}, thread, @@ -19,61 +21,94 @@ use std::{ use compare::Compare; use itertools::Itertools; +use tempfile::TempDir; use crate::{ chunks::{self, Chunk}, compare_by, GlobalSettings, }; -// Merge already sorted files. -pub fn merge_with_file_limit>>( - files: F, +/// Merge pre-sorted `Box`s. +/// +/// If `settings.merge_batch_size` is greater than the length of `files`, intermediate files will be used. +/// If `settings.compress_prog` is `Some`, intermediate files will be compressed with it. +pub fn merge>>( + files: Files, settings: &GlobalSettings, +) -> FileMerger { + if settings.compress_prog.is_none() { + merge_with_file_limit::<_, _, WriteablePlainTmpFile>( + files.map(|file| PlainMergeInput { inner: file }), + settings, + None, + ) + } else { + merge_with_file_limit::<_, _, WriteableCompressedTmpFile>( + files.map(|file| PlainMergeInput { inner: file }), + settings, + None, + ) + } +} + +// Merge already sorted `MergeInput`s. +pub fn merge_with_file_limit< + M: MergeInput + 'static, + F: ExactSizeIterator, + Tmp: WriteableTmpFile + 'static, +>( + files: F, + settings: &GlobalSettings, + tmp_dir: Option<(TempDir, usize)>, ) -> FileMerger { if files.len() > settings.merge_batch_size { - let tmp_dir = tempfile::Builder::new() - .prefix("uutils_sort") - .tempdir_in(&settings.tmp_dir) - .unwrap(); - let mut batch_number = 0; + // If we did not get a tmp_dir, create one. + let (tmp_dir, mut tmp_dir_size) = tmp_dir.unwrap_or_else(|| { + ( + tempfile::Builder::new() + .prefix("uutils_sort") + .tempdir_in(&settings.tmp_dir) + .unwrap(), + 0, + ) + }); let mut remaining_files = files.len(); let batches = files.chunks(settings.merge_batch_size); let mut batches = batches.into_iter(); - while batch_number + remaining_files > settings.merge_batch_size && remaining_files != 0 { + let mut temporary_files = vec![]; + while remaining_files != 0 { + // Work around the fact that `Chunks` is not an `ExactSizeIterator`. remaining_files = remaining_files.saturating_sub(settings.merge_batch_size); let mut merger = merge_without_limit(batches.next().unwrap(), settings); - let tmp_file = File::create(tmp_dir.path().join(batch_number.to_string())).unwrap(); - merger.write_all_to(settings, &mut BufWriter::new(tmp_file)); - batch_number += 1; - } - let batch_files = (0..batch_number).map(|n| { - Box::new(File::open(tmp_dir.path().join(n.to_string())).unwrap()) - as Box - }); - if batch_number > settings.merge_batch_size { - assert!(batches.next().is_none()); - merge_with_file_limit( - Box::new(batch_files) as Box>>, - settings, - ) - } else { - let final_batch = batches.next(); - assert!(batches.next().is_none()); - merge_without_limit( - batch_files.chain(final_batch.into_iter().flatten()), - settings, - ) + let mut tmp_file = Tmp::create( + tmp_dir.path().join(tmp_dir_size.to_string()), + settings.compress_prog.as_deref(), + ); + tmp_dir_size += 1; + merger.write_all_to(settings, tmp_file.as_write()); + temporary_files.push(tmp_file.finished_writing()); } + assert!(batches.next().is_none()); + merge_with_file_limit::<_, _, Tmp>( + temporary_files + .into_iter() + .map(Box::new(|c: Tmp::Closed| c.reopen()) + as Box< + dyn FnMut(Tmp::Closed) -> ::Reopened, + >), + settings, + Some((tmp_dir, tmp_dir_size)), + ) } else { merge_without_limit(files, settings) } } -/// Merge files without limiting how many files are concurrently open +/// Merge files without limiting how many files are concurrently open. /// /// It is the responsibility of the caller to ensure that `files` yields only /// as many files as we are allowed to open concurrently. -fn merge_without_limit>>( +fn merge_without_limit>( files: F, settings: &GlobalSettings, ) -> FileMerger { @@ -83,16 +118,18 @@ fn merge_without_limit>>( for (file_number, file) in files.enumerate() { let (sender, receiver) = sync_channel(2); loaded_receivers.push(receiver); - reader_files.push(ReaderFile { + reader_files.push(Some(ReaderFile { file, - sender: Some(sender), + sender, carry_over: vec![], - }); + })); + // Send the initial chunk to trigger a read for each file request_sender .send((file_number, Chunk::new(vec![0; 8 * 1024], |_| Vec::new()))) .unwrap(); } + // Send the second chunk for each file for file_number in 0..reader_files.len() { request_sender .send((file_number, Chunk::new(vec![0; 8 * 1024], |_| Vec::new()))) @@ -136,37 +173,45 @@ fn merge_without_limit>>( } } /// The struct on the reader thread representing an input file -struct ReaderFile { - file: Box, - sender: Option>, +struct ReaderFile { + file: M, + sender: SyncSender, carry_over: Vec, } /// The function running on the reader thread. fn reader( recycled_receiver: Receiver<(usize, Chunk)>, - files: &mut [ReaderFile], + files: &mut [Option>], settings: &GlobalSettings, separator: u8, ) { for (file_idx, chunk) in recycled_receiver.iter() { let (recycled_lines, recycled_buffer) = chunk.recycle(); - let ReaderFile { + if let Some(ReaderFile { file, sender, carry_over, - } = &mut files[file_idx]; - chunks::read( - sender, - recycled_buffer, - None, - carry_over, - file, - &mut iter::empty(), - separator, - recycled_lines, - settings, - ); + }) = &mut files[file_idx] + { + let should_continue = chunks::read( + sender, + recycled_buffer, + None, + carry_over, + file.as_read(), + &mut iter::empty(), + separator, + recycled_lines, + settings, + ); + if !should_continue { + // Remove the file from the list by replacing it with `None`. + let ReaderFile { file, .. } = files[file_idx].take().unwrap(); + // Depending on the kind of the `MergeInput`, this may delete the file: + file.finished_reading(); + } + } } } /// The struct on the main thread representing an input file @@ -241,11 +286,14 @@ impl<'a> FileMerger<'a> { self.heap.pop(); } } else { + // This will cause the comparison to use a different line and the heap to readjust. self.heap.peek_mut().unwrap().line_idx += 1; } if let Some(prev) = prev { if let Ok(prev_chunk) = Rc::try_unwrap(prev.chunk) { + // If nothing is referencing the previous chunk anymore, this means that the previous line + // was the last line of the chunk. We can recycle the chunk. self.request_sender .send((prev.file_number, prev_chunk)) .ok(); @@ -273,7 +321,195 @@ impl<'a> Compare for FileComparator<'a> { // as lines from a file with a lower number are to be considered "earlier". cmp = a.file_number.cmp(&b.file_number); } - // Our BinaryHeap is a max heap. We use it as a min heap, so we need to reverse the ordering. + // BinaryHeap is a max heap. We use it as a min heap, so we need to reverse the ordering. cmp.reverse() } } + +// Wait for the child to exit and check its exit code. +fn assert_child_success(mut child: Child, program: &str) { + if !matches!( + child.wait().map(|e| e.code()), + Ok(Some(0)) | Ok(None) | Err(_) + ) { + crash!(2, "'{}' terminated abnormally", program) + } +} + +/// A temporary file that can be written to. +pub trait WriteableTmpFile { + type Closed: ClosedTmpFile; + type InnerWrite: Write; + fn create(path: PathBuf, compress_prog: Option<&str>) -> Self; + /// Closes the temporary file. + fn finished_writing(self) -> Self::Closed; + fn as_write(&mut self) -> &mut Self::InnerWrite; +} +/// A temporary file that is (temporarily) closed, but can be reopened. +pub trait ClosedTmpFile { + type Reopened: MergeInput; + /// Reopens the temporary file. + fn reopen(self) -> Self::Reopened; +} +/// A pre-sorted input for merging. +pub trait MergeInput: Send { + type InnerRead: Read; + /// Cleans this `MergeInput` up. + /// Implementations may delete the backing file. + fn finished_reading(self); + fn as_read(&mut self) -> &mut Self::InnerRead; +} + +pub struct WriteablePlainTmpFile { + path: PathBuf, + file: BufWriter, +} +pub struct ClosedPlainTmpFile { + path: PathBuf, +} +pub struct PlainTmpMergeInput { + path: PathBuf, + file: File, +} +impl WriteableTmpFile for WriteablePlainTmpFile { + type Closed = ClosedPlainTmpFile; + type InnerWrite = BufWriter; + + fn create(path: PathBuf, _: Option<&str>) -> Self { + WriteablePlainTmpFile { + file: BufWriter::new(File::create(&path).unwrap()), + path, + } + } + + fn finished_writing(self) -> Self::Closed { + ClosedPlainTmpFile { path: self.path } + } + + fn as_write(&mut self) -> &mut Self::InnerWrite { + &mut self.file + } +} +impl ClosedTmpFile for ClosedPlainTmpFile { + type Reopened = PlainTmpMergeInput; + fn reopen(self) -> Self::Reopened { + PlainTmpMergeInput { + file: File::open(&self.path).unwrap(), + path: self.path, + } + } +} +impl MergeInput for PlainTmpMergeInput { + type InnerRead = File; + + fn finished_reading(self) { + fs::remove_file(self.path).ok(); + } + + fn as_read(&mut self) -> &mut Self::InnerRead { + &mut self.file + } +} + +pub struct WriteableCompressedTmpFile { + path: PathBuf, + compress_prog: String, + child: Child, + child_stdin: BufWriter, +} +pub struct ClosedCompressedTmpFile { + path: PathBuf, + compress_prog: String, +} +pub struct CompressedTmpMergeInput { + path: PathBuf, + compress_prog: String, + child: Child, + child_stdout: ChildStdout, +} +impl WriteableTmpFile for WriteableCompressedTmpFile { + type Closed = ClosedCompressedTmpFile; + type InnerWrite = BufWriter; + + fn create(path: PathBuf, compress_prog: Option<&str>) -> Self { + let compress_prog = compress_prog.unwrap(); + let mut command = Command::new(compress_prog); + command + .stdin(Stdio::piped()) + .stdout(File::create(&path).unwrap()); + let mut child = crash_if_err!( + 2, + command.spawn().map_err(|err| format!( + "couldn't execute compress program: errno {}", + err.raw_os_error().unwrap() + )) + ); + let child_stdin = child.stdin.take().unwrap(); + WriteableCompressedTmpFile { + path, + compress_prog: compress_prog.to_owned(), + child, + child_stdin: BufWriter::new(child_stdin), + } + } + + fn finished_writing(self) -> Self::Closed { + drop(self.child_stdin); + assert_child_success(self.child, &self.compress_prog); + ClosedCompressedTmpFile { + path: self.path, + compress_prog: self.compress_prog, + } + } + + fn as_write(&mut self) -> &mut Self::InnerWrite { + &mut self.child_stdin + } +} +impl ClosedTmpFile for ClosedCompressedTmpFile { + type Reopened = CompressedTmpMergeInput; + + fn reopen(self) -> Self::Reopened { + let mut command = Command::new(&self.compress_prog); + let file = File::open(&self.path).unwrap(); + command.stdin(file).stdout(Stdio::piped()).arg("-d"); + let mut child = crash_if_err!( + 2, + command.spawn().map_err(|err| format!( + "couldn't execute compress program: errno {}", + err.raw_os_error().unwrap() + )) + ); + let child_stdout = child.stdout.take().unwrap(); + CompressedTmpMergeInput { + path: self.path, + compress_prog: self.compress_prog, + child, + child_stdout, + } + } +} +impl MergeInput for CompressedTmpMergeInput { + type InnerRead = ChildStdout; + + fn finished_reading(self) { + drop(self.child_stdout); + assert_child_success(self.child, &self.compress_prog); + fs::remove_file(self.path).ok(); + } + + fn as_read(&mut self) -> &mut Self::InnerRead { + &mut self.child_stdout + } +} + +pub struct PlainMergeInput { + inner: R, +} +impl MergeInput for PlainMergeInput { + type InnerRead = R; + fn finished_reading(self) {} + fn as_read(&mut self) -> &mut Self::InnerRead { + &mut self.inner + } +} diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index bc5048e11..ccf129d39 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1313,7 +1313,7 @@ fn output_sorted_lines<'a>(iter: impl Iterator>, settings: & fn exec(files: &[String], settings: &GlobalSettings) -> i32 { if settings.merge { - let mut file_merger = merge::merge_with_file_limit(files.iter().map(open), settings); + let mut file_merger = merge::merge(files.iter().map(open), settings); file_merger.write_all(settings); } else if settings.check { if files.len() > 1 { From 6a3c1c19d9f5d8128c49e64e9389b7a2bbfee44b Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 9 Jun 2021 20:33:00 +0200 Subject: [PATCH 211/320] sort: remove needless allow --- src/uu/sort/src/sort.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index ccf129d39..4964f6514 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1516,8 +1516,6 @@ fn get_hash(t: &T) -> u64 { } fn random_shuffle(a: &str, b: &str, salt: &str) -> Ordering { - #![allow(clippy::comparison_chain)] - let da = get_hash(&[a, salt].concat()); let db = get_hash(&[b, salt].concat()); From 047ced2c7f2b6a44db2870ee8ca9e14d682fc954 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 9 Jun 2021 20:34:54 +0200 Subject: [PATCH 212/320] sort: increase default merge batch size I think we can attempt to open 32 files concurrently on all systems without risking resource exhaustion. --- src/uu/sort/src/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 4964f6514..4e865b208 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -236,7 +236,7 @@ impl Default for GlobalSettings { buffer_size: DEFAULT_BUF_SIZE, tmp_dir: PathBuf::new(), compress_prog: None, - merge_batch_size: 16, + merge_batch_size: 32, } } } From 6979b707c06c5cf20174946cd7418504385adb72 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 10 Jun 2021 21:45:58 +0200 Subject: [PATCH 213/320] sort: fix clippy lints --- src/uu/sort/src/ext_sort.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 7d39b13a2..022c57741 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -83,7 +83,7 @@ fn reader_writer>, Tmp: WriteableTmpFile // Heuristically chosen: Dividing by 10 seems to keep our memory usage roughly // around settings.buffer_size as a whole. buffer_size, - &settings, + settings, receiver, sender, ); @@ -92,22 +92,22 @@ fn reader_writer>, Tmp: WriteableTmpFile let tmp_dir_size = tmp_files.len(); let mut merger = merge::merge_with_file_limit::<_, _, Tmp>( tmp_files.into_iter().map(|c| c.reopen()), - &settings, + settings, Some((tmp_dir, tmp_dir_size)), ); - merger.write_all(&settings); + merger.write_all(settings); } ReadResult::SortedSingleChunk(chunk) => { - output_sorted_lines(chunk.borrow_lines().iter(), &settings); + output_sorted_lines(chunk.borrow_lines().iter(), settings); } ReadResult::SortedTwoChunks([a, b]) => { let merged_iter = a .borrow_lines() .iter() .merge_by(b.borrow_lines().iter(), |line_a, line_b| { - compare_by(line_a, line_b, &settings) != Ordering::Greater + compare_by(line_a, line_b, settings) != Ordering::Greater }); - output_sorted_lines(merged_iter, &settings); + output_sorted_lines(merged_iter, settings); } ReadResult::EmptyInput => { // don't output anything @@ -220,7 +220,7 @@ fn read_write_loop( if let Some(sender) = &sender_option { let should_continue = chunks::read( - &sender, + sender, recycled_buffer, None, &mut carry_over, From 4bd556d58e0121140f28fe63aa8f80c52a12521d Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 14 Jun 2021 11:22:26 +0200 Subject: [PATCH 214/320] sort: better convey that the return value should be ignored --- src/uu/sort/src/check.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/uu/sort/src/check.rs b/src/uu/sort/src/check.rs index 8e37602e1..f53e4edb4 100644 --- a/src/uu/sort/src/check.rs +++ b/src/uu/sort/src/check.rs @@ -34,9 +34,7 @@ pub fn check(path: &str, settings: &GlobalSettings) -> i32 { move || reader(file, recycled_receiver, loaded_sender, &settings) }); for _ in 0..2 { - recycled_sender - .send(Chunk::new(vec![0; 100 * 1024], |_| Vec::new())) - .ok(); + let _ = recycled_sender.send(Chunk::new(vec![0; 100 * 1024], |_| Vec::new())); } let mut prev_chunk: Option = None; @@ -55,7 +53,7 @@ pub fn check(path: &str, settings: &GlobalSettings) -> i32 { } return 1; } - recycled_sender.send(prev_chunk).ok(); + let _ = recycled_sender.send(prev_chunk); } for (a, b) in chunk.borrow_lines().iter().tuple_windows() { From d4914b694344fa3c058249531362f7d688ddcaa2 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 14 Jun 2021 11:33:42 +0200 Subject: [PATCH 215/320] sort: add a test for --batch-size --- tests/by-util/test_sort.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index d0af7a9c9..02d9fe92d 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -897,3 +897,19 @@ fn test_merge_batches() { .succeeds() .stdout_only_fixture("ext_sort.expected"); } + +#[test] +fn test_merge_batch_size() { + new_ucmd!() + .arg("--batch-size=2") + .arg("-m") + .arg("--unique") + .arg("merge_ints_interleaved_1.txt") + .arg("merge_ints_interleaved_2.txt") + .arg("merge_ints_interleaved_3.txt") + .arg("merge_ints_interleaved_3.txt") + .arg("merge_ints_interleaved_2.txt") + .arg("merge_ints_interleaved_1.txt") + .succeeds() + .stdout_only_fixture("merge_ints_interleaved.expected"); +} From 996e1b8539456b033b3f96607f2a0a6c1afb19ea Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Tue, 15 Jun 2021 22:13:52 +0200 Subject: [PATCH 216/320] uucore/entries: fix `getgrouplist` wrapper to handle a bug in macOS's `getgrouplist` implementation * add documentation --- src/uucore/src/lib/features/entries.rs | 60 ++++++++++++++++++++------ src/uucore/src/lib/features/process.rs | 4 ++ 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index bc4166346..6b986e616 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -47,6 +47,9 @@ use std::io::Result as IOResult; use std::ptr; extern "C" { + /// From: https://man7.org/linux/man-pages/man3/getgrouplist.3.html + /// > The getgrouplist() function scans the group database to obtain + /// > the list of groups that user belongs to. fn getgrouplist( name: *const c_char, gid: gid_t, @@ -55,6 +58,13 @@ extern "C" { ) -> c_int; } +/// From: https://man7.org/linux/man-pages/man2/getgroups.2.html +/// > getgroups() returns the supplementary group IDs of the calling +/// > process in list. +/// > If size is zero, list is not modified, but the total number of +/// > supplementary group IDs for the process is returned. This allows +/// > the caller to determine the size of a dynamically allocated list +/// > to be used in a further call to getgroups(). pub fn get_groups() -> IOResult> { let ngroups = unsafe { getgroups(0, ptr::null_mut()) }; if ngroups == -1 { @@ -83,17 +93,17 @@ pub fn get_groups() -> IOResult> { /// for `id --groups --real` if `gid` and `egid` are not equal. /// /// From: https://www.man7.org/linux/man-pages/man3/getgroups.3p.html -/// As implied by the definition of supplementary groups, the -/// effective group ID may appear in the array returned by -/// getgroups() or it may be returned only by getegid(). Duplication -/// may exist, but the application needs to call getegid() to be sure -/// of getting all of the information. Various implementation -/// variations and administrative sequences cause the set of groups -/// appearing in the result of getgroups() to vary in order and as to -/// whether the effective group ID is included, even when the set of -/// groups is the same (in the mathematical sense of ``set''). (The -/// history of a process and its parents could affect the details of -/// the result.) +/// > As implied by the definition of supplementary groups, the +/// > effective group ID may appear in the array returned by +/// > getgroups() or it may be returned only by getegid(). Duplication +/// > may exist, but the application needs to call getegid() to be sure +/// > of getting all of the information. Various implementation +/// > variations and administrative sequences cause the set of groups +/// > appearing in the result of getgroups() to vary in order and as to +/// > whether the effective group ID is included, even when the set of +/// > groups is the same (in the mathematical sense of ``set''). (The +/// > history of a process and its parents could affect the details of +/// > the result.) #[cfg(all(unix, feature = "process"))] pub fn get_groups_gnu(arg_id: Option) -> IOResult> { let groups = get_groups()?; @@ -184,16 +194,38 @@ impl Passwd { self.inner } + /// This is a wrapper function for `libc::getgrouplist`. + /// + /// From: https://man7.org/linux/man-pages/man3/getgrouplist.3.html + /// > If the number of groups of which user is a member is less than or + /// > equal to *ngroups, then the value *ngroups is returned. + /// > If the user is a member of more than *ngroups groups, then + /// > getgrouplist() returns -1. In this case, the value returned in + /// > *ngroups can be used to resize the buffer passed to a further + /// > call getgrouplist(). + /// + /// However, on macOS/darwin (and maybe others?) `getgrouplist` does + /// not update `ngroups` if `ngroups` is too small. Therefore, if not + /// updated by `getgrouplist`, `ngroups` needs to be increased in a + /// loop until `getgrouplist` stops returning -1. pub fn belongs_to(&self) -> Vec { let mut ngroups: c_int = 8; + let mut ngroups_old: c_int; let mut groups = Vec::with_capacity(ngroups as usize); let gid = self.inner.pw_gid; let name = self.inner.pw_name; - unsafe { - if getgrouplist(name, gid, groups.as_mut_ptr(), &mut ngroups) == -1 { + loop { + ngroups_old = ngroups; + if unsafe { getgrouplist(name, gid, groups.as_mut_ptr(), &mut ngroups) } == -1 { + if ngroups == ngroups_old { + ngroups *= 2; + } groups.resize(ngroups as usize, 0); - getgrouplist(name, gid, groups.as_mut_ptr(), &mut ngroups); + } else { + break; } + } + unsafe { groups.set_len(ngroups as usize); } groups.truncate(ngroups as usize); diff --git a/src/uucore/src/lib/features/process.rs b/src/uucore/src/lib/features/process.rs index cda41bb4f..21bfa992c 100644 --- a/src/uucore/src/lib/features/process.rs +++ b/src/uucore/src/lib/features/process.rs @@ -17,18 +17,22 @@ use std::process::ExitStatus as StdExitStatus; use std::thread; use std::time::{Duration, Instant}; +/// `geteuid()` returns the effective user ID of the calling process. pub fn geteuid() -> uid_t { unsafe { libc::geteuid() } } +/// `getegid()` returns the effective group ID of the calling process. pub fn getegid() -> gid_t { unsafe { libc::getegid() } } +/// `getgid()` returns the real group ID of the calling process. pub fn getgid() -> gid_t { unsafe { libc::getgid() } } +/// `getuid()` returns the real user ID of the calling process. pub fn getuid() -> uid_t { unsafe { libc::getuid() } } From 84affa2137feecedc997b5b865bb5690705f1599 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 15 Jun 2021 21:52:47 +0200 Subject: [PATCH 217/320] touch: support `@` date format parse `@` as a valid date. --- src/uu/touch/src/touch.rs | 4 ++++ tests/by-util/test_touch.rs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index efa436c81..2e1c3c8e8 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -268,6 +268,10 @@ fn parse_date(str: &str) -> FileTime { return local_tm_to_filetime(to_local(tm)); } } + if let Ok(tm) = time::strptime(str, "@%s") { + // Don't convert to local time in this case - seconds since epoch are not time-zone dependent + return local_tm_to_filetime(tm); + } show_error!("Unable to parse date: {}\n", str); process::exit(1); } diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 5e8114092..3ed7f3bb2 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -375,6 +375,24 @@ fn test_touch_set_date2() { assert_eq!(mtime, start_of_year); } +#[test] +fn test_touch_set_date3() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_date"; + + ucmd.args(&["-d", "@1623786360", file]) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file)); + + let expected = FileTime::from_unix_time(1623786360, 0); + let (atime, mtime) = get_file_times(&at, file); + assert_eq!(atime, mtime); + assert_eq!(atime, expected); + assert_eq!(mtime, expected); +} + #[test] fn test_touch_set_date_wrong_format() { let (_at, mut ucmd) = at_and_ucmd!(); From 2a7209116d9199b03fa3aaa90e7829837591b75b Mon Sep 17 00:00:00 2001 From: Hanif Bin Ariffin Date: Sun, 13 Jun 2021 07:33:14 +0800 Subject: [PATCH 218/320] Fixed cp --preserve accepting no args Signed-off-by: Hanif Bin Ariffin --- src/uu/cp/src/cp.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index a87e86b98..2ebbeddb0 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -396,6 +396,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .multiple(true) .use_delimiter(true) .possible_values(PRESERVABLE_ATTRIBUTES) + .min_values(0) .value_name("ATTR_LIST") .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE]) // -d sets this option From 23de1811711455c6130b0115661e1d8533ebcd22 Mon Sep 17 00:00:00 2001 From: Hanif Bin Ariffin Date: Wed, 16 Jun 2021 11:32:15 +0800 Subject: [PATCH 219/320] Added tests for cp --preserve without args Signed-off-by: Hanif Bin Ariffin --- tests/by-util/test_cp.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index bc6c6fc79..83b199bc4 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -726,6 +726,15 @@ fn test_cp_parents_dest_not_directory() { .stderr_contains("with --parents, the destination must be a directory"); } +#[test] +fn test_cp_preserve_no_args() { + new_ucmd!() + .arg(TEST_COPY_FROM_FOLDER_FILE) + .arg(TEST_HELLO_WORLD_DEST) + .arg("--preserve") + .succeeds(); +} + #[test] // For now, disable the test on Windows. Symlinks aren't well support on Windows. // It works on Unix for now and it works locally when run from a powershell From 3c271304f5496b75e6f8f287934bf2d47a3c071f Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 16 Jun 2021 16:54:28 +0200 Subject: [PATCH 220/320] tty: correct exit code for wrong args --- src/uu/tty/src/tty.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index edcdf091e..331f5e254 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -44,7 +44,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("print nothing, only return an exit status") .required(false), ) - .get_matches_from(args); + .get_matches_from_safe(args); + + let matches = match matches { + Ok(m) => m, + Err(e) => { + eprint!("{}", e); + return 2; + } + }; let silent = matches.is_present(options::SILENT); From aeaf2cebfb3b931a0538ad0f2ec9929180cb2ea8 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 16 Jun 2021 17:16:28 +0200 Subject: [PATCH 221/320] tests/tty: fix test inputs calling `pipe_in(" Date: Wed, 16 Jun 2021 17:38:07 +0200 Subject: [PATCH 222/320] tty: correct exit code for write errrors --- src/uu/tty/src/tty.rs | 14 +++++++++++--- tests/by-util/test_tty.rs | 9 +++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 331f5e254..cc5052dea 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -14,6 +14,7 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::ffi::CStr; +use std::io::Write; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Print the file name of the terminal connected to standard input."; @@ -66,11 +67,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } }; + let mut stdout = std::io::stdout(); + if !silent { - if !tty.chars().all(|c| c.is_whitespace()) { - println!("{}", tty); + let write_result = if !tty.chars().all(|c| c.is_whitespace()) { + writeln!(stdout, "{}", tty) } else { - println!("not a tty"); + writeln!(stdout, "not a tty") + }; + if write_result.is_err() || stdout.flush().is_err() { + // Don't return to prevent a panic later when another flush is attempted + // because the `uucore_procs::main` macro inserts a flush after execution for every utility. + std::process::exit(3); } } diff --git a/tests/by-util/test_tty.rs b/tests/by-util/test_tty.rs index a59be61b4..6ba8cd029 100644 --- a/tests/by-util/test_tty.rs +++ b/tests/by-util/test_tty.rs @@ -63,3 +63,12 @@ fn test_close_stdin_silent_alias() { fn test_wrong_argument() { new_ucmd!().args(&["a"]).fails().code_is(2); } + +#[test] +#[cfg(not(windows))] +fn test_stdout_fail() { + let mut child = new_ucmd!().run_no_wait(); + drop(child.stdout.take()); + let status = child.wait().unwrap(); + assert_eq!(status.code(), Some(3)); +} From 54cbb69d373766e884cf38778da01d8f82344ef1 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 13 Jun 2021 15:39:31 +0200 Subject: [PATCH 223/320] id/tests: fix tests if run on macOS --- .github/workflows/CICD.yml | 1 + .github/workflows/GNU.yml | 6 + src/uu/id/src/id.rs | 13 +-- tests/by-util/test_id.rs | 232 +++++++++++++++++++++++-------------- 4 files changed, 160 insertions(+), 92 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index a8ed1b704..fcaddd310 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -235,6 +235,7 @@ jobs: # { os, target, cargo-options, features, use-cross, toolchain } - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , features: feat_os_unix_gnueabihf , use-cross: use-cross } - { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross } + - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } - { os: ubuntu-16.04 , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } # - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only # - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index e9227e38e..1202de87f 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -45,8 +45,14 @@ jobs: - name: Run GNU tests shell: bash run: | + # bash uutils/util/run-gnu-test.sh + bash uutils/util/run-gnu-test.sh tests/id/context.sh # TODO: remove after debugging + sudo bash uutils/util/run-gnu-test.sh tests/id/setgid.sh # TODO: remove after debugging + bash uutils/util/run-gnu-test.sh tests/id/smack.sh # TODO: remove after debugging bash uutils/util/run-gnu-test.sh tests/id/uid.sh # TODO: remove after debugging bash uutils/util/run-gnu-test.sh tests/id/zero.sh # TODO: remove after debugging + bash uutils/util/run-gnu-test.sh tests/id/no-context.sh # TODO: remove after debugging + bash uutils/util/run-gnu-test.sh tests/id/gnu-zero-uids.sh # todo: remove after debugging - name: Extract tests info shell: bash run: | diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 6afb23d67..35f641b3f 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -24,7 +24,7 @@ // * Help text based on BSD's `id`. // -// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag +// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag testsuite #![allow(non_camel_case_types)] #![allow(dead_code)] @@ -92,7 +92,7 @@ struct State { rflag: bool, // --real zflag: bool, // --zero ids: Option, - // The behaviour for calling GNU's `id` and calling GNU's `id $USER` is similar but different. + // The behavior for calling GNU's `id` and calling GNU's `id $USER` is similar but different. // * The SELinux context is only displayed without a specified user. // * The `getgroups` system call is only used without a specified user, this causes // the order of the displayed groups to be different between `id` and `id $USER`. @@ -336,12 +336,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ); } + let groups = entries::get_groups_gnu(Some(gid)).unwrap(); let groups = if state.user_specified { - possible_pw - .map(|p| p.belongs_to()) - .unwrap_or_else(|| entries::get_groups_gnu(Some(gid)).unwrap()) + possible_pw.map(|p| p.belongs_to()).unwrap() } else { - entries::get_groups_gnu(Some(gid)).unwrap() + groups.clone() }; if state.gsflag { @@ -517,7 +516,7 @@ fn id_print(state: &State, groups: Vec) { .join(",") ); - // placeholder ("-Z" is NotImplemented): + // NOTE: placeholder ("-Z" is NotImplemented): // if !state.user_specified { // // print SElinux context (does not depend on "-Z") // print!(" context={}", get_selinux_contexts().join(":")); diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 9e1a218ea..4c41e3131 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -12,11 +12,29 @@ use crate::common::util::*; // whoami: "runner" // +// spell-checker:ignore (ToDO) testsuite coreutil + +const VERSION_EXPECTED: &str = "8.30"; // 8.32 +const UUTILS_WARNING: &str = "uutils-tests-warning"; +const UUTILS_INFO: &str = "uutils-tests-info"; + +macro_rules! unwrap_or_return { + ( $e:expr ) => { + match $e { + Ok(x) => x, + Err(e) => { + println!("{}: test skipped: {}", UUTILS_INFO, e); + return; + } + } + }; +} + fn whoami() -> String { - // Use environment variable to get current user instead of invoking `whoami` - // and fall back to user "nobody" on error. + // Use environment variable to get current user instead of + // invoking `whoami` and fall back to user "nobody" on error. std::env::var("USER").unwrap_or_else(|e| { - println!("warning: {}, using \"nobody\" instead", e); + println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e); "nobody".to_string() }) } @@ -25,20 +43,23 @@ fn whoami() -> String { #[cfg(unix)] fn test_id_no_specified_user() { let result = new_ucmd!().run(); - let expected_result = expected_result(&[]); - let mut exp_stdout = expected_result.stdout_str().to_string(); + let exp_result = unwrap_or_return!(expected_result(&[])); + let mut _exp_stdout = exp_result.stdout_str().to_string(); - // uu_id does not support selinux context. Remove 'context' part from exp_stdout: - let context_offset = expected_result - .stdout_str() - .find(" context") - .unwrap_or(exp_stdout.len()); - exp_stdout.replace_range(context_offset.., "\n"); + #[cfg(target_os = "linux")] + { + // NOTE: Strip 'context' part from exp_stdout (remove if SElinux gets added): + let context_offset = exp_result + .stdout_str() + .find(" context=") + .unwrap_or_else(|| _exp_stdout.len()); + _exp_stdout.replace_range(context_offset.., "\n"); + } result - .stdout_is(exp_stdout) - .stderr_is(expected_result.stderr_str()) - .code_is(expected_result.code()); + .stdout_is(_exp_stdout) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); } #[test] @@ -47,53 +68,53 @@ fn test_id_single_user() { let test_users = [&whoami()[..]]; let scene = TestScenario::new(util_name!()); - let mut exp_result = expected_result(&test_users); + let mut exp_result = unwrap_or_return!(expected_result(&test_users)); scene .ucmd() .args(&test_users) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); // u/g/G z/n for &opt in &["--user", "--group", "--groups"] { let mut args = vec![opt]; args.extend_from_slice(&test_users); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--zero"); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--name"); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.pop(); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); } } @@ -103,11 +124,16 @@ fn test_id_single_user() { fn test_id_single_user_non_existing() { let args = &["hopefully_non_existing_username"]; let result = new_ucmd!().args(args).run(); - let expected_result = expected_result(args); + let exp_result = unwrap_or_return!(expected_result(args)); + + // coreutils 8.32: $ LC_ALL=C id foobar + // macOS: stderr: "id: 'foobar': no such user: Invalid argument" + // linux: stderr: "id: 'foobar': no such user" + // It is unkown why the output on macOS is different. result - .stdout_is(expected_result.stdout_str()) - .stderr_is(expected_result.stderr_str()) - .code_is(expected_result.code()); + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) + .code_is(exp_result.code()); } #[test] @@ -117,11 +143,11 @@ fn test_id_name() { for &opt in &["--user", "--group", "--groups"] { let args = [opt, "--name"]; let result = scene.ucmd().args(&args).run(); - let expected_result = expected_result(&args); + let exp_result = unwrap_or_return!(expected_result(&args)); result - .stdout_is(expected_result.stdout_str()) - .stderr_is(expected_result.stderr_str()) - .code_is(expected_result.code()); + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); if opt == "--user" { assert_eq!(result.stdout_str().trim_end(), whoami()); @@ -136,11 +162,11 @@ fn test_id_real() { for &opt in &["--user", "--group", "--groups"] { let args = [opt, "--real"]; let result = scene.ucmd().args(&args).run(); - let expected_result = expected_result(&args); + let exp_result = unwrap_or_return!(expected_result(&args)); result - .stdout_is(expected_result.stdout_str()) - .stderr_is(expected_result.stderr_str()) - .code_is(expected_result.code()); + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); } } @@ -159,7 +185,6 @@ fn test_id_pretty_print() { // stdout = // stderr = ', tests/common/util.rs:157:13 println!("test skipped:"); - return; } else { result.success().stdout_contains(username); } @@ -181,53 +206,53 @@ fn test_id_multiple_users() { let test_users = ["root", "man", "postfix", "sshd", &whoami()]; let scene = TestScenario::new(util_name!()); - let mut exp_result = expected_result(&test_users); + let mut exp_result = unwrap_or_return!(expected_result(&test_users)); scene .ucmd() .args(&test_users) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); // u/g/G z/n for &opt in &["--user", "--group", "--groups"] { let mut args = vec![opt]; args.extend_from_slice(&test_users); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--zero"); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--name"); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.pop(); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); } } @@ -249,53 +274,53 @@ fn test_id_multiple_users_non_existing() { ]; let scene = TestScenario::new(util_name!()); - let mut exp_result = expected_result(&test_users); + let mut exp_result = unwrap_or_return!(expected_result(&test_users)); scene .ucmd() .args(&test_users) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); // u/g/G z/n for &opt in &["--user", "--group", "--groups"] { let mut args = vec![opt]; args.extend_from_slice(&test_users); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--zero"); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.push("--name"); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); args.pop(); - exp_result = expected_result(&args); + exp_result = unwrap_or_return!(expected_result(&args)); scene .ucmd() .args(&args) .run() .stdout_is(exp_result.stdout_str()) - .stderr_is(exp_result.stderr_str()) + .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) .code_is(exp_result.code()); } } @@ -311,12 +336,12 @@ fn test_id_default_format() { .ucmd() .args(&args) .fails() - .stderr_only(expected_result(&args).stderr_str()); + .stderr_only(unwrap_or_return!(expected_result(&args)).stderr_str()); for &opt2 in &["--user", "--group", "--groups"] { // u/g/G n/r let args = [opt2, opt1]; let result = scene.ucmd().args(&args).run(); - let exp_result = expected_result(&args); + let exp_result = unwrap_or_return!(expected_result(&args)); result .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str()) @@ -330,7 +355,7 @@ fn test_id_default_format() { .ucmd() .args(&args) .succeeds() - .stdout_only(expected_result(&args).stdout_str()); + .stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str()); } } @@ -344,7 +369,7 @@ fn test_id_zero() { .ucmd() .args(&[z_flag]) .fails() - .stderr_only(expected_result(&[z_flag]).stderr_str()); + .stderr_only(unwrap_or_return!(expected_result(&[z_flag])).stderr_str()); for &opt1 in &["--name", "--real"] { // id: cannot print only names or real IDs in default format let args = [opt1, z_flag]; @@ -352,12 +377,12 @@ fn test_id_zero() { .ucmd() .args(&args) .fails() - .stderr_only(expected_result(&args).stderr_str()); + .stderr_only(unwrap_or_return!(expected_result(&args)).stderr_str()); for &opt2 in &["--user", "--group", "--groups"] { // u/g/G n/r z let args = [opt2, z_flag, opt1]; let result = scene.ucmd().args(&args).run(); - let exp_result = expected_result(&args); + let exp_result = unwrap_or_return!(expected_result(&args)); result .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str()) @@ -371,46 +396,83 @@ fn test_id_zero() { .ucmd() .args(&args) .succeeds() - .stdout_only(expected_result(&args).stdout_str()); + .stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str()); } } } #[allow(clippy::needless_borrow)] #[cfg(unix)] -fn expected_result(args: &[&str]) -> CmdResult { +fn expected_result(args: &[&str]) -> Result { + // version for reference coreutil binary + #[cfg(target_os = "linux")] let util_name = util_name!(); #[cfg(all(unix, not(target_os = "linux")))] let util_name = format!("g{}", util_name!()); - let result = TestScenario::new(&util_name) + let scene = TestScenario::new(&util_name); + let version_check = scene + .cmd_keepenv(&util_name) + .env("LANGUAGE", "C") + .arg("--version") + .run(); + let version_check_string: String = version_check + .stdout_str() + .split('\n') + .collect::>() + .get(0) + .map_or_else( + || format!("{}: unexpected output format for reference coreutils '{} --version'", UUTILS_WARNING, util_name), + |s| { + if s.contains(&format!("(GNU coreutils) {}", VERSION_EXPECTED)) { + s.to_string() + } else if s.contains("(GNU coreutils)") { + // example: id (GNU coreutils) 8.32.162-4eda + let version_found = s.split_whitespace().last().unwrap()[..4].parse::().unwrap_or_default(); + let version_expected = VERSION_EXPECTED.parse::().unwrap_or_default(); + if version_found > version_expected { + format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, VERSION_EXPECTED, version_found) + } else { + format!("{}: version for the reference coreutil '{}' does not match; expected: {}, found: {}", UUTILS_WARNING, util_name, VERSION_EXPECTED, version_found) } + } else { + format!("{}: no coreutils version string found for reference coreutils '{} --version'", UUTILS_WARNING, util_name) + } + }, + ); + if version_check_string.starts_with(UUTILS_WARNING) { + return Err(version_check_string); + } + println!("{}", version_check_string); + + let result = scene .cmd_keepenv(&util_name) .env("LANGUAGE", "C") .args(args) .run(); - let mut _o = 0; - let mut _e = 0; - #[cfg(all(unix, not(target_os = "linux")))] - { - _o = if result.stdout_str().starts_with(&util_name) { - 1 - } else { - 0 - }; - _e = if result.stderr_str().starts_with(&util_name) { - 1 - } else { - 0 - }; - } + // #[cfg(all(unix, not(target_os = "linux")))] + // if cfg!(target_os = "macos") { + let (stdout, stderr): (String, String) = if cfg!(target_os = "linux") { + ( + result.stdout_str().to_string(), + result.stderr_str().to_string(), + ) + } else { + // strip 'g' prefix from results: + let from = util_name.to_string() + ":"; + let to = &from[1..]; + ( + result.stdout_str().replace(&from, to), + result.stderr_str().replace(&from, to), + ) + }; - CmdResult::new( + Ok(CmdResult::new( Some(result.tmpd()), Some(result.code()), result.succeeded(), - &result.stdout()[_o..], - &result.stderr()[_e..], - ) + stdout.as_bytes(), + stderr.as_bytes(), + )) } From 39aa5312edd768f88a8307adcfc339ae5f20c41a Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 16 Jun 2021 20:45:46 +0200 Subject: [PATCH 224/320] id/tests: skip tests for multiple_user feature if there's not at least coreutils `id` version 8.31 in `$PATH` --- .github/workflows/GNU.yml | 9 +--- src/uu/id/src/id.rs | 10 ++-- tests/by-util/test_id.rs | 111 +++++++++++++++++++++++--------------- 3 files changed, 76 insertions(+), 54 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 1202de87f..1f9250900 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -45,14 +45,7 @@ jobs: - name: Run GNU tests shell: bash run: | - # bash uutils/util/run-gnu-test.sh - bash uutils/util/run-gnu-test.sh tests/id/context.sh # TODO: remove after debugging - sudo bash uutils/util/run-gnu-test.sh tests/id/setgid.sh # TODO: remove after debugging - bash uutils/util/run-gnu-test.sh tests/id/smack.sh # TODO: remove after debugging - bash uutils/util/run-gnu-test.sh tests/id/uid.sh # TODO: remove after debugging - bash uutils/util/run-gnu-test.sh tests/id/zero.sh # TODO: remove after debugging - bash uutils/util/run-gnu-test.sh tests/id/no-context.sh # TODO: remove after debugging - bash uutils/util/run-gnu-test.sh tests/id/gnu-zero-uids.sh # todo: remove after debugging + bash uutils/util/run-gnu-test.sh - name: Extract tests info shell: bash run: | diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 35f641b3f..9037745eb 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -15,13 +15,15 @@ // * This was partially rewritten in order for stdout/stderr/exit_code // to be conform with GNU coreutils (8.32) testsuite for `id`. // -// * This passes GNU's coreutils Testsuite (8.32.161-370c2-dirty) +// * This supports multiple users (a feature that was introduced in coreutils 8.31) +// +// * This passes GNU's coreutils Testsuite (8.32) // for "tests/id/uid.sh" and "tests/id/zero/sh". // -// * Option '--zero' does not exist for BSD's `id`, therefor '--zero' is only +// * Option '--zero' does not exist for BSD's `id`, therefore '--zero' is only // allowed together with other options that are available on GNU's `id`. // -// * Help text based on BSD's `id`. +// * Help text based on BSD's `id` manpage and GNU's `id` manpage. // // spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag testsuite @@ -516,7 +518,7 @@ fn id_print(state: &State, groups: Vec) { .join(",") ); - // NOTE: placeholder ("-Z" is NotImplemented): + // NOTE: (SELinux NotImplemented) placeholder: // if !state.user_specified { // // print SElinux context (does not depend on "-Z") // print!(" context={}", get_selinux_contexts().join(":")); diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 4c41e3131..b4b929a2c 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -1,20 +1,15 @@ use crate::common::util::*; -// Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. -// -// From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" -// whoami: cannot find name for user ID 1001 -// id --name: cannot find name for user ID 1001 -// id --name: cannot find name for group ID 116 -// -// However, when running "id" from within "/bin/bash" it looks fine: -// id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)" -// whoami: "runner" -// - // spell-checker:ignore (ToDO) testsuite coreutil -const VERSION_EXPECTED: &str = "8.30"; // 8.32 +// These tests run the GNU coreutils `(g)id` binary in `$PATH` in order to gather reference values. +// If the `(g)id` in `$PATH` doesn't include a coreutils version string, +// or the version is too low, the test is skipped. + +// The reference version is 8.32. Here 8.30 was chosen because right now there's no +// ubuntu image for github action available with a higher version than 8.30. +const VERSION_EXPECTED: &str = "8.30"; // Version expected for the reference `id` in $PATH +const VERSION_MULTIPLE_USERS: &str = "8.31"; const UUTILS_WARNING: &str = "uutils-tests-warning"; const UUTILS_INFO: &str = "uutils-tests-info"; @@ -31,6 +26,17 @@ macro_rules! unwrap_or_return { } fn whoami() -> String { + // Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. + // + // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" + // whoami: cannot find name for user ID 1001 + // id --name: cannot find name for user ID 1001 + // id --name: cannot find name for group ID 116 + // + // However, when running "id" from within "/bin/bash" it looks fine: + // id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)" + // whoami: "runner" + // Use environment variable to get current user instead of // invoking `whoami` and fall back to user "nobody" on error. std::env::var("USER").unwrap_or_else(|e| { @@ -48,12 +54,10 @@ fn test_id_no_specified_user() { #[cfg(target_os = "linux")] { - // NOTE: Strip 'context' part from exp_stdout (remove if SElinux gets added): - let context_offset = exp_result - .stdout_str() - .find(" context=") - .unwrap_or_else(|| _exp_stdout.len()); - _exp_stdout.replace_range(context_offset.., "\n"); + // NOTE: (SELinux NotImplemented) strip 'context' part from exp_stdout: + if let Some(context_offset) = exp_result.stdout_str().find(" context=") { + _exp_stdout.replace_range(context_offset.._exp_stdout.len() - 1, ""); + } } result @@ -126,10 +130,10 @@ fn test_id_single_user_non_existing() { let result = new_ucmd!().args(args).run(); let exp_result = unwrap_or_return!(expected_result(args)); + // It is unknown why on macOS (and possibly others?) `id` adds "Invalid argument". // coreutils 8.32: $ LC_ALL=C id foobar // macOS: stderr: "id: 'foobar': no such user: Invalid argument" // linux: stderr: "id: 'foobar': no such user" - // It is unkown why the output on macOS is different. result .stdout_is(exp_result.stdout_str()) .stderr_is(exp_result.stderr_str().replace(": Invalid argument", "")) @@ -202,6 +206,16 @@ fn test_id_password_style() { #[test] #[cfg(unix)] fn test_id_multiple_users() { + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(all(unix, not(target_os = "linux")))] + let util_name = &format!("g{}", util_name!()); + let version_check_string = check_coreutil_version(util_name, VERSION_MULTIPLE_USERS); + if version_check_string.starts_with(UUTILS_WARNING) { + println!("{}\ntest skipped", version_check_string); + return; + } + // Same typical users that GNU testsuite is using. let test_users = ["root", "man", "postfix", "sshd", &whoami()]; @@ -260,6 +274,16 @@ fn test_id_multiple_users() { #[test] #[cfg(unix)] fn test_id_multiple_users_non_existing() { + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(all(unix, not(target_os = "linux")))] + let util_name = &format!("g{}", util_name!()); + let version_check_string = check_coreutil_version(util_name, VERSION_MULTIPLE_USERS); + if version_check_string.starts_with(UUTILS_WARNING) { + println!("{}\ntest skipped", version_check_string); + return; + } + let test_users = [ "root", "hopefully_non_existing_username1", @@ -401,58 +425,61 @@ fn test_id_zero() { } } -#[allow(clippy::needless_borrow)] -#[cfg(unix)] -fn expected_result(args: &[&str]) -> Result { - // version for reference coreutil binary - - #[cfg(target_os = "linux")] - let util_name = util_name!(); - #[cfg(all(unix, not(target_os = "linux")))] - let util_name = format!("g{}", util_name!()); - - let scene = TestScenario::new(&util_name); +fn check_coreutil_version(util_name: &str, version_expected: &str) -> String { + // example: + // $ id --version | head -n 1 + // id (GNU coreutils) 8.32.162-4eda + let scene = TestScenario::new(util_name); let version_check = scene .cmd_keepenv(&util_name) .env("LANGUAGE", "C") .arg("--version") .run(); - let version_check_string: String = version_check + version_check .stdout_str() .split('\n') .collect::>() .get(0) .map_or_else( - || format!("{}: unexpected output format for reference coreutils '{} --version'", UUTILS_WARNING, util_name), + || format!("{}: unexpected output format for reference coreutil: '{} --version'", UUTILS_WARNING, util_name), |s| { - if s.contains(&format!("(GNU coreutils) {}", VERSION_EXPECTED)) { + if s.contains(&format!("(GNU coreutils) {}", version_expected)) { s.to_string() } else if s.contains("(GNU coreutils)") { - // example: id (GNU coreutils) 8.32.162-4eda let version_found = s.split_whitespace().last().unwrap()[..4].parse::().unwrap_or_default(); - let version_expected = VERSION_EXPECTED.parse::().unwrap_or_default(); + let version_expected = version_expected.parse::().unwrap_or_default(); if version_found > version_expected { - format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, VERSION_EXPECTED, version_found) + format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, version_expected, version_found) } else { - format!("{}: version for the reference coreutil '{}' does not match; expected: {}, found: {}", UUTILS_WARNING, util_name, VERSION_EXPECTED, version_found) } + format!("{}: version for the reference coreutil '{}' does not match; expected: {}, found: {}", UUTILS_WARNING, util_name, version_expected, version_found) } } else { format!("{}: no coreutils version string found for reference coreutils '{} --version'", UUTILS_WARNING, util_name) } }, - ); + ) +} + +#[allow(clippy::needless_borrow)] +#[cfg(unix)] +fn expected_result(args: &[&str]) -> Result { + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(all(unix, not(target_os = "linux")))] + let util_name = &format!("g{}", util_name!()); + + let version_check_string = check_coreutil_version(util_name, VERSION_EXPECTED); if version_check_string.starts_with(UUTILS_WARNING) { return Err(version_check_string); } println!("{}", version_check_string); + let scene = TestScenario::new(util_name); let result = scene - .cmd_keepenv(&util_name) + .cmd_keepenv(util_name) .env("LANGUAGE", "C") .args(args) .run(); - // #[cfg(all(unix, not(target_os = "linux")))] - // if cfg!(target_os = "macos") { let (stdout, stderr): (String, String) = if cfg!(target_os = "linux") { ( result.stdout_str().to_string(), From 816c55dce4153151ff341525678aa92c6d77d0a2 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 16 Jun 2021 12:12:57 +0200 Subject: [PATCH 225/320] sort: avoid sigpipe errors By calling `unwrap` we get a panic instead of an abort, and since we mute sigpipe panics for all utilites, no error message will be printed. --- src/uu/sort/src/ext_sort.rs | 4 ++-- src/uu/sort/src/sort.rs | 10 +++++----- tests/by-util/test_sort.rs | 13 +++++++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 022c57741..44ff6014a 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -255,7 +255,7 @@ fn write( fn write_lines<'a, T: Write>(lines: &[Line<'a>], writer: &mut T, separator: u8) { for s in lines { - crash_if_err!(1, writer.write_all(s.line.as_bytes())); - crash_if_err!(1, writer.write_all(&[separator])); + writer.write_all(s.line.as_bytes()).unwrap(); + writer.write_all(&[separator]).unwrap(); } } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 4e865b208..7f3d2872e 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -384,13 +384,13 @@ impl<'a> Line<'a> { fn print(&self, writer: &mut impl Write, settings: &GlobalSettings) { if settings.zero_terminated && !settings.debug { - crash_if_err!(1, writer.write_all(self.line.as_bytes())); - crash_if_err!(1, writer.write_all(b"\0")); + writer.write_all(self.line.as_bytes()).unwrap(); + writer.write_all(b"\0").unwrap(); } else if !settings.debug { - crash_if_err!(1, writer.write_all(self.line.as_bytes())); - crash_if_err!(1, writer.write_all(b"\n")); + writer.write_all(self.line.as_bytes()).unwrap(); + writer.write_all(b"\n").unwrap(); } else { - crash_if_err!(1, self.print_debug(settings, writer)); + self.print_debug(settings, writer).unwrap(); } } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 02d9fe92d..0f9a9d3f1 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -913,3 +913,16 @@ fn test_merge_batch_size() { .succeeds() .stdout_only_fixture("merge_ints_interleaved.expected"); } + +#[test] +fn test_sigpipe_panic() { + let mut cmd = new_ucmd!(); + let mut child = cmd.args(&["ext_sort.txt"]).run_no_wait(); + // Dropping the stdout should not lead to an error. + // The "Broken pipe" error should be silently ignored. + drop(child.stdout.take()); + assert_eq!( + String::from_utf8(child.wait_with_output().unwrap().stderr), + Ok(String::new()) + ); +} From b87387964de8fad7df636dd59e7b179b5bc63886 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 16 Jun 2021 12:28:50 +0200 Subject: [PATCH 226/320] core: mute all BrokenPipe errors On windows the error message does not contain `Broken pipe`, so let's try to find the error `kind` which should be `BrokenPipe` in all cases. --- src/uucore/src/lib/mods/panic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/mods/panic.rs b/src/uucore/src/lib/mods/panic.rs index 6947df2ac..ba0ecdf12 100644 --- a/src/uucore/src/lib/mods/panic.rs +++ b/src/uucore/src/lib/mods/panic.rs @@ -8,7 +8,7 @@ pub fn mute_sigpipe_panic() { let hook = panic::take_hook(); panic::set_hook(Box::new(move |info| { if let Some(res) = info.payload().downcast_ref::() { - if res.contains("Broken pipe") { + if res.contains("BrokenPipe") { return; } } From bc8415c9dbce3083d706eaa492c0352d8e95728d Mon Sep 17 00:00:00 2001 From: Syukron Rifail M Date: Thu, 10 Jun 2021 22:01:28 +0700 Subject: [PATCH 227/320] du: add --dereference --- src/uu/du/src/du.rs | 61 +++++++------- tests/by-util/test_du.rs | 80 ++++++++++++++----- .../subdir/deeper/deeper_dir/deeper_words.txt | 1 + 3 files changed, 95 insertions(+), 47 deletions(-) create mode 100644 tests/fixtures/du/subdir/deeper/deeper_dir/deeper_words.txt diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index e466b8afe..e4bac2e18 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -25,7 +25,6 @@ use std::os::unix::fs::MetadataExt; use std::os::windows::fs::MetadataExt; #[cfg(windows)] use std::os::windows::io::AsRawHandle; -#[cfg(windows)] use std::path::Path; use std::path::PathBuf; use std::str::FromStr; @@ -62,6 +61,7 @@ mod options { pub const TIME: &str = "time"; pub const TIME_STYLE: &str = "time-style"; pub const ONE_FILE_SYSTEM: &str = "one-file-system"; + pub const DEREFERENCE: &str = "dereference"; pub const FILE: &str = "FILE"; } @@ -87,6 +87,7 @@ struct Options { total: bool, separate_dirs: bool, one_file_system: bool, + dereference: bool, } #[derive(PartialEq, Eq, Hash, Clone, Copy)] @@ -107,8 +108,12 @@ struct Stat { } impl Stat { - fn new(path: PathBuf) -> Result { - let metadata = fs::symlink_metadata(&path)?; + fn new(path: PathBuf, options: &Options) -> Result { + let metadata = if options.dereference { + fs::metadata(&path)? + } else { + fs::symlink_metadata(&path)? + }; #[cfg(not(windows))] let file_info = FileInfo { @@ -279,8 +284,14 @@ fn du( for f in read { match f { - Ok(entry) => match Stat::new(entry.path()) { + Ok(entry) => match Stat::new(entry.path(), options) { Ok(this_stat) => { + if let Some(inode) = this_stat.inode { + if inodes.contains(&inode) { + continue; + } + inodes.insert(inode); + } if this_stat.is_dir { if options.one_file_system { if let (Some(this_inode), Some(my_inode)) = @@ -293,12 +304,6 @@ fn du( } futures.push(du(this_stat, options, depth + 1, inodes)); } else { - if let Some(inode) = this_stat.inode { - if inodes.contains(&inode) { - continue; - } - inodes.insert(inode); - } my_stat.size += this_stat.size; my_stat.blocks += this_stat.blocks; if options.all { @@ -308,18 +313,13 @@ fn du( } Err(error) => match error.kind() { ErrorKind::PermissionDenied => { - let description = format!( - "cannot access '{}'", - entry - .path() - .as_os_str() - .to_str() - .unwrap_or("") - ); + let description = format!("cannot access '{}'", entry.path().display()); let error_message = "Permission denied"; show_error_custom_description!(description, "{}", error_message) } - _ => show_error!("{}", error), + _ => { + show_error!("cannot access '{}': {}", entry.path().display(), error) + } }, }, Err(error) => show_error!("{}", error), @@ -327,7 +327,7 @@ fn du( } } - stats.extend(futures.into_iter().flatten().rev().filter(|stat| { + stats.extend(futures.into_iter().flatten().filter(|stat| { if !options.separate_dirs && stat.path.parent().unwrap() == my_stat.path { my_stat.size += stat.size; my_stat.blocks += stat.blocks; @@ -466,12 +466,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long("count-links") .help("count sizes many times if hard linked") ) - // .arg( - // Arg::with_name("dereference") - // .short("L") - // .long("dereference") - // .help("dereference all symbolic links") - // ) + .arg( + Arg::with_name(options::DEREFERENCE) + .short("L") + .long(options::DEREFERENCE) + .help("dereference all symbolic links") + ) // .arg( // Arg::with_name("no-dereference") // .short("P") @@ -588,12 +588,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { total: matches.is_present(options::TOTAL), separate_dirs: matches.is_present(options::SEPARATE_DIRS), one_file_system: matches.is_present(options::ONE_FILE_SYSTEM), + dereference: matches.is_present(options::DEREFERENCE), }; let files = match matches.value_of(options::FILE) { Some(_) => matches.values_of(options::FILE).unwrap().collect(), None => { - vec!["./"] // TODO: gnu `du` doesn't use trailing "/" here + vec!["."] } }; @@ -655,10 +656,12 @@ Try '{} --help' for more information.", let mut grand_total = 0; for path_string in files { let path = PathBuf::from(&path_string); - match Stat::new(path) { + match Stat::new(path, &options) { Ok(stat) => { let mut inodes: HashSet = HashSet::new(); - + if let Some(inode) = stat.inode { + inodes.insert(inode); + } let iter = du(stat, &options, 0, &mut inodes); let (_, len) = iter.size_hint(); let len = len.unwrap(); diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 93875ae51..ffe449880 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -8,7 +8,9 @@ use crate::common::util::*; const SUB_DIR: &str = "subdir/deeper"; +const SUB_DEEPER_DIR: &str = "subdir/deeper/deeper_dir"; const SUB_DIR_LINKS: &str = "subdir/links"; +const SUB_DIR_LINKS_DEEPER_SYM_DIR: &str = "subdir/links/deeper_dir"; const SUB_FILE: &str = "subdir/links/subwords.txt"; const SUB_LINK: &str = "subdir/links/sublink.txt"; @@ -21,7 +23,7 @@ fn _du_basics(s: &str) { let answer = "32\t./subdir 8\t./subdir/deeper 24\t./subdir/links -40\t./ +40\t. "; assert_eq!(s, answer); } @@ -30,7 +32,7 @@ fn _du_basics(s: &str) { let answer = "28\t./subdir 8\t./subdir/deeper 16\t./subdir/links -36\t./ +36\t. "; assert_eq!(s, answer); } @@ -54,15 +56,15 @@ fn test_du_basics_subdir() { #[cfg(target_vendor = "apple")] fn _du_basics_subdir(s: &str) { - assert_eq!(s, "4\tsubdir/deeper\n"); + assert_eq!(s, "4\tsubdir/deeper/deeper_dir\n8\tsubdir/deeper\n"); } #[cfg(target_os = "windows")] fn _du_basics_subdir(s: &str) { - assert_eq!(s, "0\tsubdir/deeper\n"); + assert_eq!(s, "0\tsubdir/deeper\\deeper_dir\n0\tsubdir/deeper\n"); } #[cfg(target_os = "freebsd")] fn _du_basics_subdir(s: &str) { - assert_eq!(s, "8\tsubdir/deeper\n"); + assert_eq!(s, "8\tsubdir/deeper/deeper_dir\n16\tsubdir/deeper\n"); } #[cfg(all( not(target_vendor = "apple"), @@ -210,12 +212,7 @@ fn test_du_d_flag() { { let result_reference = scene.cmd("du").arg("-d1").run(); if result_reference.succeeded() { - assert_eq!( - // TODO: gnu `du` doesn't use trailing "/" here - // result.stdout_str(), result_reference.stdout_str() - result.stdout_str().trim_end_matches("/\n"), - result_reference.stdout_str().trim_end_matches('\n') - ); + assert_eq!(result.stdout_str(), result_reference.stdout_str()); return; } } @@ -224,15 +221,15 @@ fn test_du_d_flag() { #[cfg(target_vendor = "apple")] fn _du_d_flag(s: &str) { - assert_eq!(s, "16\t./subdir\n20\t./\n"); + assert_eq!(s, "20\t./subdir\n24\t.\n"); } #[cfg(target_os = "windows")] fn _du_d_flag(s: &str) { - assert_eq!(s, "8\t./subdir\n8\t./\n"); + assert_eq!(s, "8\t.\\subdir\n8\t.\n"); } #[cfg(target_os = "freebsd")] fn _du_d_flag(s: &str) { - assert_eq!(s, "28\t./subdir\n36\t./\n"); + assert_eq!(s, "36\t./subdir\n44\t.\n"); } #[cfg(all( not(target_vendor = "apple"), @@ -242,9 +239,56 @@ fn _du_d_flag(s: &str) { fn _du_d_flag(s: &str) { // MS-WSL linux has altered expected output if !uucore::os::is_wsl_1() { - assert_eq!(s, "28\t./subdir\n36\t./\n"); + assert_eq!(s, "28\t./subdir\n36\t.\n"); } else { - assert_eq!(s, "8\t./subdir\n8\t./\n"); + assert_eq!(s, "8\t./subdir\n8\t.\n"); + } +} + +#[test] +fn test_du_dereference() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.symlink_dir(SUB_DEEPER_DIR, SUB_DIR_LINKS_DEEPER_SYM_DIR); + + let result = scene.ucmd().arg("-L").arg(SUB_DIR_LINKS).succeeds(); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg("-L").arg(SUB_DIR_LINKS).run(); + if result_reference.succeeded() { + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + return; + } + } + + _du_dereference(result.stdout_str()); +} + +#[cfg(target_vendor = "apple")] +fn _du_dereference(s: &str) { + assert_eq!(s, "4\tsubdir/links/deeper_dir\n16\tsubdir/links\n"); +} +#[cfg(target_os = "windows")] +fn _du_dereference(s: &str) { + assert_eq!(s, "0\tsubdir/links\\deeper_dir\n8\tsubdir/links\n"); +} +#[cfg(target_os = "freebsd")] +fn _du_dereference(s: &str) { + assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n"); +} +#[cfg(all( + not(target_vendor = "apple"), + not(target_os = "windows"), + not(target_os = "freebsd") +))] +fn _du_dereference(s: &str) { + // MS-WSL linux has altered expected output + if !uucore::os::is_wsl_1() { + assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n"); + } else { + assert_eq!(s, "0\tsubdir/links/deeper_dir\n8\tsubdir/links\n"); } } @@ -366,12 +410,12 @@ fn test_du_threshold() { .arg(format!("--threshold={}", threshold)) .succeeds() .stdout_contains("links") - .stdout_does_not_contain("deeper"); + .stdout_does_not_contain("deeper_dir"); scene .ucmd() .arg(format!("--threshold=-{}", threshold)) .succeeds() .stdout_does_not_contain("links") - .stdout_contains("deeper"); + .stdout_contains("deeper_dir"); } diff --git a/tests/fixtures/du/subdir/deeper/deeper_dir/deeper_words.txt b/tests/fixtures/du/subdir/deeper/deeper_dir/deeper_words.txt new file mode 100644 index 000000000..a04238969 --- /dev/null +++ b/tests/fixtures/du/subdir/deeper/deeper_dir/deeper_words.txt @@ -0,0 +1 @@ +hello world! From c73ba1630ec116d0f170940a765538de26ba5ccf Mon Sep 17 00:00:00 2001 From: Dean Li Date: Tue, 15 Jun 2021 21:47:36 +0800 Subject: [PATCH 228/320] ls: set show-control-char if stdout is terminal --- src/uu/ls/src/ls.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index dc67d5738..0bffa2e52 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -382,10 +382,11 @@ impl Config { #[allow(clippy::needless_bool)] let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) { false - } else if options.is_present(options::SHOW_CONTROL_CHARS) { + } else if options.is_present(options::SHOW_CONTROL_CHARS) || atty::is(atty::Stream::Stdout) + { true } else { - false // TODO: only if output is a terminal and the program is `ls` + false }; let quoting_style = if let Some(style) = options.value_of(options::QUOTING_STYLE) { From 12a1c87cb8e72f0df8b03594f425f8c98268ec1c Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 17 Jun 2021 22:26:13 +0200 Subject: [PATCH 229/320] cp: improve symlink handling --- src/uu/cp/src/cp.rs | 80 ++++++++++++++++++++++------------------ tests/by-util/test_cp.rs | 26 +++++++++++++ 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 2ebbeddb0..7e7bcca4c 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -48,7 +48,6 @@ use std::path::{Path, PathBuf, StripPrefixError}; use std::str::FromStr; use std::string::ToString; use uucore::backup_control::{self, BackupMode}; -use uucore::fs::resolve_relative_path; use uucore::fs::{canonicalize, CanonicalizeMode}; use walkdir::WalkDir; @@ -198,7 +197,6 @@ pub struct Options { copy_contents: bool, copy_mode: CopyMode, dereference: bool, - no_dereference: bool, no_target_dir: bool, one_file_system: bool, overwrite: OverwriteMode, @@ -641,11 +639,12 @@ impl Options { attributes_only: matches.is_present(OPT_ATTRIBUTES_ONLY), copy_contents: matches.is_present(OPT_COPY_CONTENTS), copy_mode: CopyMode::from_matches(matches), - dereference: matches.is_present(OPT_DEREFERENCE), // No dereference is set with -p, -d and --archive - no_dereference: matches.is_present(OPT_NO_DEREFERENCE) + dereference: !(matches.is_present(OPT_NO_DEREFERENCE) || matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS) - || matches.is_present(OPT_ARCHIVE), + || matches.is_present(OPT_ARCHIVE) + || recursive) + || matches.is_present(OPT_DEREFERENCE), one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM), parents: matches.is_present(OPT_PARENTS), update: matches.is_present(OPT_UPDATE), @@ -896,7 +895,14 @@ fn copy_source( options: &Options, ) -> CopyResult<()> { let source_path = Path::new(&source); - if source_path.is_dir() { + // if no-dereference is enabled and this is a symlink, don't treat it as a directory + if source_path.is_dir() + && !(!options.dereference + && fs::symlink_metadata(source_path) + .unwrap() + .file_type() + .is_symlink()) + { // Copy as directory copy_directory(source, target, options) } else { @@ -937,7 +943,7 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR return Err(format!("omitting directory '{}'", root.display()).into()); } - let root_path = Path::new(&root).canonicalize()?; + let root_path = env::current_dir().unwrap().join(root); let root_parent = if target.exists() { root_path.parent() @@ -958,17 +964,15 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR #[cfg(any(windows, target_os = "redox"))] let mut hard_links: Vec<(String, u64)> = vec![]; - for path in WalkDir::new(root).same_file_system(options.one_file_system) { + for path in WalkDir::new(root) + .same_file_system(options.one_file_system) + .follow_links(options.dereference) + { let p = or_continue!(path); let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink(); - let path = if (options.no_dereference || options.dereference) && is_symlink { - // we are dealing with a symlink. Don't follow it - match env::current_dir() { - Ok(cwd) => cwd.join(resolve_relative_path(p.path())), - Err(e) => crash!(1, "failed to get current directory {}", e), - } - } else { - or_continue!(p.path().canonicalize()) + let path = match env::current_dir() { + Ok(cwd) => cwd.join(&p.path()), + Err(e) => crash!(1, "failed to get current directory {}", e), }; let local_to_root_parent = match root_parent { @@ -992,9 +996,10 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR }; let local_to_target = target.join(&local_to_root_parent); - - if path.is_dir() && !local_to_target.exists() { - or_continue!(fs::create_dir_all(local_to_target.clone())); + if is_symlink && !options.dereference { + copy_link(&path, &local_to_target)?; + } else if path.is_dir() && !local_to_target.exists() { + or_continue!(fs::create_dir_all(local_to_target)); } else if !path.is_dir() { if preserve_hard_links { let mut found_hard_link = false; @@ -1220,25 +1225,10 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> #[cfg(target_os = "macos")] copy_on_write_macos(source, dest, options.reflink_mode)?; - #[cfg(target_os = "linux")] copy_on_write_linux(source, dest, options.reflink_mode)?; - } else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() { - // Here, we will copy the symlink itself (actually, just recreate it) - let link = fs::read_link(&source)?; - let dest: Cow<'_, Path> = if dest.is_dir() { - match source.file_name() { - Some(name) => dest.join(name).into(), - None => crash!( - EXIT_ERR, - "cannot stat ‘{}’: No such file or directory", - source.display() - ), - } - } else { - dest.into() - }; - symlink_file(&link, &dest, &*context_for(&link, &dest))?; + } else if !options.dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() { + copy_link(source, dest)?; } else if source.to_string_lossy() == "/dev/null" { /* workaround a limitation of fs::copy * https://github.com/rust-lang/rust/issues/79390 @@ -1255,6 +1245,24 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> Ok(()) } +fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> { + // Here, we will copy the symlink itself (actually, just recreate it) + let link = fs::read_link(&source)?; + let dest: Cow<'_, Path> = if dest.is_dir() { + match source.file_name() { + Some(name) => dest.join(name).into(), + None => crash!( + EXIT_ERR, + "cannot stat ‘{}’: No such file or directory", + source.display() + ), + } + } else { + dest.into() + }; + symlink_file(&link, &dest, &*context_for(&link, &dest)) +} + /// Copies `source` to `dest` using copy-on-write if possible. #[cfg(target_os = "linux")] fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> { diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 83b199bc4..4ce587e02 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1299,3 +1299,29 @@ fn test_closes_file_descriptors() { .with_limit(Resource::NOFILE, 9, 9) .succeeds(); } + +#[test] +fn test_copy_dir_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("dir"); + at.symlink_dir("dir", "dir-link"); + ucmd.args(&["-r", "dir-link", "copy"]).succeeds(); + assert_eq!(at.resolve_link("copy"), "dir"); +} + +#[test] +fn test_copy_dir_with_symlinks() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("dir"); + at.make_file("dir/file"); + + TestScenario::new("ln") + .ucmd() + .arg("-sr") + .arg(at.subdir.join("dir/file")) + .arg(at.subdir.join("dir/file-link")) + .succeeds(); + + ucmd.args(&["-r", "dir", "copy"]).succeeds(); + assert_eq!(at.resolve_link("copy/file-link"), "file"); +} From d05964a8cbb4b8e695a978b2fbd4ef6c0555b03c Mon Sep 17 00:00:00 2001 From: Tuomas Tynkkynen Date: Thu, 17 Jun 2021 21:15:26 +0300 Subject: [PATCH 230/320] test: Implement -k parser.rs already accepts this, finish the implementation. --- src/uu/test/src/test.rs | 5 +++++ tests/by-util/test_test.rs | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index e30d7cf51..5f20b95f0 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -98,6 +98,7 @@ fn eval(stack: &mut Vec) -> Result { "-g" => path(&f, PathCondition::GroupIdFlag), "-G" => path(&f, PathCondition::GroupOwns), "-h" => path(&f, PathCondition::SymLink), + "-k" => path(&f, PathCondition::Sticky), "-L" => path(&f, PathCondition::SymLink), "-O" => path(&f, PathCondition::UserOwns), "-p" => path(&f, PathCondition::Fifo), @@ -170,6 +171,7 @@ enum PathCondition { GroupIdFlag, GroupOwns, SymLink, + Sticky, UserOwns, Fifo, Readable, @@ -187,6 +189,7 @@ fn path(path: &OsStr, condition: PathCondition) -> bool { const S_ISUID: u32 = 0o4000; const S_ISGID: u32 = 0o2000; + const S_ISVTX: u32 = 0o1000; enum Permission { Read = 0o4, @@ -246,6 +249,7 @@ fn path(path: &OsStr, condition: PathCondition) -> bool { PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0, PathCondition::GroupOwns => metadata.gid() == getegid(), PathCondition::SymLink => metadata.file_type().is_symlink(), + PathCondition::Sticky => metadata.mode() & S_ISVTX != 0, PathCondition::UserOwns => metadata.uid() == geteuid(), PathCondition::Fifo => file_type.is_fifo(), PathCondition::Readable => perm(metadata, Permission::Read), @@ -275,6 +279,7 @@ fn path(path: &OsStr, condition: PathCondition) -> bool { PathCondition::GroupIdFlag => false, PathCondition::GroupOwns => unimplemented!(), PathCondition::SymLink => false, + PathCondition::Sticky => false, PathCondition::UserOwns => unimplemented!(), PathCondition::Fifo => false, PathCondition::Readable => false, // TODO diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 8d41c5ead..c4964d6bf 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -476,6 +476,27 @@ fn test_nonexistent_file_is_not_symlink() { .succeeds(); } +#[test] +#[cfg(not(windows))] // Windows has no concept of sticky bit +fn test_file_is_sticky() { + let scenario = TestScenario::new(util_name!()); + let mut ucmd = scenario.ucmd(); + let mut chmod = scenario.cmd("chmod"); + + scenario.fixtures.touch("sticky_file"); + chmod.args(&["+t", "sticky_file"]).succeeds(); + + ucmd.args(&["-k", "sticky_file"]).succeeds(); +} + +#[test] +fn test_file_is_not_sticky() { + new_ucmd!() + .args(&["-k", "regular_file"]) + .run() + .status_code(1); +} + #[test] #[cfg(not(windows))] fn test_file_owned_by_euid() { From 315bfd65a3f07221abdb262ba8731d364441e581 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 18 Jun 2021 11:42:37 +0200 Subject: [PATCH 231/320] cp: move symlink check to the right place --- src/uu/cp/src/cp.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 7e7bcca4c..8f47adc28 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -895,14 +895,7 @@ fn copy_source( options: &Options, ) -> CopyResult<()> { let source_path = Path::new(&source); - // if no-dereference is enabled and this is a symlink, don't treat it as a directory - if source_path.is_dir() - && !(!options.dereference - && fs::symlink_metadata(source_path) - .unwrap() - .file_type() - .is_symlink()) - { + if source_path.is_dir() { // Copy as directory copy_directory(source, target, options) } else { @@ -943,6 +936,11 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR return Err(format!("omitting directory '{}'", root.display()).into()); } + // if no-dereference is enabled and this is a symlink, copy it as a file + if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink() { + return copy_file(root, target, options); + } + let root_path = env::current_dir().unwrap().join(root); let root_parent = if target.exists() { From 32526e30486613b3e83840331856ae08177dff7a Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 18 Jun 2021 11:45:04 +0200 Subject: [PATCH 232/320] cp: one more clippy fix --- src/uu/cp/src/cp.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 8f47adc28..c1d5e0ee3 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1397,9 +1397,9 @@ pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result { fn test_cp_localize_to_target() { assert!( localize_to_target( - &Path::new("a/source/"), - &Path::new("a/source/c.txt"), - &Path::new("target/") + Path::new("a/source/"), + Path::new("a/source/c.txt"), + Path::new("target/") ) .unwrap() == Path::new("target/c.txt") From a371c034311b17a66460605a786565307791ac46 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 18 Jun 2021 11:48:13 +0200 Subject: [PATCH 233/320] cp: only get the current directory once --- src/uu/cp/src/cp.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index c1d5e0ee3..a7694ae08 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -941,7 +941,10 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR return copy_file(root, target, options); } - let root_path = env::current_dir().unwrap().join(root); + let current_dir = + env::current_dir().unwrap_or_else(|e| crash!(1, "failed to get current directory {}", e)); + + let root_path = current_dir.join(root); let root_parent = if target.exists() { root_path.parent() @@ -968,10 +971,7 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR { let p = or_continue!(path); let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink(); - let path = match env::current_dir() { - Ok(cwd) => cwd.join(&p.path()), - Err(e) => crash!(1, "failed to get current directory {}", e), - }; + let path = current_dir.join(&p.path()); let local_to_root_parent = match root_parent { Some(parent) => { From 3d3af5c8caa2a28977a75c2ad2d69942203fa442 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 17 Jun 2021 22:54:04 +0200 Subject: [PATCH 234/320] ln: don't return an empty path in `relative_path` --- src/uu/ln/src/ln.rs | 5 ++++- tests/by-util/test_ln.rs | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index ce1dd15b0..29cab58e5 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -382,12 +382,15 @@ fn relative_path<'a>(src: &Path, dst: &Path) -> Result> { let src_iter = src_abs.components().skip(suffix_pos).map(|x| x.as_os_str()); - let result: PathBuf = dst_abs + let mut result: PathBuf = dst_abs .components() .skip(suffix_pos + 1) .map(|_| OsStr::new("..")) .chain(src_iter) .collect(); + if result.as_os_str().is_empty() { + result.push("."); + } Ok(result.into()) } diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index fc97ff779..9fa73c0bc 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -580,3 +580,11 @@ fn test_relative_src_already_symlink() { ucmd.arg("-sr").arg("file2").arg("file3").succeeds(); assert!(at.resolve_link("file3").ends_with("file1")); } + +#[test] +fn test_relative_recursive() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("dir"); + ucmd.args(&["-sr", "dir", "dir/recursive"]).succeeds(); + assert_eq!(at.resolve_link("dir/recursive"), "."); +} From 65f47be5ee260f8a0ce7747239a7fde8ccb59a83 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 18 Jun 2021 12:10:40 +0200 Subject: [PATCH 235/320] cut: fix `-d=` (#2424) --- src/uu/cut/src/cut.rs | 11 ++++++++++- tests/by-util/test_cut.rs | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index af4a27d8a..6602b1eb1 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -531,7 +531,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let zero_terminated = matches.is_present(options::ZERO_TERMINATED); match matches.value_of(options::DELIMITER) { - Some(delim) => { + Some(mut delim) => { + // GNU's `cut` supports `-d=` to set the delimiter to `=`. + // Clap parsing is limited in this situation, see: + // https://github.com/uutils/coreutils/issues/2424#issuecomment-863825242 + // Since clap parsing handles `-d=` as delimiter explicitly set to "" and + // an empty delimiter is not accepted by GNU's `cut` (and makes no sense), + // we can use this as basis for a simple workaround: + if delim.is_empty() { + delim = "="; + } if delim.chars().count() > 1 { Err(msg_opt_invalid_should_be!( "empty or 1 character long", diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 8f81b94c1..e21010ec8 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -157,3 +157,12 @@ fn test_directory_and_no_such_file() { .run() .stderr_is("cut: some: No such file or directory\n"); } + +#[test] +fn test_equal_as_delimiter() { + new_ucmd!() + .args(&["-f", "2", "-d="]) + .pipe_in("--libdir=./out/lib") + .succeeds() + .stdout_only("./out/lib\n"); +} From 4e62c9db71be6c387f9c63d46edb86ceb13556cd Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 18 Jun 2021 16:45:36 +0200 Subject: [PATCH 236/320] install: support target-directory --- src/uu/install/src/install.rs | 27 +++++++++++++++------------ tests/by-util/test_install.rs | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index ad5ea694c..3992ac25e 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -42,6 +42,7 @@ pub struct Behavior { strip: bool, strip_program: String, create_leading: bool, + target_dir: Option, } #[derive(Clone, Eq, PartialEq)] @@ -194,7 +195,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(OPT_TARGET_DIRECTORY) .short("t") .long(OPT_TARGET_DIRECTORY) - .help("(unimplemented) move all SOURCE arguments into DIRECTORY") + .help("move all SOURCE arguments into DIRECTORY") .value_name("DIRECTORY") ) .arg( @@ -268,8 +269,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> { Err("-b") } else if matches.is_present(OPT_SUFFIX) { Err("--suffix, -S") - } else if matches.is_present(OPT_TARGET_DIRECTORY) { - Err("--target-directory, -t") } else if matches.is_present(OPT_NO_TARGET_DIRECTORY) { Err("--no-target-directory, -T") } else if matches.is_present(OPT_PRESERVE_CONTEXT) { @@ -314,6 +313,8 @@ fn behavior(matches: &ArgMatches) -> Result { "~" }; + let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned()); + Ok(Behavior { main_function, specified_mode, @@ -330,6 +331,7 @@ fn behavior(matches: &ArgMatches) -> Result { .unwrap_or(DEFAULT_STRIP_PROGRAM), ), create_leading: matches.is_present(OPT_CREATE_LEADING), + target_dir, }) } @@ -392,16 +394,17 @@ fn is_new_file_path(path: &Path) -> bool { /// /// Returns an integer intended as a program return code. /// -fn standard(paths: Vec, b: Behavior) -> i32 { - let sources = &paths[0..paths.len() - 1] - .iter() - .map(PathBuf::from) - .collect::>(); +fn standard(mut paths: Vec, b: Behavior) -> i32 { + let target: PathBuf = b + .target_dir + .clone() + .unwrap_or_else(|| paths.pop().unwrap()) + .into(); - let target = Path::new(paths.last().unwrap()); + let sources = &paths.iter().map(PathBuf::from).collect::>(); if sources.len() > 1 || (target.exists() && target.is_dir()) { - copy_files_into_dir(sources, &target.to_path_buf(), &b) + copy_files_into_dir(sources, &target, &b) } else { if let Some(parent) = target.parent() { if !parent.exists() && b.create_leading { @@ -417,8 +420,8 @@ fn standard(paths: Vec, b: Behavior) -> i32 { } } - if target.is_file() || is_new_file_path(target) { - copy_file_to_file(&sources[0], &target.to_path_buf(), &b) + if target.is_file() || is_new_file_path(&target) { + copy_file_to_file(&sources[0], &target, &b) } else { show_error!( "invalid target {}: No such file or directory", diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 3ab5cbdfb..ea2c2818e 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -674,3 +674,25 @@ fn test_install_creating_leading_dir_fails_on_long_name() { .fails() .stderr_contains("failed to create"); } + +#[test] +fn test_install_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "target_dir"; + let file1 = "source_file1"; + let file2 = "source_file2"; + + at.touch(file1); + at.touch(file2); + at.mkdir(dir); + ucmd.arg(file1) + .arg(file2) + .arg(&format!("--target-directory={}", dir)) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file1)); + assert!(at.file_exists(file2)); + assert!(at.file_exists(&format!("{}/{}", dir, file1))); + assert!(at.file_exists(&format!("{}/{}", dir, file2))); +} From cf1a7d079645b526af926a01b7fc51e469f5ad5e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 18 Jun 2021 17:56:04 +0200 Subject: [PATCH 237/320] cp: use options module --- src/uu/cp/src/cp.rs | 309 ++++++++++++++++++++++---------------------- 1 file changed, 156 insertions(+), 153 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 2ebbeddb0..96642f397 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -228,39 +228,41 @@ fn get_usage() -> String { } // Argument constants -static OPT_ARCHIVE: &str = "archive"; -static OPT_ATTRIBUTES_ONLY: &str = "attributes-only"; -static OPT_BACKUP: &str = "backup"; -static OPT_BACKUP_NO_ARG: &str = "b"; -static OPT_CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links"; -static OPT_CONTEXT: &str = "context"; -static OPT_COPY_CONTENTS: &str = "copy-contents"; -static OPT_DEREFERENCE: &str = "dereference"; -static OPT_FORCE: &str = "force"; -static OPT_INTERACTIVE: &str = "interactive"; -static OPT_LINK: &str = "link"; -static OPT_NO_CLOBBER: &str = "no-clobber"; -static OPT_NO_DEREFERENCE: &str = "no-dereference"; -static OPT_NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-linkgs"; -static OPT_NO_PRESERVE: &str = "no-preserve"; -static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; -static OPT_ONE_FILE_SYSTEM: &str = "one-file-system"; -static OPT_PARENT: &str = "parent"; -static OPT_PARENTS: &str = "parents"; -static OPT_PATHS: &str = "paths"; -static OPT_PRESERVE: &str = "preserve"; -static OPT_PRESERVE_DEFAULT_ATTRIBUTES: &str = "preserve-default-attributes"; -static OPT_RECURSIVE: &str = "recursive"; -static OPT_RECURSIVE_ALIAS: &str = "recursive_alias"; -static OPT_REFLINK: &str = "reflink"; -static OPT_REMOVE_DESTINATION: &str = "remove-destination"; -static OPT_SPARSE: &str = "sparse"; -static OPT_STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; -static OPT_SUFFIX: &str = "suffix"; -static OPT_SYMBOLIC_LINK: &str = "symbolic-link"; -static OPT_TARGET_DIRECTORY: &str = "target-directory"; -static OPT_UPDATE: &str = "update"; -static OPT_VERBOSE: &str = "verbose"; +mod options { + pub const ARCHIVE: &str = "archive"; + pub const ATTRIBUTES_ONLY: &str = "attributes-only"; + pub const BACKUP: &str = "backup"; + pub const BACKUP_NO_ARG: &str = "b"; + pub const CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links"; + pub const CONTEXT: &str = "context"; + pub const COPY_CONTENTS: &str = "copy-contents"; + pub const DEREFERENCE: &str = "dereference"; + pub const FORCE: &str = "force"; + pub const INTERACTIVE: &str = "interactive"; + pub const LINK: &str = "link"; + pub const NO_CLOBBER: &str = "no-clobber"; + pub const NO_DEREFERENCE: &str = "no-dereference"; + pub const NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-linkgs"; + pub const NO_PRESERVE: &str = "no-preserve"; + pub const NO_TARGET_DIRECTORY: &str = "no-target-directory"; + pub const ONE_FILE_SYSTEM: &str = "one-file-system"; + pub const PARENT: &str = "parent"; + pub const PARENTS: &str = "parents"; + pub const PATHS: &str = "paths"; + pub const PRESERVE: &str = "preserve"; + pub const PRESERVE_DEFAULT_ATTRIBUTES: &str = "preserve-default-attributes"; + pub const RECURSIVE: &str = "recursive"; + pub const RECURSIVE_ALIAS: &str = "recursive_alias"; + pub const REFLINK: &str = "reflink"; + pub const REMOVE_DESTINATION: &str = "remove-destination"; + pub const SPARSE: &str = "sparse"; + pub const STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; + pub const SUFFIX: &str = "suffix"; + pub const SYMBOLIC_LINK: &str = "symbolic-link"; + pub const TARGET_DIRECTORY: &str = "target-directory"; + pub const UPDATE: &str = "update"; + pub const VERBOSE: &str = "verbose"; +} #[cfg(unix)] static PRESERVABLE_ATTRIBUTES: &[&str] = &[ @@ -297,67 +299,67 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .about(ABOUT) .after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP)) .usage(&usage[..]) - .arg(Arg::with_name(OPT_TARGET_DIRECTORY) + .arg(Arg::with_name(options::TARGET_DIRECTORY) .short("t") - .conflicts_with(OPT_NO_TARGET_DIRECTORY) - .long(OPT_TARGET_DIRECTORY) - .value_name(OPT_TARGET_DIRECTORY) + .conflicts_with(options::NO_TARGET_DIRECTORY) + .long(options::TARGET_DIRECTORY) + .value_name(options::TARGET_DIRECTORY) .takes_value(true) .help("copy all SOURCE arguments into target-directory")) - .arg(Arg::with_name(OPT_NO_TARGET_DIRECTORY) + .arg(Arg::with_name(options::NO_TARGET_DIRECTORY) .short("T") - .long(OPT_NO_TARGET_DIRECTORY) - .conflicts_with(OPT_TARGET_DIRECTORY) + .long(options::NO_TARGET_DIRECTORY) + .conflicts_with(options::TARGET_DIRECTORY) .help("Treat DEST as a regular file and not a directory")) - .arg(Arg::with_name(OPT_INTERACTIVE) + .arg(Arg::with_name(options::INTERACTIVE) .short("i") - .long(OPT_INTERACTIVE) - .conflicts_with(OPT_NO_CLOBBER) + .long(options::INTERACTIVE) + .conflicts_with(options::NO_CLOBBER) .help("ask before overwriting files")) - .arg(Arg::with_name(OPT_LINK) + .arg(Arg::with_name(options::LINK) .short("l") - .long(OPT_LINK) - .overrides_with(OPT_REFLINK) + .long(options::LINK) + .overrides_with(options::REFLINK) .help("hard-link files instead of copying")) - .arg(Arg::with_name(OPT_NO_CLOBBER) + .arg(Arg::with_name(options::NO_CLOBBER) .short("n") - .long(OPT_NO_CLOBBER) - .conflicts_with(OPT_INTERACTIVE) + .long(options::NO_CLOBBER) + .conflicts_with(options::INTERACTIVE) .help("don't overwrite a file that already exists")) - .arg(Arg::with_name(OPT_RECURSIVE) + .arg(Arg::with_name(options::RECURSIVE) .short("r") - .long(OPT_RECURSIVE) + .long(options::RECURSIVE) // --archive sets this option .help("copy directories recursively")) - .arg(Arg::with_name(OPT_RECURSIVE_ALIAS) + .arg(Arg::with_name(options::RECURSIVE_ALIAS) .short("R") .help("same as -r")) - .arg(Arg::with_name(OPT_STRIP_TRAILING_SLASHES) - .long(OPT_STRIP_TRAILING_SLASHES) + .arg(Arg::with_name(options::STRIP_TRAILING_SLASHES) + .long(options::STRIP_TRAILING_SLASHES) .help("remove any trailing slashes from each SOURCE argument")) - .arg(Arg::with_name(OPT_VERBOSE) + .arg(Arg::with_name(options::VERBOSE) .short("v") - .long(OPT_VERBOSE) + .long(options::VERBOSE) .help("explicitly state what is being done")) - .arg(Arg::with_name(OPT_SYMBOLIC_LINK) + .arg(Arg::with_name(options::SYMBOLIC_LINK) .short("s") - .long(OPT_SYMBOLIC_LINK) - .conflicts_with(OPT_LINK) - .overrides_with(OPT_REFLINK) + .long(options::SYMBOLIC_LINK) + .conflicts_with(options::LINK) + .overrides_with(options::REFLINK) .help("make symbolic links instead of copying")) - .arg(Arg::with_name(OPT_FORCE) + .arg(Arg::with_name(options::FORCE) .short("f") - .long(OPT_FORCE) + .long(options::FORCE) .help("if an existing destination file cannot be opened, remove it and \ try again (this option is ignored when the -n option is also used). \ Currently not implemented for Windows.")) - .arg(Arg::with_name(OPT_REMOVE_DESTINATION) - .long(OPT_REMOVE_DESTINATION) - .conflicts_with(OPT_FORCE) + .arg(Arg::with_name(options::REMOVE_DESTINATION) + .long(options::REMOVE_DESTINATION) + .conflicts_with(options::FORCE) .help("remove each existing destination file before attempting to open it \ (contrast with --force). On Windows, current only works for writeable files.")) - .arg(Arg::with_name(OPT_BACKUP) - .long(OPT_BACKUP) + .arg(Arg::with_name(options::BACKUP) + .long(options::BACKUP) .help("make a backup of each existing destination file") .takes_value(true) .require_equals(true) @@ -365,104 +367,104 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .possible_values(backup_control::BACKUP_CONTROL_VALUES) .value_name("CONTROL") ) - .arg(Arg::with_name(OPT_BACKUP_NO_ARG) - .short(OPT_BACKUP_NO_ARG) + .arg(Arg::with_name(options::BACKUP_NO_ARG) + .short(options::BACKUP_NO_ARG) .help("like --backup but does not accept an argument") ) - .arg(Arg::with_name(OPT_SUFFIX) + .arg(Arg::with_name(options::SUFFIX) .short("S") - .long(OPT_SUFFIX) + .long(options::SUFFIX) .takes_value(true) .value_name("SUFFIX") .help("override the usual backup suffix")) - .arg(Arg::with_name(OPT_UPDATE) + .arg(Arg::with_name(options::UPDATE) .short("u") - .long(OPT_UPDATE) + .long(options::UPDATE) .help("copy only when the SOURCE file is newer than the destination file\ or when the destination file is missing")) - .arg(Arg::with_name(OPT_REFLINK) - .long(OPT_REFLINK) + .arg(Arg::with_name(options::REFLINK) + .long(options::REFLINK) .takes_value(true) .value_name("WHEN") .help("control clone/CoW copies. See below")) - .arg(Arg::with_name(OPT_ATTRIBUTES_ONLY) - .long(OPT_ATTRIBUTES_ONLY) - .conflicts_with(OPT_COPY_CONTENTS) - .overrides_with(OPT_REFLINK) + .arg(Arg::with_name(options::ATTRIBUTES_ONLY) + .long(options::ATTRIBUTES_ONLY) + .conflicts_with(options::COPY_CONTENTS) + .overrides_with(options::REFLINK) .help("Don't copy the file data, just the attributes")) - .arg(Arg::with_name(OPT_PRESERVE) - .long(OPT_PRESERVE) + .arg(Arg::with_name(options::PRESERVE) + .long(options::PRESERVE) .takes_value(true) .multiple(true) .use_delimiter(true) .possible_values(PRESERVABLE_ATTRIBUTES) .min_values(0) .value_name("ATTR_LIST") - .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE]) + .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE]) // -d sets this option // --archive sets this option .help("Preserve the specified attributes (default: mode(unix only),ownership,timestamps),\ if possible additional attributes: context, links, xattr, all")) - .arg(Arg::with_name(OPT_PRESERVE_DEFAULT_ATTRIBUTES) + .arg(Arg::with_name(options::PRESERVE_DEFAULT_ATTRIBUTES) .short("-p") - .long(OPT_PRESERVE_DEFAULT_ATTRIBUTES) - .conflicts_with_all(&[OPT_PRESERVE, OPT_NO_PRESERVE, OPT_ARCHIVE]) + .long(options::PRESERVE_DEFAULT_ATTRIBUTES) + .conflicts_with_all(&[options::PRESERVE, options::NO_PRESERVE, options::ARCHIVE]) .help("same as --preserve=mode(unix only),ownership,timestamps")) - .arg(Arg::with_name(OPT_NO_PRESERVE) - .long(OPT_NO_PRESERVE) + .arg(Arg::with_name(options::NO_PRESERVE) + .long(options::NO_PRESERVE) .takes_value(true) .value_name("ATTR_LIST") - .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_ARCHIVE]) + .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::ARCHIVE]) .help("don't preserve the specified attributes")) - .arg(Arg::with_name(OPT_PARENTS) - .long(OPT_PARENTS) - .alias(OPT_PARENT) + .arg(Arg::with_name(options::PARENTS) + .long(options::PARENTS) + .alias(options::PARENT) .help("use full source file name under DIRECTORY")) - .arg(Arg::with_name(OPT_NO_DEREFERENCE) + .arg(Arg::with_name(options::NO_DEREFERENCE) .short("-P") - .long(OPT_NO_DEREFERENCE) - .conflicts_with(OPT_DEREFERENCE) + .long(options::NO_DEREFERENCE) + .conflicts_with(options::DEREFERENCE) // -d sets this option .help("never follow symbolic links in SOURCE")) - .arg(Arg::with_name(OPT_DEREFERENCE) + .arg(Arg::with_name(options::DEREFERENCE) .short("L") - .long(OPT_DEREFERENCE) - .conflicts_with(OPT_NO_DEREFERENCE) + .long(options::DEREFERENCE) + .conflicts_with(options::NO_DEREFERENCE) .help("always follow symbolic links in SOURCE")) - .arg(Arg::with_name(OPT_ARCHIVE) + .arg(Arg::with_name(options::ARCHIVE) .short("a") - .long(OPT_ARCHIVE) - .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE]) + .long(options::ARCHIVE) + .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::NO_PRESERVE]) .help("Same as -dR --preserve=all")) - .arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS) + .arg(Arg::with_name(options::NO_DEREFERENCE_PRESERVE_LINKS) .short("d") .help("same as --no-dereference --preserve=links")) - .arg(Arg::with_name(OPT_ONE_FILE_SYSTEM) + .arg(Arg::with_name(options::ONE_FILE_SYSTEM) .short("x") - .long(OPT_ONE_FILE_SYSTEM) + .long(options::ONE_FILE_SYSTEM) .help("stay on this file system")) // TODO: implement the following args - .arg(Arg::with_name(OPT_COPY_CONTENTS) - .long(OPT_COPY_CONTENTS) - .conflicts_with(OPT_ATTRIBUTES_ONLY) + .arg(Arg::with_name(options::COPY_CONTENTS) + .long(options::COPY_CONTENTS) + .conflicts_with(options::ATTRIBUTES_ONLY) .help("NotImplemented: copy contents of special files when recursive")) - .arg(Arg::with_name(OPT_SPARSE) - .long(OPT_SPARSE) + .arg(Arg::with_name(options::SPARSE) + .long(options::SPARSE) .takes_value(true) .value_name("WHEN") .help("NotImplemented: control creation of sparse files. See below")) - .arg(Arg::with_name(OPT_CONTEXT) - .long(OPT_CONTEXT) + .arg(Arg::with_name(options::CONTEXT) + .long(options::CONTEXT) .takes_value(true) .value_name("CTX") .help("NotImplemented: set SELinux security context of destination file to default type")) - .arg(Arg::with_name(OPT_CLI_SYMBOLIC_LINKS) + .arg(Arg::with_name(options::CLI_SYMBOLIC_LINKS) .short("H") .help("NotImplemented: follow command-line symbolic links in SOURCE")) // END TODO - .arg(Arg::with_name(OPT_PATHS) + .arg(Arg::with_name(options::PATHS) .multiple(true)) .get_matches_from(args); @@ -474,7 +476,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } let paths: Vec = matches - .values_of(OPT_PATHS) + .values_of(options::PATHS) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); @@ -496,9 +498,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { impl ClobberMode { fn from_matches(matches: &ArgMatches) -> ClobberMode { - if matches.is_present(OPT_FORCE) { + if matches.is_present(options::FORCE) { ClobberMode::Force - } else if matches.is_present(OPT_REMOVE_DESTINATION) { + } else if matches.is_present(options::REMOVE_DESTINATION) { ClobberMode::RemoveDestination } else { ClobberMode::Standard @@ -508,9 +510,9 @@ impl ClobberMode { impl OverwriteMode { fn from_matches(matches: &ArgMatches) -> OverwriteMode { - if matches.is_present(OPT_INTERACTIVE) { + if matches.is_present(options::INTERACTIVE) { OverwriteMode::Interactive(ClobberMode::from_matches(matches)) - } else if matches.is_present(OPT_NO_CLOBBER) { + } else if matches.is_present(options::NO_CLOBBER) { OverwriteMode::NoClobber } else { OverwriteMode::Clobber(ClobberMode::from_matches(matches)) @@ -520,15 +522,15 @@ impl OverwriteMode { impl CopyMode { fn from_matches(matches: &ArgMatches) -> CopyMode { - if matches.is_present(OPT_LINK) { + if matches.is_present(options::LINK) { CopyMode::Link - } else if matches.is_present(OPT_SYMBOLIC_LINK) { + } else if matches.is_present(options::SYMBOLIC_LINK) { CopyMode::SymLink - } else if matches.is_present(OPT_SPARSE) { + } else if matches.is_present(options::SPARSE) { CopyMode::Sparse - } else if matches.is_present(OPT_UPDATE) { + } else if matches.is_present(options::UPDATE) { CopyMode::Update - } else if matches.is_present(OPT_ATTRIBUTES_ONLY) { + } else if matches.is_present(options::ATTRIBUTES_ONLY) { CopyMode::AttrOnly } else { CopyMode::Copy @@ -576,13 +578,13 @@ fn add_all_attributes() -> Vec { impl Options { fn from_matches(matches: &ArgMatches) -> CopyResult { let not_implemented_opts = vec![ - OPT_COPY_CONTENTS, - OPT_SPARSE, + options::COPY_CONTENTS, + options::SPARSE, #[cfg(not(any(windows, unix)))] - OPT_ONE_FILE_SYSTEM, - OPT_CONTEXT, + options::ONE_FILE_SYSTEM, + options::CONTEXT, #[cfg(windows)] - OPT_FORCE, + options::FORCE, ]; for not_implemented_opt in not_implemented_opts { @@ -591,27 +593,28 @@ impl Options { } } - let recursive = matches.is_present(OPT_RECURSIVE) - || matches.is_present(OPT_RECURSIVE_ALIAS) - || matches.is_present(OPT_ARCHIVE); + let recursive = matches.is_present(options::RECURSIVE) + || matches.is_present(options::RECURSIVE_ALIAS) + || matches.is_present(options::ARCHIVE); let backup_mode = backup_control::determine_backup_mode( - matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP), - matches.value_of(OPT_BACKUP), + matches.is_present(options::BACKUP_NO_ARG) || matches.is_present(options::BACKUP), + matches.value_of(options::BACKUP), ); - let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)); + let backup_suffix = + backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX)); let overwrite = OverwriteMode::from_matches(matches); // Parse target directory options - let no_target_dir = matches.is_present(OPT_NO_TARGET_DIRECTORY); + let no_target_dir = matches.is_present(options::NO_TARGET_DIRECTORY); let target_dir = matches - .value_of(OPT_TARGET_DIRECTORY) + .value_of(options::TARGET_DIRECTORY) .map(ToString::to_string); // Parse attributes to preserve - let preserve_attributes: Vec = if matches.is_present(OPT_PRESERVE) { - match matches.values_of(OPT_PRESERVE) { + let preserve_attributes: Vec = if matches.is_present(options::PRESERVE) { + match matches.values_of(options::PRESERVE) { None => DEFAULT_ATTRIBUTES.to_vec(), Some(attribute_strs) => { let mut attributes = Vec::new(); @@ -626,33 +629,33 @@ impl Options { attributes } } - } else if matches.is_present(OPT_ARCHIVE) { + } else if matches.is_present(options::ARCHIVE) { // --archive is used. Same as --preserve=all add_all_attributes() - } else if matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS) { + } else if matches.is_present(options::NO_DEREFERENCE_PRESERVE_LINKS) { vec![Attribute::Links] - } else if matches.is_present(OPT_PRESERVE_DEFAULT_ATTRIBUTES) { + } else if matches.is_present(options::PRESERVE_DEFAULT_ATTRIBUTES) { DEFAULT_ATTRIBUTES.to_vec() } else { vec![] }; let options = Options { - attributes_only: matches.is_present(OPT_ATTRIBUTES_ONLY), - copy_contents: matches.is_present(OPT_COPY_CONTENTS), + attributes_only: matches.is_present(options::ATTRIBUTES_ONLY), + copy_contents: matches.is_present(options::COPY_CONTENTS), copy_mode: CopyMode::from_matches(matches), - dereference: matches.is_present(OPT_DEREFERENCE), + dereference: matches.is_present(options::DEREFERENCE), // No dereference is set with -p, -d and --archive - no_dereference: matches.is_present(OPT_NO_DEREFERENCE) - || matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS) - || matches.is_present(OPT_ARCHIVE), - one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM), - parents: matches.is_present(OPT_PARENTS), - update: matches.is_present(OPT_UPDATE), - verbose: matches.is_present(OPT_VERBOSE), - strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES), + no_dereference: matches.is_present(options::NO_DEREFERENCE) + || matches.is_present(options::NO_DEREFERENCE_PRESERVE_LINKS) + || matches.is_present(options::ARCHIVE), + one_file_system: matches.is_present(options::ONE_FILE_SYSTEM), + parents: matches.is_present(options::PARENTS), + update: matches.is_present(options::UPDATE), + verbose: matches.is_present(options::VERBOSE), + strip_trailing_slashes: matches.is_present(options::STRIP_TRAILING_SLASHES), reflink_mode: { - if let Some(reflink) = matches.value_of(OPT_REFLINK) { + if let Some(reflink) = matches.value_of(options::REFLINK) { match reflink { "always" => ReflinkMode::Always, "auto" => ReflinkMode::Auto, @@ -1177,7 +1180,7 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { CopyMode::SymLink => { symlink_file(source, dest, &*context_for(source, dest))?; } - CopyMode::Sparse => return Err(Error::NotImplemented(OPT_SPARSE.to_string())), + CopyMode::Sparse => return Err(Error::NotImplemented(options::SPARSE.to_string())), CopyMode::Update => { if dest.exists() { let src_metadata = fs::metadata(source)?; From ab3f2cb6728afc42e5595e8ea4ad6a2e5837baa9 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 18 Jun 2021 17:56:22 +0200 Subject: [PATCH 238/320] cp: update list of implemented arguments --- src/uu/cp/README.md | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/uu/cp/README.md b/src/uu/cp/README.md index 91753eb66..5745cb8a0 100644 --- a/src/uu/cp/README.md +++ b/src/uu/cp/README.md @@ -7,37 +7,38 @@ ### To Do -- [ ] archive -- [ ] attributes-only -- [ ] copy-contents -- [ ] no-dereference-preserve-linkgs -- [ ] dereference -- [ ] no-dereference -- [ ] preserve-default-attributes -- [ ] preserve -- [ ] no-preserve -- [ ] parents -- [ ] reflink -- [ ] sparse -- [ ] strip-trailing-slashes -- [ ] update -- [ ] one-file-system -- [ ] context + - [ ] cli-symbolic-links +- [ ] context +- [ ] copy-contents +- [ ] sparse ### Completed +- [x] archive +- [x] attributes-only - [x] backup +- [x] dereference - [x] force (Not implemented on Windows) - [x] interactive - [x] link - [x] no-clobber +- [x] no-dereference +- [x] no-dereference-preserve-links +- [x] no-preserve - [x] no-target-directory +- [x] one-file-system +- [x] parents - [x] paths +- [x] preserve +- [x] preserve-default-attributes - [x] recursive +- [x] reflink - [x] remove-destination (On Windows, current only works for writeable files) +- [x] strip-trailing-slashes - [x] suffix - [x] symbolic-link - [x] target-directory +- [x] update - [x] verbose - [x] version From 14520fb64ef5152af0bcb267cde3bf236d4745de Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 18 Jun 2021 18:00:27 +0200 Subject: [PATCH 239/320] cp: remove redundant newline in readme --- src/uu/cp/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/cp/README.md b/src/uu/cp/README.md index 5745cb8a0..13399c3b1 100644 --- a/src/uu/cp/README.md +++ b/src/uu/cp/README.md @@ -7,7 +7,6 @@ ### To Do - - [ ] cli-symbolic-links - [ ] context - [ ] copy-contents From 285eeac1fb88d1d3b354ec77f73375546fd72e75 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 18 Jun 2021 18:49:39 +0200 Subject: [PATCH 240/320] tests/pr: include one more possible minute --- tests/by-util/test_pr.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_pr.rs b/tests/by-util/test_pr.rs index fb6703f28..4a79a3eda 100644 --- a/tests/by-util/test_pr.rs +++ b/tests/by-util/test_pr.rs @@ -22,6 +22,7 @@ fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String { } fn all_minutes(from: DateTime, to: DateTime) -> Vec { + let to = to + Duration::minutes(1); const FORMAT: &str = "%b %d %H:%M %Y"; let mut vec = vec![]; let mut current = from; From 65fe9beaada0d0c60638a98beeeb6219178cf78a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 19 Jun 2021 08:58:33 +0200 Subject: [PATCH 241/320] bring back #[cfg(windows)] --- src/uu/du/src/du.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index e4bac2e18..fa6c34165 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -25,6 +25,7 @@ use std::os::unix::fs::MetadataExt; use std::os::windows::fs::MetadataExt; #[cfg(windows)] use std::os::windows::io::AsRawHandle; +#[cfg(windows)] use std::path::Path; use std::path::PathBuf; use std::str::FromStr; From 7b9814c7784dd93c31df03e12038ada37082dd55 Mon Sep 17 00:00:00 2001 From: Tuomas Tynkkynen Date: Fri, 18 Jun 2021 16:56:00 +0300 Subject: [PATCH 242/320] test: Implement [ expr ] syntax When invoked via '[' name, last argument must be ']' or we bail out with syntax error. Then the trailing ']' is simply disregarded and processing happens like usual. --- build.rs | 23 +++++++++++++++++++++++ src/uu/test/src/test.rs | 20 +++++++++++++++++--- tests/by-util/test_test.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/build.rs b/build.rs index ae38177b0..2ed8e1345 100644 --- a/build.rs +++ b/build.rs @@ -54,6 +54,29 @@ pub fn main() { for krate in crates { match krate.as_ref() { + // 'test' is named uu_test to avoid collision with rust core crate 'test'. + // It can also be invoked by name '[' for the '[ expr ] syntax'. + "uu_test" => { + mf.write_all( + format!( + "\ + \tmap.insert(\"test\", {krate}::uumain);\n\ + \t\tmap.insert(\"[\", {krate}::uumain);\n\ + ", + krate = krate + ) + .as_bytes(), + ) + .unwrap(); + tf.write_all( + format!( + "#[path=\"{dir}/test_test.rs\"]\nmod test_test;\n", + dir = util_tests_dir, + ) + .as_bytes(), + ) + .unwrap() + } k if k.starts_with(override_prefix) => { mf.write_all( format!( diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 5f20b95f0..97a244cdc 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -12,10 +12,24 @@ mod parser; use parser::{parse, Symbol}; use std::ffi::{OsStr, OsString}; +use std::path::Path; -pub fn uumain(args: impl uucore::Args) -> i32 { - // TODO: handle being called as `[` - let args: Vec<_> = args.skip(1).collect(); +pub fn uumain(mut args: impl uucore::Args) -> i32 { + let program = args.next().unwrap_or_else(|| OsString::from("test")); + let binary_name = Path::new(&program) + .file_name() + .unwrap_or_else(|| OsStr::new("test")) + .to_string_lossy(); + let mut args: Vec<_> = args.collect(); + + // If invoked via name '[', matching ']' must be in the last arg + if binary_name == "[" { + let last = args.pop(); + if last != Some(OsString::from("]")) { + eprintln!("[: missing ']'"); + return 2; + } + } let result = parse(args).and_then(|mut stack| eval(&mut stack)); diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index c4964d6bf..36e825f2d 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -690,3 +690,31 @@ fn test_or_as_filename() { fn test_string_length_and_nothing() { new_ucmd!().args(&["-n", "a", "-a"]).run().status_code(2); } + +#[test] +fn test_bracket_syntax_success() { + let scenario = TestScenario::new("["); + let mut ucmd = scenario.ucmd(); + + ucmd.args(&["1", "-eq", "1", "]"]).succeeds(); +} + +#[test] +fn test_bracket_syntax_failure() { + let scenario = TestScenario::new("["); + let mut ucmd = scenario.ucmd(); + + ucmd.args(&["1", "-eq", "2", "]"]).run().status_code(1); +} + +#[test] +fn test_bracket_syntax_missing_right_bracket() { + let scenario = TestScenario::new("["); + let mut ucmd = scenario.ucmd(); + + // Missing closing bracket takes precedence over other possible errors. + ucmd.args(&["1", "-eq"]) + .run() + .status_code(2) + .stderr_is("[: missing ']'"); +} From 6400cded54ba436ad72f0758dfafe174491e378e Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 19 Jun 2021 17:45:45 +0200 Subject: [PATCH 243/320] cp: fix order of checks in `copy_helper` --- src/uu/cp/src/cp.rs | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 851117bde..cf723e4ee 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1218,28 +1218,38 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a /// copy-on-write scheme if --reflink is specified and the filesystem supports it. fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { - if options.reflink_mode != ReflinkMode::Never { - #[cfg(not(any(target_os = "linux", target_os = "macos")))] - return Err("--reflink is only supported on linux and macOS" - .to_string() - .into()); - - #[cfg(target_os = "macos")] - copy_on_write_macos(source, dest, options.reflink_mode)?; - #[cfg(target_os = "linux")] - copy_on_write_linux(source, dest, options.reflink_mode)?; - } else if !options.dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() { - copy_link(source, dest)?; - } else if source.to_string_lossy() == "/dev/null" { + if options.parents { + let parent = dest.parent().unwrap_or(dest); + fs::create_dir_all(parent)?; + } + let is_symlink = fs::symlink_metadata(&source)?.file_type().is_symlink(); + if source.to_string_lossy() == "/dev/null" { /* workaround a limitation of fs::copy * https://github.com/rust-lang/rust/issues/79390 */ File::create(dest)?; - } else { - if options.parents { - let parent = dest.parent().unwrap_or(dest); - fs::create_dir_all(parent)?; + } else if !options.dereference && is_symlink { + copy_link(source, dest)?; + } else if options.reflink_mode != ReflinkMode::Never { + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + return Err("--reflink is only supported on linux and macOS" + .to_string() + .into()); + if is_symlink { + assert!(options.dereference); + let real_path = std::fs::read_link(source)?; + + #[cfg(target_os = "macos")] + copy_on_write_macos(&real_path, dest, options.reflink_mode)?; + #[cfg(target_os = "linux")] + copy_on_write_linux(&real_path, dest, options.reflink_mode)?; + } else { + #[cfg(target_os = "macos")] + copy_on_write_macos(source, dest, options.reflink_mode)?; + #[cfg(target_os = "linux")] + copy_on_write_linux(source, dest, options.reflink_mode)?; } + } else { fs::copy(source, dest).context(&*context_for(source, dest))?; } From 9fb927aa856eec92a58b6cfbf53438e4a48f71e2 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 19 Jun 2021 17:49:04 +0200 Subject: [PATCH 244/320] cp: always delete the destination for symlinks --- src/uu/cp/src/cp.rs | 5 +++++ tests/by-util/test_cp.rs | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index cf723e4ee..840035e4a 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1269,6 +1269,11 @@ fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> { ), } } else { + // we always need to remove the file to be able to create a symlink, + // even if it is writeable. + if dest.exists() { + fs::remove_file(dest)?; + } dest.into() }; symlink_file(&link, &dest, &*context_for(&link, &dest)) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 4ce587e02..19f93e499 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1325,3 +1325,16 @@ fn test_copy_dir_with_symlinks() { ucmd.args(&["-r", "dir", "copy"]).succeeds(); assert_eq!(at.resolve_link("copy/file-link"), "file"); } + +#[test] +#[cfg(not(windows))] +fn test_copy_symlink_force() { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file"); + at.symlink_file("file", "file-link"); + at.touch("copy"); + + ucmd.args(&["file-link", "copy", "-f", "--no-dereference"]) + .succeeds(); + assert_eq!(at.resolve_link("copy"), "file"); +} From 076c7fa501d43a190ab4356dfaa583677c80dc3b Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 19 Jun 2021 17:49:41 +0200 Subject: [PATCH 245/320] cp: default to --reflink=auto on linux and macos --- src/uu/cp/src/cp.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 840035e4a..7cf6a1d9b 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -667,7 +667,14 @@ impl Options { } } } else { - ReflinkMode::Never + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + ReflinkMode::Auto + } + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + ReflinkMode::Never + } } }, backup: backup_mode, From 3086e95702073e3ad0c5e8067f2ff650ab85141f Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 20 Jun 2021 00:21:14 +0200 Subject: [PATCH 246/320] numfmt: add round and use C locale style for errors --- src/uu/numfmt/src/format.rs | 60 +++++++++++++++++------------------- src/uu/numfmt/src/numfmt.rs | 38 +++++++++++++++++------ src/uu/numfmt/src/options.rs | 41 ++++++++++++++++++++++-- src/uu/numfmt/src/units.rs | 4 --- tests/by-util/test_numfmt.rs | 44 ++++++++++++++++++++------ 5 files changed, 129 insertions(+), 58 deletions(-) diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index ee692d8f0..f0f1bf739 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -1,7 +1,5 @@ -use crate::options::NumfmtOptions; -use crate::units::{ - DisplayableSuffix, RawSuffix, Result, Suffix, Transform, Unit, IEC_BASES, SI_BASES, -}; +use crate::options::{NumfmtOptions, RoundMethod}; +use crate::units::{DisplayableSuffix, RawSuffix, Result, Suffix, Unit, IEC_BASES, SI_BASES}; /// Iterate over a line's fields, where each field is a contiguous sequence of /// non-whitespace, optionally prefixed with one or more characters of leading @@ -62,7 +60,7 @@ impl<'a> Iterator for WhitespaceSplitter<'a> { fn parse_suffix(s: &str) -> Result<(f64, Option)> { if s.is_empty() { - return Err("invalid number: ‘’".to_string()); + return Err("invalid number: ''".to_string()); } let with_i = s.ends_with('i'); @@ -70,18 +68,18 @@ fn parse_suffix(s: &str) -> Result<(f64, Option)> { if with_i { iter.next_back(); } - let suffix: Option = match iter.next_back() { - Some('K') => Ok(Some((RawSuffix::K, with_i))), - Some('M') => Ok(Some((RawSuffix::M, with_i))), - Some('G') => Ok(Some((RawSuffix::G, with_i))), - Some('T') => Ok(Some((RawSuffix::T, with_i))), - Some('P') => Ok(Some((RawSuffix::P, with_i))), - Some('E') => Ok(Some((RawSuffix::E, with_i))), - Some('Z') => Ok(Some((RawSuffix::Z, with_i))), - Some('Y') => Ok(Some((RawSuffix::Y, with_i))), - Some('0'..='9') => Ok(None), - _ => Err(format!("invalid suffix in input: ‘{}’", s)), - }?; + let suffix = match iter.next_back() { + Some('K') => Some((RawSuffix::K, with_i)), + Some('M') => Some((RawSuffix::M, with_i)), + Some('G') => Some((RawSuffix::G, with_i)), + Some('T') => Some((RawSuffix::T, with_i)), + Some('P') => Some((RawSuffix::P, with_i)), + Some('E') => Some((RawSuffix::E, with_i)), + Some('Z') => Some((RawSuffix::Z, with_i)), + Some('Y') => Some((RawSuffix::Y, with_i)), + Some('0'..='9') => None, + _ => return Err(format!("invalid suffix in input: '{}'", s)), + }; let suffix_len = match suffix { None => 0, @@ -91,7 +89,7 @@ fn parse_suffix(s: &str) -> Result<(f64, Option)> { let number = s[..s.len() - suffix_len] .parse::() - .map_err(|_| format!("invalid number: ‘{}’", s))?; + .map_err(|_| format!("invalid number: '{}'", s))?; Ok((number, suffix)) } @@ -127,10 +125,10 @@ fn remove_suffix(i: f64, s: Option, u: &Unit) -> Result { } } -fn transform_from(s: &str, opts: &Transform) -> Result { +fn transform_from(s: &str, opts: &Unit) -> Result { let (i, suffix) = parse_suffix(s)?; - remove_suffix(i, suffix, &opts.unit).map(|n| if n < 0.0 { -n.abs().ceil() } else { n.ceil() }) + remove_suffix(i, suffix, opts).map(|n| if n < 0.0 { -n.abs().ceil() } else { n.ceil() }) } /// Divide numerator by denominator, with ceiling. @@ -153,18 +151,17 @@ fn transform_from(s: &str, opts: &Transform) -> Result { /// assert_eq!(div_ceil(1000.0, -3.14), -319.0); /// assert_eq!(div_ceil(-271828.0, -271.0), 1004.0); /// ``` -pub fn div_ceil(n: f64, d: f64) -> f64 { - let v = n / (d / 10.0); - let (v, sign) = if v < 0.0 { (v.abs(), -1.0) } else { (v, 1.0) }; +pub fn div_round(n: f64, d: f64, method: RoundMethod) -> f64 { + let v = n / d; - if v < 100.0 { - v.ceil() / 10.0 * sign + if v.abs() < 10.0 { + method.round(10.0 * v) / 10.0 } else { - (v / 10.0).ceil() * sign + method.round(v) } } -fn consider_suffix(n: f64, u: &Unit) -> Result<(f64, Option)> { +fn consider_suffix(n: f64, u: &Unit, round_method: RoundMethod) -> Result<(f64, Option)> { use crate::units::RawSuffix::*; let abs_n = n.abs(); @@ -190,7 +187,7 @@ fn consider_suffix(n: f64, u: &Unit) -> Result<(f64, Option)> { _ => return Err("Number is too big and unsupported".to_string()), }; - let v = div_ceil(n, bases[i]); + let v = div_round(n, bases[i], round_method); // check if rounding pushed us into the next base if v.abs() >= bases[1] { @@ -200,8 +197,8 @@ fn consider_suffix(n: f64, u: &Unit) -> Result<(f64, Option)> { } } -fn transform_to(s: f64, opts: &Transform) -> Result { - let (i2, s) = consider_suffix(s, &opts.unit)?; +fn transform_to(s: f64, opts: &Unit, round_method: RoundMethod) -> Result { + let (i2, s) = consider_suffix(s, opts, round_method)?; Ok(match s { None => format!("{}", i2), Some(s) if i2.abs() < 10.0 => format!("{:.1}{}", i2, DisplayableSuffix(s)), @@ -217,10 +214,11 @@ fn format_string( let number = transform_to( transform_from(source, &options.transform.from)?, &options.transform.to, + options.round, )?; Ok(match implicit_padding.unwrap_or(options.padding) { - p if p == 0 => number, + 0 => number, p if p > 0 => format!("{:>padding$}", number, padding = p as usize), p => format!("{: Result { let from = parse_unit(args.value_of(options::FROM).unwrap())?; let to = parse_unit(args.value_of(options::TO).unwrap())?; - let transform = TransformOptions { - from: Transform { unit: from }, - to: Transform { unit: to }, - }; + let transform = TransformOptions { from, to }; let padding = match args.value_of(options::PADDING) { Some(s) => s.parse::().map_err(|err| err.to_string()), @@ -114,17 +111,16 @@ fn parse_options(args: &ArgMatches) -> Result { 0 => Err(value), _ => Ok(n), }) - .map_err(|value| format!("invalid header value ‘{}’", value)) + .map_err(|value| format!("invalid header value '{}'", value)) } }?; - let fields = match args.value_of(options::FIELD) { - Some("-") => vec![Range { + let fields = match args.value_of(options::FIELD).unwrap() { + "-" => vec![Range { low: 1, high: std::usize::MAX, }], - Some(v) => Range::from_list(v)?, - None => unreachable!(), + v => Range::from_list(v)?, }; let delimiter = args.value_of(options::DELIMITER).map_or(Ok(None), |arg| { @@ -135,12 +131,23 @@ fn parse_options(args: &ArgMatches) -> Result { } })?; + // unwrap is fine because the argument has a default value + let round = match args.value_of(options::ROUND).unwrap() { + "up" => RoundMethod::Up, + "down" => RoundMethod::Down, + "from-zero" => RoundMethod::FromZero, + "towards-zero" => RoundMethod::TowardsZero, + "nearest" => RoundMethod::Nearest, + _ => unreachable!("Should be restricted by clap"), + }; + Ok(NumfmtOptions { transform, padding, header, fields, delimiter, + round, }) } @@ -203,6 +210,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .default_value(options::HEADER_DEFAULT) .hide_default_value(true), ) + .arg( + Arg::with_name(options::ROUND) + .long(options::ROUND) + .help( + "use METHOD for rounding when scaling; METHOD can be: up,\ + down, from-zero (default), towards-zero, nearest", + ) + .value_name("METHOD") + .default_value("from-zero") + .possible_values(&["up", "down", "from-zero", "towards-zero", "nearest"]), + ) .arg(Arg::with_name(options::NUMBER).hidden(true).multiple(true)) .get_matches_from(args); diff --git a/src/uu/numfmt/src/options.rs b/src/uu/numfmt/src/options.rs index 17f0a6fbe..59bf9d8d3 100644 --- a/src/uu/numfmt/src/options.rs +++ b/src/uu/numfmt/src/options.rs @@ -1,4 +1,4 @@ -use crate::units::Transform; +use crate::units::Unit; use uucore::ranges::Range; pub const DELIMITER: &str = "delimiter"; @@ -10,12 +10,13 @@ pub const HEADER: &str = "header"; pub const HEADER_DEFAULT: &str = "1"; pub const NUMBER: &str = "NUMBER"; pub const PADDING: &str = "padding"; +pub const ROUND: &str = "round"; pub const TO: &str = "to"; pub const TO_DEFAULT: &str = "none"; pub struct TransformOptions { - pub from: Transform, - pub to: Transform, + pub from: Unit, + pub to: Unit, } pub struct NumfmtOptions { @@ -24,4 +25,38 @@ pub struct NumfmtOptions { pub header: usize, pub fields: Vec, pub delimiter: Option, + pub round: RoundMethod, +} + +#[derive(Clone, Copy)] +pub enum RoundMethod { + Up, + Down, + FromZero, + TowardsZero, + Nearest, +} + +impl RoundMethod { + pub fn round(&self, f: f64) -> f64 { + match self { + RoundMethod::Up => f.ceil(), + RoundMethod::Down => f.floor(), + RoundMethod::FromZero => { + if f < 0.0 { + f.floor() + } else { + f.ceil() + } + } + RoundMethod::TowardsZero => { + if f < 0.0 { + f.ceil() + } else { + f.floor() + } + } + RoundMethod::Nearest => f.round(), + } + } } diff --git a/src/uu/numfmt/src/units.rs b/src/uu/numfmt/src/units.rs index 5f9907bdf..8a2895ab7 100644 --- a/src/uu/numfmt/src/units.rs +++ b/src/uu/numfmt/src/units.rs @@ -24,10 +24,6 @@ pub enum Unit { None, } -pub struct Transform { - pub unit: Unit, -} - pub type Result = std::result::Result; #[derive(Clone, Copy, Debug)] diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index bb29d431e..336b0f7cd 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -35,7 +35,7 @@ fn test_from_iec_i_requires_suffix() { new_ucmd!() .args(&["--from=iec-i", "1024"]) .fails() - .stderr_is("numfmt: missing 'i' suffix in input: ‘1024’ (e.g Ki/Mi/Gi)"); + .stderr_is("numfmt: missing 'i' suffix in input: '1024' (e.g Ki/Mi/Gi)"); } #[test] @@ -123,7 +123,7 @@ fn test_header_error_if_non_numeric() { new_ucmd!() .args(&["--header=two"]) .run() - .stderr_is("numfmt: invalid header value ‘two’"); + .stderr_is("numfmt: invalid header value 'two'"); } #[test] @@ -131,7 +131,7 @@ fn test_header_error_if_0() { new_ucmd!() .args(&["--header=0"]) .run() - .stderr_is("numfmt: invalid header value ‘0’"); + .stderr_is("numfmt: invalid header value '0'"); } #[test] @@ -139,7 +139,7 @@ fn test_header_error_if_negative() { new_ucmd!() .args(&["--header=-3"]) .run() - .stderr_is("numfmt: invalid header value ‘-3’"); + .stderr_is("numfmt: invalid header value '-3'"); } #[test] @@ -187,7 +187,7 @@ fn test_should_report_invalid_empty_number_on_empty_stdin() { .args(&["--from=auto"]) .pipe_in("\n") .run() - .stderr_is("numfmt: invalid number: ‘’\n"); + .stderr_is("numfmt: invalid number: ''\n"); } #[test] @@ -196,7 +196,7 @@ fn test_should_report_invalid_empty_number_on_blank_stdin() { .args(&["--from=auto"]) .pipe_in(" \t \n") .run() - .stderr_is("numfmt: invalid number: ‘’\n"); + .stderr_is("numfmt: invalid number: ''\n"); } #[test] @@ -205,14 +205,14 @@ fn test_should_report_invalid_suffix_on_stdin() { .args(&["--from=auto"]) .pipe_in("1k") .run() - .stderr_is("numfmt: invalid suffix in input: ‘1k’\n"); + .stderr_is("numfmt: invalid suffix in input: '1k'\n"); // GNU numfmt reports this one as “invalid number” new_ucmd!() .args(&["--from=auto"]) .pipe_in("NaN") .run() - .stderr_is("numfmt: invalid suffix in input: ‘NaN’\n"); + .stderr_is("numfmt: invalid suffix in input: 'NaN'\n"); } #[test] @@ -222,7 +222,7 @@ fn test_should_report_invalid_number_with_interior_junk() { .args(&["--from=auto"]) .pipe_in("1x0K") .run() - .stderr_is("numfmt: invalid number: ‘1x0K’\n"); + .stderr_is("numfmt: invalid number: '1x0K'\n"); } #[test] @@ -461,7 +461,7 @@ fn test_delimiter_overrides_whitespace_separator() { .args(&["-d,"]) .pipe_in("1 234,56") .fails() - .stderr_is("numfmt: invalid number: ‘1 234’\n"); + .stderr_is("numfmt: invalid number: '1 234'\n"); } #[test] @@ -481,3 +481,27 @@ fn test_delimiter_with_padding_and_fields() { .succeeds() .stdout_only(" 1.0K| 2.0K\n"); } + +#[test] +fn test_round() { + for (method, exp) in &[ + ("from-zero", ["9.1K", "-9.1K", "9.1K", "-9.1K"]), + ("towards-zero", ["9.0K", "-9.0K", "9.0K", "-9.0K"]), + ("up", ["9.1K", "-9.0K", "9.1K", "-9.0K"]), + ("down", ["9.0K", "-9.1K", "9.0K", "-9.1K"]), + ("nearest", ["9.0K", "-9.0K", "9.1K", "-9.1K"]), + ] { + new_ucmd!() + .args(&[ + "--to=si", + &format!("--round={}", method), + "--", + "9001", + "-9001", + "9099", + "-9099", + ]) + .succeeds() + .stdout_only(exp.join("\n") + "\n"); + } +} From eaa93e9c27eac914a6735d160706df3709b581d6 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 20 Jun 2021 00:25:02 +0200 Subject: [PATCH 247/320] numfmt: move to common core --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 804c5f978..0fec2af78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ feat_common_core = [ "more", "mv", "nl", + "numfmt", "od", "paste", "pr", @@ -160,7 +161,6 @@ feat_require_unix = [ "mkfifo", "mknod", "nice", - "numfmt", "nohup", "pathchk", "stat", From 90bf26a51c805ec0d8f776ff47f98e4e3e6bb1b1 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 6 Jun 2021 20:21:23 +0200 Subject: [PATCH 248/320] maint/CICD ~ (GHA) update to checkout@v2 --- .github/workflows/CICD.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index fcaddd310..65051a88e 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -26,7 +26,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Initialize workflow variables id: vars shell: bash @@ -66,7 +66,7 @@ jobs: job: - { os: ubuntu-latest } steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Install/setup prerequisites shell: bash run: | @@ -87,7 +87,7 @@ jobs: - { os: macos-latest , features: feat_os_macos } - { os: windows-latest , features: feat_os_windows } steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Initialize workflow variables id: vars shell: bash @@ -122,7 +122,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }}) uses: actions-rs/toolchain@v1 with: @@ -181,7 +181,7 @@ jobs: job: - { os: ubuntu-latest } steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: @@ -212,7 +212,7 @@ jobs: job: - { os: ubuntu-latest } steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: @@ -249,7 +249,7 @@ jobs: - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Install/setup prerequisites shell: bash run: | @@ -488,7 +488,7 @@ jobs: - { os: macos-latest , features: macos } - { os: windows-latest , features: windows } steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Install/setup prerequisites shell: bash run: | From 298851096ef941a3d9658002e5b9eac4f8fb1bea Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 12 Jun 2021 21:35:11 -0500 Subject: [PATCH 249/320] maint/CICD ~ (GHA) remove deprecated 'ubuntu-16.04' environment --- .github/workflows/CICD.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 65051a88e..2667169dd 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -235,8 +235,6 @@ jobs: # { os, target, cargo-options, features, use-cross, toolchain } - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , features: feat_os_unix_gnueabihf , use-cross: use-cross } - { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross } - - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } - - { os: ubuntu-16.04 , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } # - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only # - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only - { os: ubuntu-18.04 , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } From db621c7d7a0a8a23b2fedb0eac5deefa0b74198a Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 12 Jun 2021 13:46:37 -0500 Subject: [PATCH 250/320] maint/CICD ~ (GHA) change/refactor CICD (convert most warnings to errors) - adds additional instruction to error message showing how to fix the error --- .github/workflows/CICD.yml | 167 +++++++++++++++++++------------------ 1 file changed, 88 insertions(+), 79 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 2667169dd..48df4e546 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -48,36 +48,19 @@ jobs: - name: "`fmt` testing" shell: bash run: | - # `fmt` testing + ## `fmt` testing # * convert any warnings to GHA UI annotations; ref: - S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::warning file=\1,line=\2::WARNING: \`cargo fmt\`: style violation/p" ; } + S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::error file=\1,line=\2::ERROR: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt \"\1\"\`)/p" ; exit 1 ; } - name: "`fmt` testing of tests" + if: success() || failure() # run regardless of prior step success/failure shell: bash run: | - # `fmt` testing of tests + ## `fmt` testing of tests # * convert any warnings to GHA UI annotations; ref: - S=$(find tests -name "*.rs" -print0 | xargs -0 cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" | sed -E -n "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::warning file=\1,line=\2::WARNING: \`cargo fmt\`: style violation/p" ; } + S=$(find tests -name "*.rs" -print0 | xargs -0 cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::error file=\1,line=\2::ERROR: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt \"\1\"\`)/p" ; exit 1 ; } - code_spellcheck: - name: Style/spelling - runs-on: ${{ matrix.job.os }} - strategy: - matrix: - job: - - { os: ubuntu-latest } - steps: - - uses: actions/checkout@v2 - - name: Install/setup prerequisites - shell: bash - run: | - sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell -g; - - name: Run `cspell` - shell: bash - run: | - cspell --config .vscode/cSpell.json --no-summary --no-progress "**/*" | sed "s/\(.*\):\(.*\):\(.*\) - \(.*\)/::warning file=\1,line=\2,col=\3::cspell: \4/" || true - - code_warnings: - name: Style/warnings + code_lint: + name: Style/lint runs-on: ${{ matrix.job.os }} strategy: fail-fast: false @@ -106,13 +89,32 @@ jobs: default: true profile: minimal # minimal component installation (ie, no documentation) components: clippy - - name: "`clippy` testing" - if: success() || failure() # run regardless of prior step success/failure + - name: "`clippy` lint testing" shell: bash run: | - # `clippy` testing + ## `clippy` lint testing # * convert any warnings to GHA UI annotations; ref: - S=$(cargo +nightly clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::warning file=\2,line=\3,col=\4::WARNING: \`cargo clippy\`: \1/p;" -e '}' ; } + S=$(cargo +nightly clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+${PWD//\//\\/}\/(.*):([0-9]+):([0-9]+).*$/::error file=\2,line=\3,col=\4::ERROR: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; exit 1 ; } + + code_spellcheck: + name: Style/spelling + runs-on: ${{ matrix.job.os }} + strategy: + matrix: + job: + - { os: ubuntu-latest } + steps: + - uses: actions/checkout@v2 + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell -g ; + - name: Run `cspell` + shell: bash + run: | + ## Run `cspell` + cspell --config .vscode/cSpell.json --no-summary --no-progress "**/*" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::error file=\1,line=\2,col=\3::ERROR: \4 (file:'\1', line:\2)/p" min_version: name: MinRustV # Minimum supported rust version @@ -137,20 +139,20 @@ jobs: use-tool-cache: true env: RUSTUP_TOOLCHAIN: stable - - name: Confirm compatible 'Cargo.lock' + - name: Confirm MinSRV compatible 'Cargo.lock' shell: bash run: | - # Confirm compatible 'Cargo.lock' + ## Confirm MinSRV compatible 'Cargo.lock' # * 'Cargo.lock' is required to be in a format that `cargo` of MinSRV can interpret (eg, v1-format for MinSRV < v1.38) - cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::Incompatible 'Cargo.lock' format; try \`cargo +${{ env.RUST_MIN_SRV }} update\`" ; exit 1 ; } + cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::Incompatible (or out-of-date) 'Cargo.lock' file; update using \`cargo +${{ env.RUST_MIN_SRV }} update\`" ; exit 1 ; } - name: Info shell: bash run: | - # Info - ## environment + ## Info + # environment echo "## environment" echo "CI='${CI}'" - ## tooling info display + # tooling info display echo "## tooling" which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true rustup -V @@ -158,12 +160,11 @@ jobs: cargo -V rustc -V cargo-tree tree -V - ## dependencies + # dependencies echo "## dependency list" cargo fetch --locked --quiet ## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors RUSTUP_TOOLCHAIN=stable cargo-tree tree --frozen --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique - - name: Test uses: actions-rs/cargo@v1 with: @@ -172,8 +173,8 @@ jobs: env: RUSTFLAGS: '-Awarnings' - busybox_test: - name: Busybox test suite + build_makefile: + name: Build/Makefile runs-on: ${{ matrix.job.os }} strategy: fail-fast: false @@ -188,42 +189,19 @@ jobs: toolchain: stable default: true profile: minimal # minimal component installation (ie, no documentation) - - name: "prepare busytest" + - name: Install/setup prerequisites shell: bash run: | - make prepare-busytest - - name: "run busybox testsuite" + ## Install/setup prerequisites + sudo apt-get -y update ; sudo apt-get -y install python3-sphinx ; + - name: "`make build`" shell: bash run: | - bindir=$(pwd)/target/debug - cd tmp/busybox-*/testsuite - ## S=$(bindir=$bindir ./runtest) && printf "%s\n" "$S" || { printf "%s\n" "$S" | grep "FAIL:" | sed -e "s/FAIL: /::warning ::Test failure:/g" ; } - output=$(bindir=$bindir ./runtest 2>&1 || true) - printf "%s\n" "${output}" - n_fails=$(echo "$output" | grep "^FAIL:\s" | wc --lines) - if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi - - makefile_build: - name: Test the build target of the Makefile - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - - { os: ubuntu-latest } - steps: - - uses: actions/checkout@v2 - - name: Install `rust` toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - default: true - profile: minimal # minimal component installation (ie, no documentation) - - name: "Run make build" - shell: bash - run: | - sudo apt-get -y update ; sudo apt-get -y install python3-sphinx; make build + - name: "`make test`" + shell: bash + run: | + make test build: name: Build @@ -251,7 +229,7 @@ jobs: - name: Install/setup prerequisites shell: bash run: | - ## install/setup prerequisites + ## Install/setup prerequisites case '${{ matrix.job.target }}' in arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; @@ -350,7 +328,7 @@ jobs: - name: Create all needed build/work directories shell: bash run: | - ## create build/work space + ## Create build/work space mkdir -p '${{ steps.vars.outputs.STAGING }}' mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}' mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg' @@ -387,15 +365,15 @@ jobs: - name: Info shell: bash run: | - # Info - ## commit info + ## Info + # commit info echo "## commit" echo GITHUB_REF=${GITHUB_REF} echo GITHUB_SHA=${GITHUB_SHA} - ## environment + # environment echo "## environment" echo "CI='${CI}'" - ## tooling info display + # tooling info display echo "## tooling" which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true rustup -V @@ -403,7 +381,7 @@ jobs: cargo -V rustc -V cargo-tree tree -V - ## dependencies + # dependencies echo "## dependency list" cargo fetch --locked --quiet cargo-tree tree --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all --no-dev-dependencies --no-indent | grep -vE "$PWD" | sort --unique @@ -433,7 +411,7 @@ jobs: - name: Package shell: bash run: | - ## package artifact(s) + ## Package artifact(s) # binary cp 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/' # `strip` binary (if needed) @@ -474,6 +452,37 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + test_busybox: + name: Tests/BusyBox test suite + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest } + steps: + - uses: actions/checkout@v2 + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + profile: minimal # minimal component installation (ie, no documentation) + - name: Install/setup prerequisites + shell: bash + run: | + make prepare-busytest + - name: "Run BusyBox test suite" + shell: bash + run: | + ## Run BusyBox test suite + bindir=$(pwd)/target/debug + cd tmp/busybox-*/testsuite + output=$(bindir=$bindir ./runtest 2>&1 || true) + printf "%s\n" "${output}" + n_fails=$(echo "$output" | grep "^FAIL:\s" | wc --lines) + if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi + coverage: name: Code Coverage runs-on: ${{ matrix.job.os }} @@ -490,7 +499,7 @@ jobs: - name: Install/setup prerequisites shell: bash run: | - ## install/setup prerequisites + ## Install/setup prerequisites case '${{ matrix.job.os }}' in macos-latest) brew install coreutils ;; # needed for testing esac @@ -584,7 +593,7 @@ jobs: id: coverage shell: bash run: | - # generate coverage data + ## Generate coverage data COVERAGE_REPORT_DIR="target/debug" COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info" # GRCOV_IGNORE_OPTION='--ignore build.rs --ignore "/*" --ignore "[a-zA-Z]:/*"' ## `grcov` ignores these params when passed as an environment variable (why?) From 92630a06904072a4db064b25b33366cfe9bee2c1 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 12 Jun 2021 19:16:06 -0500 Subject: [PATCH 251/320] maint/CICD ~ (GHA) add 'Style/dependencies' checks --- .github/workflows/CICD.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 48df4e546..9d2edeac1 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -17,6 +17,40 @@ env: on: [push, pull_request] jobs: + code_deps: + name: Style/dependencies + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + steps: + - uses: actions/checkout@v2 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # target-specific options + # * CARGO_FEATURES_OPTION + CARGO_FEATURES_OPTION='' ; + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi + outputs CARGO_FEATURES_OPTION + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + profile: minimal # minimal component installation (ie, no documentation) + - name: "`cargo update` testing" + shell: bash + run: | + ## `cargo update` testing + # * convert any warnings to GHA UI annotations; ref: + cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::'Cargo.lock' file requires update (use \`cargo +${{ env.RUST_MIN_SRV }} update\`)" ; exit 1 ; } + code_format: name: Style/format runs-on: ${{ matrix.job.os }} From 5682cf30320f144c6e11fff8371767ba47974c9a Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 12 Jun 2021 17:34:48 -0500 Subject: [PATCH 252/320] maint/CICD ~ (GHA) update 'GNU' workflow - show dashboard warnings only when tests FAIL or ERROR - improve comments - fix spelling and spelling exceptions --- .github/workflows/GNU.yml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 1f9250900..7ed5f4911 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -1,5 +1,7 @@ name: GNU +# spell-checker:ignore (names) gnulib ; (utils) autopoint gperf pyinotify texinfo ; (vars) XPASS + on: [push, pull_request] jobs: @@ -7,7 +9,6 @@ jobs: name: Run GNU tests runs-on: ubuntu-latest steps: - # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code uutil uses: actions/checkout@v2 with: @@ -18,7 +19,7 @@ jobs: repository: 'coreutils/coreutils' path: 'gnu' ref: v8.32 - - name: Checkout GNU corelib + - name: Checkout GNU coreutils library (gnulib) uses: actions/checkout@v2 with: repository: 'coreutils/gnulib' @@ -32,23 +33,26 @@ jobs: default: true profile: minimal # minimal component installation (ie, no documentation) components: rustfmt - - name: Install deps + - name: Install dependencies shell: bash run: | + ## Install dependencies sudo apt-get update sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx jq - name: Build binaries shell: bash run: | - cd uutils - bash util/build-gnu.sh + ## Build binaries + cd uutils + bash util/build-gnu.sh - name: Run GNU tests shell: bash run: | bash uutils/util/run-gnu-test.sh - - name: Extract tests info + - name: Extract testing info shell: bash run: | + ## Extract testing info LOG_FILE=gnu/tests/test-suite.log if test -f "$LOG_FILE" then @@ -58,7 +62,9 @@ jobs: FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - echo "::warning ::GNU testsuite = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR" + output="GNU tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR" + echo "${output}" + if [[ "$FAIL" -gt 0 || "$ERROR" -gt 0 ]]; then echo "::warning ::${output}" ; fi jq -n \ --arg date "$(date --rfc-email)" \ --arg sha "$GITHUB_SHA" \ @@ -72,12 +78,10 @@ jobs: else echo "::error ::Failed to get summary of test results" fi - - uses: actions/upload-artifact@v2 with: name: test-report path: gnu/tests/**/*.log - - uses: actions/upload-artifact@v2 with: name: gnu-result From dd46c2f03b3c085828cbafc9e01ad63f5d06d061 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 12 Jun 2021 23:37:00 -0500 Subject: [PATCH 253/320] maint/CICD ~ (GHA) rename 'GNU' workflow to 'GnuTests' --- .github/workflows/{GNU.yml => GnuTests.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{GNU.yml => GnuTests.yml} (99%) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GnuTests.yml similarity index 99% rename from .github/workflows/GNU.yml rename to .github/workflows/GnuTests.yml index 7ed5f4911..90af6a689 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GnuTests.yml @@ -1,4 +1,4 @@ -name: GNU +name: GnuTests # spell-checker:ignore (names) gnulib ; (utils) autopoint gperf pyinotify texinfo ; (vars) XPASS From c171b13982dfae75e6e53f2c76934e0c37e94762 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 12 Jun 2021 19:50:40 -0500 Subject: [PATCH 254/320] docs/spell ~ update cspell dictionaries --- .vscode/cspell.dictionaries/acronyms+names.wordlist.txt | 2 ++ .vscode/cspell.dictionaries/jargon.wordlist.txt | 1 + .vscode/cspell.dictionaries/workspace.wordlist.txt | 8 +++++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt index 3956d1d8a..a46448a32 100644 --- a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt +++ b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt @@ -12,6 +12,7 @@ FIFOs FQDN # fully qualified domain name GID # group ID GIDs +GNU GNUEABI GNUEABIhf JFS @@ -45,6 +46,7 @@ Deno EditorConfig FreeBSD Gmail +GNU Irix MS-DOS MSDOS diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 89af1b153..c2e2c29f3 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -78,6 +78,7 @@ symlinks syscall syscalls tokenize +toolchain truthy unbuffered unescape diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index ed634dffb..7242199a5 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -48,17 +48,19 @@ xattr # * rust/rustc RUSTDOCFLAGS RUSTFLAGS +clippy +rustc +rustfmt +rustup +# bitor # BitOr trait function bitxor # BitXor trait function -clippy concat fract powi println repr rfind -rustc -rustfmt struct structs substr From b11e9a057e3c08aa3edeffea4dd6d6e5e2064d8a Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 12 Jun 2021 19:52:34 -0500 Subject: [PATCH 255/320] docs/spell ~ (uucore) add spelling exceptions --- src/uucore/src/lib/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index f765b7b3e..bf2e5b1bb 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -186,7 +186,7 @@ mod tests { fn make_os_vec(os_str: &OsStr) -> Vec { vec![ OsString::from("test"), - OsString::from("สวัสดี"), + OsString::from("สวัสดี"), // spell-checker:disable-line os_str.to_os_string(), ] } From 2cb97c81ed9b214f718c3c6ec64210064be296f0 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 6 Jun 2021 20:24:40 +0200 Subject: [PATCH 256/320] maint/CICD ~ add GHA 'FixPR' to auto-fix issues for merging PRs - auto-fix formatting - auto-fix incompatible/out-of-date 'Cargo.lock' --- .github/workflows/FixPR.yml | 133 ++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 .github/workflows/FixPR.yml diff --git a/.github/workflows/FixPR.yml b/.github/workflows/FixPR.yml new file mode 100644 index 000000000..17470df26 --- /dev/null +++ b/.github/workflows/FixPR.yml @@ -0,0 +1,133 @@ +name: FixPR + +# Trigger automated fixes for PRs being merged (with associated commits) + +env: + BRANCH_TARGET: master + +on: + # * only trigger on pull request closed to specific branches + # ref: https://github.community/t/trigger-workflow-only-on-pull-request-merge/17359/9 + pull_request: + branches: + - master # == env.BRANCH_TARGET ## unfortunately, env context variables are only available in jobs/steps (see ) + types: [ closed ] + +jobs: + code_deps: + # Refresh dependencies (ie, 'Cargo.lock') and show updated dependency tree + if: github.event.pull_request.merged == true ## only for PR merges + name: Update/dependencies + runs-on: ${{ matrix.job.os }} + strategy: + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + steps: + - uses: actions/checkout@v2 + - name: Initialize job variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # surface MSRV from CICD workflow + RUST_MIN_SRV=$(grep -P "^\s+RUST_MIN_SRV:" .github/workflows/CICD.yml | grep -Po "(?<=\x22)\d+[.]\d+(?:[.]\d+)?(?=\x22)" ) + outputs RUST_MIN_SRV + - name: Install `rust` toolchain (v${{ steps.vars.outputs.RUST_MIN_SRV }}) + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ steps.vars.outputs.RUST_MIN_SRV }} + default: true + profile: minimal # minimal component installation (ie, no documentation) + - name: Install `cargo-tree` # for dependency information + uses: actions-rs/install@v0.1 + with: + crate: cargo-tree + version: latest + use-tool-cache: true + env: + RUSTUP_TOOLCHAIN: stable + - name: Ensure updated 'Cargo.lock' + shell: bash + run: | + # Ensure updated 'Cargo.lock' + # * 'Cargo.lock' is required to be in a format that `cargo` of MinSRV can interpret (eg, v1-format for MinSRV < v1.38) + cargo fetch --locked --quiet || cargo +${{ steps.vars.outputs.RUST_MIN_SRV }} update + - name: Info + shell: bash + run: | + # Info + ## environment + echo "## environment" + echo "CI='${CI}'" + ## tooling info display + echo "## tooling" + which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true + rustup -V + rustup show active-toolchain + cargo -V + rustc -V + cargo-tree tree -V + ## dependencies + echo "## dependency list" + cargo fetch --locked --quiet + ## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors + RUSTUP_TOOLCHAIN=stable cargo-tree tree --frozen --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique + - name: Commit any changes (to '${{ env.BRANCH_TARGET }}') + uses: EndBug/add-and-commit@v7 + with: + branch: ${{ env.BRANCH_TARGET }} + default_author: github_actions + message: "maint ~ refresh 'Cargo.lock'" + add: Cargo.lock + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + code_format: + # Recheck/refresh code formatting + if: github.event.pull_request.merged == true ## only for PR merges + name: Update/format + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + steps: + - uses: actions/checkout@v2 + - name: Initialize job variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # target-specific options + # * CARGO_FEATURES_OPTION + CARGO_FEATURES_OPTION='' ; + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi + outputs CARGO_FEATURES_OPTION + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + profile: minimal # minimal component installation (ie, no documentation) + components: rustfmt + - name: "`cargo fmt`" + shell: bash + run: | + cargo fmt + - name: "`cargo fmt` tests" + shell: bash + run: | + # `cargo fmt` of tests + find tests -name "*.rs" -print0 | xargs -0 cargo fmt -- + - name: Commit any changes (to '${{ env.BRANCH_TARGET }}') + uses: EndBug/add-and-commit@v7 + with: + branch: ${{ env.BRANCH_TARGET }} + default_author: github_actions + message: "maint ~ rustfmt (`cargo fmt`)" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b4a06cfdbad6db079f4a836996d06aef0c068986 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 12 Jun 2021 23:27:03 -0500 Subject: [PATCH 257/320] maint/CICD ~ refactor; improve logging for `outputs` shell script --- .github/workflows/CICD.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 9d2edeac1..a8046269a 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -32,7 +32,7 @@ jobs: shell: bash run: | ## VARs setup - outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='' ; @@ -66,7 +66,7 @@ jobs: shell: bash run: | ## VARs setup - outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='' ; @@ -110,7 +110,7 @@ jobs: shell: bash run: | ## VARs setup - outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='' ; @@ -276,7 +276,7 @@ jobs: shell: bash run: | ## VARs setup - outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # toolchain TOOLCHAIN="stable" ## default to "stable" toolchain # * specify alternate/non-default TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: GH:rust-lang/rust#47048, GH:rust-lang/rust#53454, GH:rust-lang/cargo#6754) @@ -382,7 +382,7 @@ jobs: shell: bash run: | ## Dependent VARs setup - outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + outputs() { step_id="dep_vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # * determine sub-crate utility list UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" echo UTILITY_LIST=${UTILITY_LIST} @@ -544,7 +544,7 @@ jobs: shell: bash run: | ## VARs setup - outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # toolchain TOOLCHAIN="nightly-${{ env.RUST_COV_SRV }}" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support # * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files @@ -579,7 +579,7 @@ jobs: shell: bash run: | ## Dependent VARs setup - outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + outputs() { step_id="dep_vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # * determine sub-crate utility list UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" From c74bc2eedd10b1575836056e72a322184ec6f7d9 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 13 Jun 2021 00:36:05 -0500 Subject: [PATCH 258/320] maint/CICD ~ add 'GHA-delete-GNU-workflow-logs' shell script utility --- util/GHA-delete-GNU-workflow-logs.sh | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 util/GHA-delete-GNU-workflow-logs.sh diff --git a/util/GHA-delete-GNU-workflow-logs.sh b/util/GHA-delete-GNU-workflow-logs.sh new file mode 100644 index 000000000..19e3311d4 --- /dev/null +++ b/util/GHA-delete-GNU-workflow-logs.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +# spell-checker:ignore (utils) gitsome jq ; (gh) repos + +ME="${0}" +ME_dir="$(dirname -- "${ME}")" +ME_parent_dir="$(dirname -- "${ME_dir}")" +ME_parent_dir_abs="$(realpath -mP -- "${ME_parent_dir}")" + +# ref: + +# note: requires `gh` and `jq` + +## tools available? + +# * `gh` available? +unset GH +gh --version 1>/dev/null 2>&1 +if [ $? -eq 0 ]; then export GH="gh"; fi + +# * `jq` available? +unset JQ +jq --version 1>/dev/null 2>&1 +if [ $? -eq 0 ]; then export JQ="jq"; fi + +if [ -z "${GH}" ] || [ -z "${JQ}" ]; then + if [ -z "${GH}" ]; then + echo 'ERR!: missing `gh` (see install instructions at )' 1>&2 + fi + if [ -z "${JQ}" ]; then + echo 'ERR!: missing `jq` (install with `sudo apt install jq`)' 1>&2 + fi + exit 1 +fi + +dry_run=true + +USER_NAME=uutils +REPO_NAME=coreutils +WORK_NAME=GNU + +# * `--paginate` retrieves all pages +# gh api --paginate "repos/${USER_NAME}/${REPO_NAME}/actions/runs" | jq -r ".workflow_runs[] | select(.name == \"${WORK_NAME}\") | (.id)" | xargs -n1 sh -c "for arg do { echo gh api repos/${USER_NAME}/${REPO_NAME}/actions/runs/\${arg} -X DELETE ; if [ -z "$dry_run" ]; then gh api repos/${USER_NAME}/${REPO_NAME}/actions/runs/\${arg} -X DELETE ; fi ; } ; done ;" _ +gh api "repos/${USER_NAME}/${REPO_NAME}/actions/runs" | jq -r ".workflow_runs[] | select(.name == \"${WORK_NAME}\") | (.id)" | xargs -n1 sh -c "for arg do { echo gh api repos/${USER_NAME}/${REPO_NAME}/actions/runs/\${arg} -X DELETE ; if [ -z "$dry_run" ]; then gh api repos/${USER_NAME}/${REPO_NAME}/actions/runs/\${arg} -X DELETE ; fi ; } ; done ;" _ From f5edc500e03dc3fc37339e1b5441a4224d460b34 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sat, 19 Jun 2021 10:53:06 -0500 Subject: [PATCH 259/320] tests ~ fix spelling errors --- tests/by-util/test_cut.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index e21010ec8..92bab4d75 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -162,7 +162,7 @@ fn test_directory_and_no_such_file() { fn test_equal_as_delimiter() { new_ucmd!() .args(&["-f", "2", "-d="]) - .pipe_in("--libdir=./out/lib") + .pipe_in("--dir=./out/lib") .succeeds() .stdout_only("./out/lib\n"); } From f6cb1324b630b8b0208efadc789d117557a81189 Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Sun, 20 Jun 2021 13:19:50 +0530 Subject: [PATCH 260/320] ls: Fix problems dealing with dangling symlinks - For dangling symlinks, errors should only be reported if dereferencing options were passed and dereferencing was applicable to the particular symlink - With -i parameter, report '?' as the inode number for dangling symlinks --- src/uu/ls/src/ls.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 0bffa2e52..677556ab0 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1196,7 +1196,9 @@ fn list(locs: Vec, config: Config) -> i32 { for loc in &locs { let p = PathBuf::from(&loc); - if !p.exists() { + let path_data = PathData::new(p, None, None, &config, true); + + if !path_data.md().is_some() { show_error!("'{}': {}", &loc, "No such file or directory"); /* We found an error, the return code of ls should not be 0 @@ -1206,8 +1208,6 @@ fn list(locs: Vec, config: Config) -> i32 { continue; } - let path_data = PathData::new(p, None, None, &config, true); - let show_dir_contents = match path_data.file_type() { Some(ft) => !config.directory && ft.is_dir(), None => { @@ -1331,7 +1331,7 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result { if dereference { - entry.metadata().or_else(|_| entry.symlink_metadata()) + entry.metadata() } else { entry.symlink_metadata() } @@ -1733,7 +1733,11 @@ fn display_file_name(path: &PathData, config: &Config) -> Option { #[cfg(unix)] { if config.format != Format::Long && config.inode { - name = get_inode(path.md()?) + " " + &name; + name = path + .md() + .map_or_else(|| "?".to_string(), |md| get_inode(md)) + + " " + + &name; } } From d0039df8c3de451a07a52dc96f1d6b4d71181817 Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Sun, 20 Jun 2021 13:50:38 +0530 Subject: [PATCH 261/320] tests: Add test for dangling symlinks with ls Add test similar to gnu dangling symlinks test --- tests/by-util/test_ls.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index f8aa4453b..741a304e3 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -2021,3 +2021,28 @@ fn test_ls_path() { .run() .stdout_is(expected_stdout); } + +#[test] +fn test_ls_dangling_symlinks() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("temp_dir"); + at.symlink_file("does_not_exist", "temp_dir/dangle"); + + scene.ucmd().arg("-L").arg("temp_dir/dangle").fails(); + scene.ucmd().arg("-H").arg("temp_dir/dangle").fails(); + + scene + .ucmd() + .arg("temp_dir/dangle") + .succeeds() + .stdout_contains("dangle"); + + scene + .ucmd() + .arg("-Li") + .arg("temp_dir") + .succeeds() // this should fail, though at the moment, ls lacks a way to propagate errors encountered during display + .stdout_contains("? dangle"); +} From 5ac0274c133299c16cb48d76cd5ef12d3946b72e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 20 Jun 2021 11:50:14 +0200 Subject: [PATCH 262/320] numfmt: fix doctest and spell check --- src/uu/numfmt/src/format.rs | 31 +++++++++++++++++++------------ src/uu/numfmt/src/numfmt.rs | 4 +++- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index f0f1bf739..e44446818 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -131,25 +131,32 @@ fn transform_from(s: &str, opts: &Unit) -> Result { remove_suffix(i, suffix, opts).map(|n| if n < 0.0 { -n.abs().ceil() } else { n.ceil() }) } -/// Divide numerator by denominator, with ceiling. +/// Divide numerator by denominator, with rounding. /// -/// If the result of the division is less than 10.0, truncate the result -/// to the next highest tenth. +/// If the result of the division is less than 10.0, round to one decimal point. /// -/// Otherwise, truncate the result to the next highest whole number. +/// Otherwise, round to an integer. /// /// # Examples: /// /// ``` -/// use uu_numfmt::format::div_ceil; +/// use uu_numfmt::format::div_round; +/// use uu_numfmt::options::RoundMethod; /// -/// assert_eq!(div_ceil(1.01, 1.0), 1.1); -/// assert_eq!(div_ceil(999.1, 1000.), 1.0); -/// assert_eq!(div_ceil(1001., 10.), 101.); -/// assert_eq!(div_ceil(9991., 10.), 1000.); -/// assert_eq!(div_ceil(-12.34, 1.0), -13.0); -/// assert_eq!(div_ceil(1000.0, -3.14), -319.0); -/// assert_eq!(div_ceil(-271828.0, -271.0), 1004.0); +/// // Rounding methods: +/// assert_eq!(div_round(1.01, 1.0, RoundMethod::FromZero), 1.1); +/// assert_eq!(div_round(1.01, 1.0, RoundMethod::TowardsZero), 1.0); +/// assert_eq!(div_round(1.01, 1.0, RoundMethod::Up), 1.1); +/// assert_eq!(div_round(1.01, 1.0, RoundMethod::Down), 1.0); +/// assert_eq!(div_round(1.01, 1.0, RoundMethod::Nearest), 1.0); +/// +/// // Division: +/// assert_eq!(div_round(999.1, 1000.0, RoundMethod::FromZero), 1.0); +/// assert_eq!(div_round(1001., 10., RoundMethod::FromZero), 101.); +/// assert_eq!(div_round(9991., 10., RoundMethod::FromZero), 1000.); +/// assert_eq!(div_round(-12.34, 1.0, RoundMethod::FromZero), -13.0); +/// assert_eq!(div_round(1000.0, -3.14, RoundMethod::FromZero), -319.0); +/// assert_eq!(div_round(-271828.0, -271.0, RoundMethod::FromZero), 1004.0); /// ``` pub fn div_round(n: f64, d: f64, method: RoundMethod) -> f64 { let v = n / d; diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 0a17882e8..b534a9789 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -5,6 +5,8 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +// spell-checker:ignore N'th M'th + #[macro_use] extern crate uucore; @@ -16,7 +18,7 @@ use std::io::{BufRead, Write}; use uucore::ranges::Range; pub mod format; -mod options; +pub mod options; mod units; static ABOUT: &str = "Convert numbers from/to human-readable strings"; From 3b641afadcf57facf160b57c5e90bfeeeb68bed7 Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Sun, 20 Jun 2021 16:56:25 +0530 Subject: [PATCH 263/320] ls: Fix issue with Windows and dangling symbolic links - Windows hidden file attribute determination would assume symbolic link to be valid and would panic - Check symbolic link's attributes if the link points to non-existing file --- src/uu/ls/src/ls.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 677556ab0..220eccb30 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1270,7 +1270,8 @@ fn sort_entries(entries: &mut Vec, config: &Config) { #[cfg(windows)] fn is_hidden(file_path: &DirEntry) -> bool { - let metadata = fs::metadata(file_path.path()).unwrap(); + let path = file_path.path(); + let metadata = fs::metadata(&path).unwrap_or_else(|_| fs::symlink_metadata(&path).unwrap()); let attr = metadata.file_attributes(); (attr & 0x2) > 0 } From ffb6b7152f8699b0a42fe811a576a6a3633f4baf Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Sun, 20 Jun 2021 16:58:28 +0530 Subject: [PATCH 264/320] tests: Fix ls dangling symbolic links test output for windows On windows we do not print inode numbers at all, so skip checking for ? for dangling symbolic links in expected output --- tests/by-util/test_ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 741a304e3..67112b4f5 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -2044,5 +2044,5 @@ fn test_ls_dangling_symlinks() { .arg("-Li") .arg("temp_dir") .succeeds() // this should fail, though at the moment, ls lacks a way to propagate errors encountered during display - .stdout_contains("? dangle"); + .stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" }); } From a91369bbff9f6587bfd23d76f043be25bfdbd294 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 20 Jun 2021 19:10:51 +0200 Subject: [PATCH 265/320] cp: fix dead code warnings on windows --- src/uu/cp/src/cp.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 7cf6a1d9b..9186ec259 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1242,6 +1242,7 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> return Err("--reflink is only supported on linux and macOS" .to_string() .into()); + #[cfg(any(target_os = "linux", target_os = "macos"))] if is_symlink { assert!(options.dereference); let real_path = std::fs::read_link(source)?; From 6aa79440f5c9ff2015a2b2d3c184b2c82699a4c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lauzier?= Date: Sun, 20 Jun 2021 21:21:50 -0400 Subject: [PATCH 266/320] Fix a clippy warning --- src/uu/timeout/src/timeout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index bc92157ca..f21a0265f 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -89,8 +89,8 @@ impl Config { signal, duration, preserve_status, - command, verbose, + command, } } } From 30e45eefa4a5e627a11d7a09f23cec7d5ec68137 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 21 Jun 2021 13:19:57 +0200 Subject: [PATCH 267/320] groups: fix to pass GNU Testsuite `groups-dash.sh` --- src/uu/groups/src/groups.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 07c25cebb..746b7ff97 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -56,7 +56,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ); 0 } else { - crash!(1, "unknown user {}", user); + crash!(1, "'{}': no such user", user); } } } From 25ef39472c46b231944d56e6cf53a1e220eeb6d2 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 21 Jun 2021 14:33:09 +0200 Subject: [PATCH 268/320] groups: fix to pass GNU Testsuite `groups-process-all.sh` * add support for multiple users * sync help text with GNU's `groups` manpage --- src/uu/groups/Cargo.toml | 2 +- src/uu/groups/src/groups.rs | 84 +++++++++++++++++++++++++------------ 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index 4a5a537e5..e7ce52650 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -3,7 +3,7 @@ name = "uu_groups" version = "0.0.6" authors = ["uutils developers"] license = "MIT" -description = "groups ~ (uutils) display group memberships for USERNAME" +description = "groups ~ (uutils) print the groups a user is in" homepage = "https://github.com/uutils/coreutils" repository = "https://github.com/uutils/coreutils/tree/master/src/uu/groups" diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 746b7ff97..22e7b8918 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -5,6 +5,13 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// +// ============================================================================ +// Testsuite summary for GNU coreutils 8.32.162-4eda +// ============================================================================ +// PASS: tests/misc/groups-dash.sh +// PASS: tests/misc/groups-process-all.sh +// PASS: tests/misc/groups-version.sh // spell-checker:ignore (ToDO) passwd @@ -14,11 +21,15 @@ use uucore::entries::{get_groups_gnu, gid2grp, Locate, Passwd}; use clap::{crate_version, App, Arg}; -static ABOUT: &str = "display current group names"; -static OPT_USER: &str = "user"; +mod options { + pub const USERS: &str = "USERNAME"; +} +static ABOUT: &str = "Print group memberships for each USERNAME or, \ + if no USERNAME is specified, for\nthe current process \ + (which may differ if the groups data‐base has changed)."; fn get_usage() -> String { - format!("{0} [USERNAME]", executable!()) + format!("{0} [OPTION]... [USERNAME]...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -28,36 +39,57 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .version(crate_version!()) .about(ABOUT) .usage(&usage[..]) - .arg(Arg::with_name(OPT_USER)) + .arg( + Arg::with_name(options::USERS) + .multiple(true) + .takes_value(true) + .value_name(options::USERS), + ) .get_matches_from(args); - match matches.value_of(OPT_USER) { - None => { + let users: Vec = matches + .values_of(options::USERS) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + let mut exit_code = 1; + + if users.is_empty() { + println!( + "{}", + get_groups_gnu(None) + .unwrap() + .iter() + .map(|&gid| gid2grp(gid).unwrap_or_else(|_| { + show_error!("cannot find name for group ID {}", gid); + exit_code = 1; + gid.to_string() + })) + .collect::>() + .join(" ") + ); + return exit_code; + } + + for user in users { + if let Ok(p) = Passwd::locate(user.as_str()) { println!( - "{}", - get_groups_gnu(None) - .unwrap() + "{} : {}", + user, + p.belongs_to() .iter() - .map(|&g| gid2grp(g).unwrap()) + .map(|&gid| gid2grp(gid).unwrap_or_else(|_| { + show_error!("cannot find name for group ID {}", gid); + exit_code = 1; + gid.to_string() + })) .collect::>() .join(" ") ); - 0 - } - Some(user) => { - if let Ok(p) = Passwd::locate(user) { - println!( - "{}", - p.belongs_to() - .iter() - .map(|&g| gid2grp(g).unwrap()) - .collect::>() - .join(" ") - ); - 0 - } else { - crash!(1, "'{}': no such user", user); - } + } else { + show_error!("'{}': no such user", user); + exit_code = 1; } } + exit_code } From 4b3224dd82d2f80bdca5598675f397c5577a568d Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Mon, 21 Jun 2021 20:29:22 +0530 Subject: [PATCH 269/320] ls: Fix clippy warning --- src/uu/ls/src/ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 220eccb30..1d050a376 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1198,7 +1198,7 @@ fn list(locs: Vec, config: Config) -> i32 { let p = PathBuf::from(&loc); let path_data = PathData::new(p, None, None, &config, true); - if !path_data.md().is_some() { + if path_data.md().is_none() { show_error!("'{}': {}", &loc, "No such file or directory"); /* We found an error, the return code of ls should not be 0 From ed8d390ca7f563e742f9ad812ad6203996fa76f2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 22 Jun 2021 14:25:32 +0200 Subject: [PATCH 270/320] CI/GNU: if an error is detected, don't generate the json file Avoid to generate incorrect json files --- .github/workflows/GnuTests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 90af6a689..9c90b0a9c 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -62,6 +62,10 @@ jobs: FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) + if [[ "$TOTAL" --eq 0 || "$TOTAL" --eq 1 ]]; then + echo "Error in the execution, failing early" + exit 1 + fi output="GNU tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR" echo "${output}" if [[ "$FAIL" -gt 0 || "$ERROR" -gt 0 ]]; then echo "::warning ::${output}" ; fi From e5a7bcbb9d6a29d0f9c3b8911ed4d4764cf51882 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 21 Jun 2021 21:13:40 +0200 Subject: [PATCH 271/320] tests: keep env vars for the temporary directory On some Windows machines this would otherwise cause `std::env::temp_dir` to fall back to a path that is not writeable (C:\\Windows). Since by default integration tests don't inherit env vars from the parent, we have to override this in some cases. --- tests/by-util/test_mktemp.rs | 4 ++-- tests/by-util/test_sort.rs | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index d601bad5b..413b35bc5 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -386,7 +386,7 @@ fn test_mktemp_tmpdir_one_arg() { let scene = TestScenario::new(util_name!()); let result = scene - .ucmd() + .ucmd_keepenv() .arg("--tmpdir") .arg("apt-key-gpghome.XXXXXXXXXX") .succeeds(); @@ -399,7 +399,7 @@ fn test_mktemp_directory_tmpdir() { let scene = TestScenario::new(util_name!()); let result = scene - .ucmd() + .ucmd_keepenv() .arg("--directory") .arg("--tmpdir") .arg("apt-key-gpghome.XXXXXXXXXX") diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 0f9a9d3f1..01fafae00 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -28,7 +28,8 @@ fn test_helper(file_name: &str, possible_args: &[&str]) { fn test_buffer_sizes() { let buffer_sizes = ["0", "50K", "50k", "1M", "100M"]; for buffer_size in &buffer_sizes { - new_ucmd!() + TestScenario::new(util_name!()) + .ucmd_keepenv() .arg("-n") .arg("-S") .arg(buffer_size) @@ -40,7 +41,8 @@ fn test_buffer_sizes() { { let buffer_sizes = ["1000G", "10T"]; for buffer_size in &buffer_sizes { - new_ucmd!() + TestScenario::new(util_name!()) + .ucmd_keepenv() .arg("-n") .arg("-S") .arg(buffer_size) @@ -877,7 +879,8 @@ fn test_compress() { #[test] fn test_compress_fail() { - new_ucmd!() + TestScenario::new(util_name!()) + .ucmd_keepenv() .args(&[ "ext_sort.txt", "-n", @@ -892,7 +895,8 @@ fn test_compress_fail() { #[test] fn test_merge_batches() { - new_ucmd!() + TestScenario::new(util_name!()) + .ucmd_keepenv() .args(&["ext_sort.txt", "-n", "-S", "150b"]) .succeeds() .stdout_only_fixture("ext_sort.expected"); @@ -900,7 +904,8 @@ fn test_merge_batches() { #[test] fn test_merge_batch_size() { - new_ucmd!() + TestScenario::new(util_name!()) + .ucmd_keepenv() .arg("--batch-size=2") .arg("-m") .arg("--unique") From 622504467f369198ce5839560617130199b6b917 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 22 Jun 2021 17:36:56 +0200 Subject: [PATCH 272/320] mktemp: note that windows uses a different env var for tmpdir On windows `std::env::temp_dir` uses the `TMP` environment variable instead of `TMPDIR`. --- src/uu/mktemp/src/mktemp.rs | 4 ++-- tests/by-util/test_mktemp.rs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index e04de8702..bd77e9d51 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -77,14 +77,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(OPT_TMPDIR) .help( "interpret TEMPLATE relative to DIR; if DIR is not specified, use \ - $TMPDIR if set, else /tmp. With this option, TEMPLATE must not \ + $TMPDIR ($TMP on windows) if set, else /tmp. With this option, TEMPLATE must not \ be an absolute name; unlike with -t, TEMPLATE may contain \ slashes, but mktemp creates only the final component", ) .value_name("DIR"), ) .arg(Arg::with_name(OPT_T).short(OPT_T).help( - "Generate a template (using the supplied prefix and TMPDIR if set) \ + "Generate a template (using the supplied prefix and TMPDIR (TMP on windows) if set) \ to create a filename template [deprecated]", )) .arg( diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index 413b35bc5..bcf75ee20 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -17,7 +17,10 @@ static TEST_TEMPLATE8: &str = "tempXXXl/ate"; #[cfg(windows)] static TEST_TEMPLATE8: &str = "tempXXXl\\ate"; +#[cfg(not(windows))] const TMPDIR: &str = "TMPDIR"; +#[cfg(windows)] +const TMPDIR: &str = "TMP"; #[test] fn test_mktemp_mktemp() { From 34db1c591654ad08c29c12028f78c404fb1aa7a7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 22 Jun 2021 18:03:12 +0200 Subject: [PATCH 273/320] Simple dash, not double --- .github/workflows/GnuTests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 9c90b0a9c..8bf6c091b 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -62,7 +62,7 @@ jobs: FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - if [[ "$TOTAL" --eq 0 || "$TOTAL" --eq 1 ]]; then + if [[ "$TOTAL" -eq 0 || "$TOTAL" -eq 1 ]]; then echo "Error in the execution, failing early" exit 1 fi From 4a956f38b9ef8d005c0f926c3c4bbfd6dd5661f7 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 20 Jun 2021 23:56:14 +0200 Subject: [PATCH 274/320] sort: separate additional data from the Line struct Data that was previously boxed inside the `Line` struct was moved to separate vectors. Inside of each `Line` remains only an index that allows to access that data. This helps with keeping the `Line` struct small and therefore reduces memory usage in most cases. Additionally, this improves performance because one big allocation (the vectors) are faster than many small ones (many boxes inside of each `Line`). Those vectors can be reused as well, reducing the amount of (de-)allocations. --- src/uu/sort/src/check.rs | 32 ++-- src/uu/sort/src/chunks.rs | 130 ++++++++++++---- src/uu/sort/src/ext_sort.rs | 76 ++++++---- src/uu/sort/src/merge.rs | 35 ++--- src/uu/sort/src/sort.rs | 290 ++++++++++++++++++++---------------- 5 files changed, 347 insertions(+), 216 deletions(-) diff --git a/src/uu/sort/src/check.rs b/src/uu/sort/src/check.rs index f53e4edb4..f1cd22686 100644 --- a/src/uu/sort/src/check.rs +++ b/src/uu/sort/src/check.rs @@ -8,7 +8,7 @@ //! Check if a file is ordered use crate::{ - chunks::{self, Chunk}, + chunks::{self, Chunk, RecycledChunk}, compare_by, open, GlobalSettings, }; use itertools::Itertools; @@ -34,7 +34,7 @@ pub fn check(path: &str, settings: &GlobalSettings) -> i32 { move || reader(file, recycled_receiver, loaded_sender, &settings) }); for _ in 0..2 { - let _ = recycled_sender.send(Chunk::new(vec![0; 100 * 1024], |_| Vec::new())); + let _ = recycled_sender.send(RecycledChunk::new(100 * 1024)); } let mut prev_chunk: Option = None; @@ -44,21 +44,29 @@ pub fn check(path: &str, settings: &GlobalSettings) -> i32 { if let Some(prev_chunk) = prev_chunk.take() { // Check if the first element of the new chunk is greater than the last // element from the previous chunk - let prev_last = prev_chunk.borrow_lines().last().unwrap(); - let new_first = chunk.borrow_lines().first().unwrap(); + let prev_last = prev_chunk.lines().last().unwrap(); + let new_first = chunk.lines().first().unwrap(); - if compare_by(prev_last, new_first, settings) == Ordering::Greater { + if compare_by( + prev_last, + new_first, + settings, + prev_chunk.line_data(), + chunk.line_data(), + ) == Ordering::Greater + { if !settings.check_silent { println!("sort: {}:{}: disorder: {}", path, line_idx, new_first.line); } return 1; } - let _ = recycled_sender.send(prev_chunk); + let _ = recycled_sender.send(prev_chunk.recycle()); } - for (a, b) in chunk.borrow_lines().iter().tuple_windows() { + for (a, b) in chunk.lines().iter().tuple_windows() { line_idx += 1; - if compare_by(a, b, settings) == Ordering::Greater { + if compare_by(a, b, settings, chunk.line_data(), chunk.line_data()) == Ordering::Greater + { if !settings.check_silent { println!("sort: {}:{}: disorder: {}", path, line_idx, b.line); } @@ -74,16 +82,15 @@ pub fn check(path: &str, settings: &GlobalSettings) -> i32 { /// The function running on the reader thread. fn reader( mut file: Box, - receiver: Receiver, + receiver: Receiver, sender: SyncSender, settings: &GlobalSettings, ) { let mut carry_over = vec![]; - for chunk in receiver.iter() { - let (recycled_lines, recycled_buffer) = chunk.recycle(); + for recycled_chunk in receiver.iter() { let should_continue = chunks::read( &sender, - recycled_buffer, + recycled_chunk, None, &mut carry_over, &mut file, @@ -93,7 +100,6 @@ fn reader( } else { b'\n' }, - recycled_lines, settings, ); if !should_continue { diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index d452401df..9e9d212c2 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -15,7 +15,7 @@ use std::{ use memchr::memchr_iter; use ouroboros::self_referencing; -use crate::{GlobalSettings, Line}; +use crate::{numeric_str_cmp::NumInfo, GeneralF64ParseResult, GlobalSettings, Line}; /// The chunk that is passed around between threads. /// `lines` consist of slices into `buffer`. @@ -25,28 +25,87 @@ pub struct Chunk { pub buffer: Vec, #[borrows(buffer)] #[covariant] - pub lines: Vec>, + pub contents: ChunkContents<'this>, +} + +#[derive(Debug)] +pub struct ChunkContents<'a> { + pub lines: Vec>, + pub line_data: LineData<'a>, +} + +#[derive(Debug)] +pub struct LineData<'a> { + pub selections: Vec<&'a str>, + pub num_infos: Vec, + pub parsed_floats: Vec, } impl Chunk { /// Destroy this chunk and return its components to be reused. - /// - /// # Returns - /// - /// * The `lines` vector, emptied - /// * The `buffer` vector, **not** emptied - pub fn recycle(mut self) -> (Vec>, Vec) { - let recycled_lines = self.with_lines_mut(|lines| { - lines.clear(); - unsafe { + pub fn recycle(mut self) -> RecycledChunk { + let recycled_contents = self.with_contents_mut(|contents| { + contents.lines.clear(); + contents.line_data.selections.clear(); + contents.line_data.num_infos.clear(); + contents.line_data.parsed_floats.clear(); + let lines = unsafe { // SAFETY: It is safe to (temporarily) transmute to a vector of lines with a longer lifetime, // because the vector is empty. // Transmuting is necessary to make recycling possible. See https://github.com/rust-lang/rfcs/pull/2802 // for a rfc to make this unnecessary. Its example is similar to the code here. - std::mem::transmute::>, Vec>>(std::mem::take(lines)) - } + std::mem::transmute::>, Vec>>(std::mem::take( + &mut contents.lines, + )) + }; + let selections = unsafe { + // SAFETY: (same as above) It is safe to (temporarily) transmute to a vector of &str with a longer lifetime, + // because the vector is empty. + std::mem::transmute::, Vec<&'static str>>(std::mem::take( + &mut contents.line_data.selections, + )) + }; + ( + lines, + selections, + std::mem::take(&mut contents.line_data.num_infos), + std::mem::take(&mut contents.line_data.parsed_floats), + ) }); - (recycled_lines, self.into_heads().buffer) + RecycledChunk { + lines: recycled_contents.0, + selections: recycled_contents.1, + num_infos: recycled_contents.2, + parsed_floats: recycled_contents.3, + buffer: self.into_heads().buffer, + } + } + + pub fn lines(&self) -> &Vec { + &self.borrow_contents().lines + } + pub fn line_data(&self) -> &LineData { + &self.borrow_contents().line_data + } +} + +pub struct RecycledChunk { + lines: Vec>, + selections: Vec<&'static str>, + num_infos: Vec, + parsed_floats: Vec, + buffer: Vec, +} + +impl RecycledChunk { + pub fn new(capacity: usize) -> Self { + RecycledChunk { + lines: Vec::new(), + selections: Vec::new(), + num_infos: Vec::new(), + parsed_floats: Vec::new(), + buffer: vec![0; capacity], + } } } @@ -63,28 +122,32 @@ impl Chunk { /// (see also `read_to_chunk` for a more detailed documentation) /// /// * `sender`: The sender to send the lines to the sorter. -/// * `buffer`: The recycled buffer. All contents will be overwritten, but it must already be filled. +/// * `recycled_chunk`: The recycled chunk, as returned by `Chunk::recycle`. /// (i.e. `buffer.len()` should be equal to `buffer.capacity()`) /// * `max_buffer_size`: How big `buffer` can be. /// * `carry_over`: The bytes that must be carried over in between invocations. /// * `file`: The current file. /// * `next_files`: What `file` should be updated to next. /// * `separator`: The line separator. -/// * `lines`: The recycled vector to fill with lines. Must be empty. /// * `settings`: The global settings. #[allow(clippy::too_many_arguments)] pub fn read( sender: &SyncSender, - mut buffer: Vec, + recycled_chunk: RecycledChunk, max_buffer_size: Option, carry_over: &mut Vec, file: &mut T, next_files: &mut impl Iterator, separator: u8, - lines: Vec>, settings: &GlobalSettings, ) -> bool { - assert!(lines.is_empty()); + let RecycledChunk { + lines, + selections, + num_infos, + parsed_floats, + mut buffer, + } = recycled_chunk; if buffer.len() < carry_over.len() { buffer.resize(carry_over.len() + 10 * 1024, 0); } @@ -101,15 +164,25 @@ pub fn read( carry_over.extend_from_slice(&buffer[read..]); if read != 0 { - let payload = Chunk::new(buffer, |buf| { + let payload = Chunk::new(buffer, |buffer| { + let selections = unsafe { + // SAFETY: It is safe to transmute to an empty vector of selections with shorter lifetime. + // It was only temporarily transmuted to a Vec> to make recycling possible. + std::mem::transmute::, Vec<&'_ str>>(selections) + }; let mut lines = unsafe { - // SAFETY: It is safe to transmute to a vector of lines with shorter lifetime, + // SAFETY: (same as above) It is safe to transmute to a vector of lines with shorter lifetime, // because it was only temporarily transmuted to a Vec> to make recycling possible. std::mem::transmute::>, Vec>>(lines) }; - let read = crash_if_err!(1, std::str::from_utf8(&buf[..read])); - parse_lines(read, &mut lines, separator, settings); - lines + let read = crash_if_err!(1, std::str::from_utf8(&buffer[..read])); + let mut line_data = LineData { + selections, + num_infos, + parsed_floats, + }; + parse_lines(read, &mut lines, &mut line_data, separator, settings); + ChunkContents { lines, line_data } }); sender.send(payload).unwrap(); } @@ -120,6 +193,7 @@ pub fn read( fn parse_lines<'a>( mut read: &'a str, lines: &mut Vec>, + line_data: &mut LineData<'a>, separator: u8, settings: &GlobalSettings, ) { @@ -128,9 +202,15 @@ fn parse_lines<'a>( read = &read[..read.len() - 1]; } + assert!(lines.is_empty()); + assert!(line_data.selections.is_empty()); + assert!(line_data.num_infos.is_empty()); + assert!(line_data.parsed_floats.is_empty()); + let mut token_buffer = vec![]; lines.extend( read.split(separator as char) - .map(|line| Line::create(line, settings)), + .enumerate() + .map(|(index, line)| Line::create(line, index, line_data, &mut token_buffer, settings)), ); } diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 44ff6014a..e0814b7a2 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -23,15 +23,16 @@ use std::{ use itertools::Itertools; +use crate::chunks::RecycledChunk; use crate::merge::ClosedTmpFile; use crate::merge::WriteableCompressedTmpFile; use crate::merge::WriteablePlainTmpFile; use crate::merge::WriteableTmpFile; -use crate::Line; use crate::{ chunks::{self, Chunk}, - compare_by, merge, output_sorted_lines, sort_by, GlobalSettings, + compare_by, merge, sort_by, GlobalSettings, }; +use crate::{print_sorted, Line}; use tempfile::TempDir; const START_BUFFER_SIZE: usize = 8_000; @@ -98,16 +99,39 @@ fn reader_writer>, Tmp: WriteableTmpFile merger.write_all(settings); } ReadResult::SortedSingleChunk(chunk) => { - output_sorted_lines(chunk.borrow_lines().iter(), settings); + if settings.unique { + print_sorted( + chunk.lines().iter().dedup_by(|a, b| { + compare_by(a, b, settings, chunk.line_data(), chunk.line_data()) + == Ordering::Equal + }), + settings, + ); + } else { + print_sorted(chunk.lines().iter(), settings); + } } ReadResult::SortedTwoChunks([a, b]) => { - let merged_iter = a - .borrow_lines() - .iter() - .merge_by(b.borrow_lines().iter(), |line_a, line_b| { - compare_by(line_a, line_b, settings) != Ordering::Greater - }); - output_sorted_lines(merged_iter, settings); + let merged_iter = a.lines().iter().map(|line| (line, &a)).merge_by( + b.lines().iter().map(|line| (line, &b)), + |(line_a, a), (line_b, b)| { + compare_by(line_a, line_b, settings, a.line_data(), b.line_data()) + != Ordering::Greater + }, + ); + if settings.unique { + print_sorted( + merged_iter + .dedup_by(|(line_a, a), (line_b, b)| { + compare_by(line_a, line_b, settings, a.line_data(), b.line_data()) + == Ordering::Equal + }) + .map(|(line, _)| line), + settings, + ); + } else { + print_sorted(merged_iter.map(|(line, _)| line), settings); + } } ReadResult::EmptyInput => { // don't output anything @@ -118,7 +142,9 @@ fn reader_writer>, Tmp: WriteableTmpFile /// The function that is executed on the sorter thread. fn sorter(receiver: Receiver, sender: SyncSender, settings: GlobalSettings) { while let Ok(mut payload) = receiver.recv() { - payload.with_lines_mut(|lines| sort_by(lines, &settings)); + payload.with_contents_mut(|contents| { + sort_by(&mut contents.lines, &settings, &contents.line_data) + }); sender.send(payload).unwrap(); } } @@ -154,20 +180,16 @@ fn read_write_loop( for _ in 0..2 { let should_continue = chunks::read( &sender, - vec![ - 0; - if START_BUFFER_SIZE < buffer_size { - START_BUFFER_SIZE - } else { - buffer_size - } - ], + RecycledChunk::new(if START_BUFFER_SIZE < buffer_size { + START_BUFFER_SIZE + } else { + buffer_size + }), Some(buffer_size), &mut carry_over, &mut file, &mut files, separator, - Vec::new(), settings, ); @@ -216,18 +238,17 @@ fn read_write_loop( file_number += 1; - let (recycled_lines, recycled_buffer) = chunk.recycle(); + let recycled_chunk = chunk.recycle(); if let Some(sender) = &sender_option { let should_continue = chunks::read( sender, - recycled_buffer, + recycled_chunk, None, &mut carry_over, &mut file, &mut files, separator, - recycled_lines, settings, ); if !should_continue { @@ -245,12 +266,9 @@ fn write( compress_prog: Option<&str>, separator: u8, ) -> I::Closed { - chunk.with_lines_mut(|lines| { - // Write the lines to the file - let mut tmp_file = I::create(file, compress_prog); - write_lines(lines, tmp_file.as_write(), separator); - tmp_file.finished_writing() - }) + let mut tmp_file = I::create(file, compress_prog); + write_lines(chunk.lines(), tmp_file.as_write(), separator); + tmp_file.finished_writing() } fn write_lines<'a, T: Write>(lines: &[Line<'a>], writer: &mut T, separator: u8) { diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index 173faaffc..12d7a9b9b 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -24,7 +24,7 @@ use itertools::Itertools; use tempfile::TempDir; use crate::{ - chunks::{self, Chunk}, + chunks::{self, Chunk, RecycledChunk}, compare_by, GlobalSettings, }; @@ -125,14 +125,14 @@ fn merge_without_limit>( })); // Send the initial chunk to trigger a read for each file request_sender - .send((file_number, Chunk::new(vec![0; 8 * 1024], |_| Vec::new()))) + .send((file_number, RecycledChunk::new(8 * 1024))) .unwrap(); } // Send the second chunk for each file for file_number in 0..reader_files.len() { request_sender - .send((file_number, Chunk::new(vec![0; 8 * 1024], |_| Vec::new()))) + .send((file_number, RecycledChunk::new(8 * 1024))) .unwrap(); } @@ -181,13 +181,12 @@ struct ReaderFile { /// The function running on the reader thread. fn reader( - recycled_receiver: Receiver<(usize, Chunk)>, + recycled_receiver: Receiver<(usize, RecycledChunk)>, files: &mut [Option>], settings: &GlobalSettings, separator: u8, ) { - for (file_idx, chunk) in recycled_receiver.iter() { - let (recycled_lines, recycled_buffer) = chunk.recycle(); + for (file_idx, recycled_chunk) in recycled_receiver.iter() { if let Some(ReaderFile { file, sender, @@ -196,13 +195,12 @@ fn reader( { let should_continue = chunks::read( sender, - recycled_buffer, + recycled_chunk, None, carry_over, file.as_read(), &mut iter::empty(), separator, - recycled_lines, settings, ); if !should_continue { @@ -234,7 +232,7 @@ struct PreviousLine { /// Merges files together. This is **not** an iterator because of lifetime problems. pub struct FileMerger<'a> { heap: binary_heap_plus::BinaryHeap>, - request_sender: Sender<(usize, Chunk)>, + request_sender: Sender<(usize, RecycledChunk)>, prev: Option, } @@ -257,14 +255,16 @@ impl<'a> FileMerger<'a> { file_number: file.file_number, }); - file.current_chunk.with_lines(|lines| { - let current_line = &lines[file.line_idx]; + file.current_chunk.with_contents(|contents| { + let current_line = &contents.lines[file.line_idx]; if settings.unique { if let Some(prev) = &prev { let cmp = compare_by( - &prev.chunk.borrow_lines()[prev.line_idx], + &prev.chunk.lines()[prev.line_idx], current_line, settings, + prev.chunk.line_data(), + file.current_chunk.line_data(), ); if cmp == Ordering::Equal { return; @@ -274,8 +274,7 @@ impl<'a> FileMerger<'a> { current_line.print(out, settings); }); - let was_last_line_for_file = - file.current_chunk.borrow_lines().len() == file.line_idx + 1; + let was_last_line_for_file = file.current_chunk.lines().len() == file.line_idx + 1; if was_last_line_for_file { if let Ok(next_chunk) = file.receiver.recv() { @@ -295,7 +294,7 @@ impl<'a> FileMerger<'a> { // If nothing is referencing the previous chunk anymore, this means that the previous line // was the last line of the chunk. We can recycle the chunk. self.request_sender - .send((prev.file_number, prev_chunk)) + .send((prev.file_number, prev_chunk.recycle())) .ok(); } } @@ -312,9 +311,11 @@ struct FileComparator<'a> { impl<'a> Compare for FileComparator<'a> { fn compare(&self, a: &MergeableFile, b: &MergeableFile) -> Ordering { let mut cmp = compare_by( - &a.current_chunk.borrow_lines()[a.line_idx], - &b.current_chunk.borrow_lines()[b.line_idx], + &a.current_chunk.lines()[a.line_idx], + &b.current_chunk.lines()[b.line_idx], self.settings, + a.current_chunk.line_data(), + b.current_chunk.line_data(), ); if cmp == Ordering::Equal { // To make sorting stable, we need to consider the file number as well, diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 7f3d2872e..2512d65d1 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -23,11 +23,11 @@ mod ext_sort; mod merge; mod numeric_str_cmp; +use chunks::LineData; use clap::{crate_version, App, Arg}; use custom_str_cmp::custom_str_cmp; use ext_sort::ext_sort; use fnv::FnvHasher; -use itertools::Itertools; use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; @@ -170,6 +170,17 @@ pub struct GlobalSettings { tmp_dir: PathBuf, compress_prog: Option, merge_batch_size: usize, + precomputed: Precomputed, +} + +/// Data needed for sorting. Should be computed once before starting to sort +/// by calling `GlobalSettings::init_precomputed`. +#[derive(Clone, Debug)] +struct Precomputed { + needs_tokens: bool, + num_infos_per_line: usize, + floats_per_line: usize, + selections_per_line: usize, } impl GlobalSettings { @@ -210,6 +221,28 @@ impl GlobalSettings { None => BufWriter::new(Box::new(stdout()) as Box), } } + + /// Precompute some data needed for sorting. + /// This function **must** be called before starting to sort, and `GlobalSettings` may not be altered + /// afterwards. + fn init_precomputed(&mut self) { + self.precomputed.needs_tokens = self.selectors.iter().any(|s| s.needs_tokens); + self.precomputed.selections_per_line = self + .selectors + .iter() + .filter(|s| !s.is_default_selection) + .count(); + self.precomputed.num_infos_per_line = self + .selectors + .iter() + .filter(|s| matches!(s.settings.mode, SortMode::Numeric | SortMode::HumanNumeric)) + .count(); + self.precomputed.floats_per_line = self + .selectors + .iter() + .filter(|s| matches!(s.settings.mode, SortMode::GeneralNumeric)) + .count(); + } } impl Default for GlobalSettings { @@ -237,9 +270,16 @@ impl Default for GlobalSettings { tmp_dir: PathBuf::new(), compress_prog: None, merge_batch_size: 32, + precomputed: Precomputed { + num_infos_per_line: 0, + floats_per_line: 0, + selections_per_line: 0, + needs_tokens: false, + }, } } } + #[derive(Clone, PartialEq, Debug)] struct KeySettings { mode: SortMode, @@ -322,32 +362,10 @@ impl Default for KeySettings { Self::from(&GlobalSettings::default()) } } - -#[derive(Clone, Debug)] enum NumCache { AsF64(GeneralF64ParseResult), WithInfo(NumInfo), -} - -impl NumCache { - fn as_f64(&self) -> GeneralF64ParseResult { - match self { - NumCache::AsF64(n) => *n, - _ => unreachable!(), - } - } - fn as_num_info(&self) -> &NumInfo { - match self { - NumCache::WithInfo(n) => n, - _ => unreachable!(), - } - } -} - -#[derive(Clone, Debug)] -struct Selection<'a> { - slice: &'a str, - num_cache: Option>, + None, } type Field = Range; @@ -355,31 +373,39 @@ type Field = Range; #[derive(Clone, Debug)] pub struct Line<'a> { line: &'a str, - selections: Box<[Selection<'a>]>, + index: usize, } impl<'a> Line<'a> { - fn create(string: &'a str, settings: &GlobalSettings) -> Self { - let fields = if settings + /// Creates a new `Line`. + /// + /// If additional data is needed for sorting it is added to `line_data`. + /// `token_buffer` allows to reuse the allocation for tokens. + fn create( + line: &'a str, + index: usize, + line_data: &mut LineData<'a>, + token_buffer: &mut Vec, + settings: &GlobalSettings, + ) -> Self { + token_buffer.clear(); + if settings.precomputed.needs_tokens { + tokenize(line, settings.separator, token_buffer); + } + for (selection, num_cache) in settings .selectors .iter() - .any(|selector| selector.needs_tokens) + .filter(|selector| !selector.is_default_selection) + .map(|selector| selector.get_selection(line, token_buffer)) { - // Only tokenize if we will need tokens. - Some(tokenize(string, settings.separator)) - } else { - None - }; - - Line { - line: string, - selections: settings - .selectors - .iter() - .filter(|selector| !selector.is_default_selection) - .map(|selector| selector.get_selection(string, fields.as_deref())) - .collect(), + line_data.selections.push(selection); + match num_cache { + NumCache::AsF64(parsed_float) => line_data.parsed_floats.push(parsed_float), + NumCache::WithInfo(num_info) => line_data.num_infos.push(num_info), + NumCache::None => (), + } } + Self { line, index } } fn print(&self, writer: &mut impl Write, settings: &GlobalSettings) { @@ -408,7 +434,8 @@ impl<'a> Line<'a> { let line = self.line.replace('\t', ">"); writeln!(writer, "{}", line)?; - let fields = tokenize(self.line, settings.separator); + let mut fields = vec![]; + tokenize(self.line, settings.separator, &mut fields); for selector in settings.selectors.iter() { let mut selection = selector.get_range(self.line, Some(&fields)); match selector.settings.mode { @@ -539,51 +566,51 @@ impl<'a> Line<'a> { } } -/// Tokenize a line into fields. -fn tokenize(line: &str, separator: Option) -> Vec { +/// Tokenize a line into fields. The result is stored into `token_buffer`. +fn tokenize(line: &str, separator: Option, token_buffer: &mut Vec) { + assert!(token_buffer.is_empty()); if let Some(separator) = separator { - tokenize_with_separator(line, separator) + tokenize_with_separator(line, separator, token_buffer) } else { - tokenize_default(line) + tokenize_default(line, token_buffer) } } /// By default fields are separated by the first whitespace after non-whitespace. /// Whitespace is included in fields at the start. -fn tokenize_default(line: &str) -> Vec { - let mut tokens = vec![0..0]; +/// The result is stored into `token_buffer`. +fn tokenize_default(line: &str, token_buffer: &mut Vec) { + token_buffer.push(0..0); // pretend that there was whitespace in front of the line let mut previous_was_whitespace = true; for (idx, char) in line.char_indices() { if char.is_whitespace() { if !previous_was_whitespace { - tokens.last_mut().unwrap().end = idx; - tokens.push(idx..0); + token_buffer.last_mut().unwrap().end = idx; + token_buffer.push(idx..0); } previous_was_whitespace = true; } else { previous_was_whitespace = false; } } - tokens.last_mut().unwrap().end = line.len(); - tokens + token_buffer.last_mut().unwrap().end = line.len(); } /// Split between separators. These separators are not included in fields. -fn tokenize_with_separator(line: &str, separator: char) -> Vec { - let mut tokens = vec![]; +/// The result is stored into `token_buffer`. +fn tokenize_with_separator(line: &str, separator: char, token_buffer: &mut Vec) { let separator_indices = line.char_indices() .filter_map(|(i, c)| if c == separator { Some(i) } else { None }); let mut start = 0; for sep_idx in separator_indices { - tokens.push(start..sep_idx); + token_buffer.push(start..sep_idx); start = sep_idx + 1; } if start < line.len() { - tokens.push(start..line.len()); + token_buffer.push(start..line.len()); } - tokens } #[derive(Clone, PartialEq, Debug)] @@ -764,8 +791,14 @@ impl FieldSelector { } /// Get the selection that corresponds to this selector for the line. - /// If needs_fields returned false, tokens may be None. - fn get_selection<'a>(&self, line: &'a str, tokens: Option<&[Field]>) -> Selection<'a> { + /// If needs_fields returned false, tokens may be empty. + fn get_selection<'a>(&self, line: &'a str, tokens: &[Field]) -> (&'a str, NumCache) { + // `get_range` expects `None` when we don't need tokens and would get confused by an empty vector. + let tokens = if self.needs_tokens { + Some(tokens) + } else { + None + }; let mut range = &line[self.get_range(line, tokens)]; let num_cache = if self.settings.mode == SortMode::Numeric || self.settings.mode == SortMode::HumanNumeric @@ -780,24 +813,19 @@ impl FieldSelector { ); // Shorten the range to what we need to pass to numeric_str_cmp later. range = &range[num_range]; - Some(Box::new(NumCache::WithInfo(info))) + NumCache::WithInfo(info) } else if self.settings.mode == SortMode::GeneralNumeric { // Parse this number as f64, as this is the requirement for general numeric sorting. - Some(Box::new(NumCache::AsF64(general_f64_parse( - &range[get_leading_gen(range)], - )))) + NumCache::AsF64(general_f64_parse(&range[get_leading_gen(range)])) } else { // This is not a numeric sort, so we don't need a NumCache. - None + NumCache::None }; - Selection { - slice: range, - num_cache, - } + (range, num_cache) } /// Look up the range in the line that corresponds to this selector. - /// If needs_fields returned false, tokens may be None. + /// If needs_fields returned false, tokens must be None. fn get_range<'a>(&self, line: &'a str, tokens: Option<&[Field]>) -> Range { enum Resolution { // The start index of the resolved character, inclusive @@ -1297,18 +1325,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ); } - exec(&files, &settings) -} + settings.init_precomputed(); -fn output_sorted_lines<'a>(iter: impl Iterator>, settings: &GlobalSettings) { - if settings.unique { - print_sorted( - iter.dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal), - settings, - ); - } else { - print_sorted(iter, settings); - } + exec(&files, &settings) } fn exec(files: &[String], settings: &GlobalSettings) -> i32 { @@ -1328,55 +1347,59 @@ fn exec(files: &[String], settings: &GlobalSettings) -> i32 { 0 } -fn sort_by<'a>(unsorted: &mut Vec>, settings: &GlobalSettings) { +fn sort_by<'a>(unsorted: &mut Vec>, settings: &GlobalSettings, line_data: &LineData<'a>) { if settings.stable || settings.unique { - unsorted.par_sort_by(|a, b| compare_by(a, b, settings)) + unsorted.par_sort_by(|a, b| compare_by(a, b, settings, line_data, line_data)) } else { - unsorted.par_sort_unstable_by(|a, b| compare_by(a, b, settings)) + unsorted.par_sort_unstable_by(|a, b| compare_by(a, b, settings, line_data, line_data)) } } -fn compare_by<'a>(a: &Line<'a>, b: &Line<'a>, global_settings: &GlobalSettings) -> Ordering { - let mut idx = 0; +fn compare_by<'a>( + a: &Line<'a>, + b: &Line<'a>, + global_settings: &GlobalSettings, + a_line_data: &LineData<'a>, + b_line_data: &LineData<'a>, +) -> Ordering { + let mut selection_index = 0; + let mut num_info_index = 0; + let mut parsed_float_index = 0; for selector in &global_settings.selectors { - let mut _selections = None; - let (a_selection, b_selection) = if selector.is_default_selection { + let (a_str, b_str) = if selector.is_default_selection { // We can select the whole line. - // We have to store the selections outside of the if-block so that they live long enough. - _selections = Some(( - Selection { - slice: a.line, - num_cache: None, - }, - Selection { - slice: b.line, - num_cache: None, - }, - )); - // Unwrap the selections again, and return references to them. - ( - &_selections.as_ref().unwrap().0, - &_selections.as_ref().unwrap().1, - ) + (a.line, b.line) } else { - let selections = (&a.selections[idx], &b.selections[idx]); - idx += 1; + let selections = ( + a_line_data.selections + [a.index * global_settings.precomputed.selections_per_line + selection_index], + b_line_data.selections + [b.index * global_settings.precomputed.selections_per_line + selection_index], + ); + selection_index += 1; selections }; - let a_str = a_selection.slice; - let b_str = b_selection.slice; + let settings = &selector.settings; let cmp: Ordering = match settings.mode { SortMode::Random => random_shuffle(a_str, b_str, &global_settings.salt), - SortMode::Numeric | SortMode::HumanNumeric => numeric_str_cmp( - (a_str, a_selection.num_cache.as_ref().unwrap().as_num_info()), - (b_str, b_selection.num_cache.as_ref().unwrap().as_num_info()), - ), - SortMode::GeneralNumeric => general_numeric_compare( - a_selection.num_cache.as_ref().unwrap().as_f64(), - b_selection.num_cache.as_ref().unwrap().as_f64(), - ), + SortMode::Numeric | SortMode::HumanNumeric => { + let a_num_info = &a_line_data.num_infos + [a.index * global_settings.precomputed.num_infos_per_line + num_info_index]; + let b_num_info = &b_line_data.num_infos + [b.index * global_settings.precomputed.num_infos_per_line + num_info_index]; + num_info_index += 1; + numeric_str_cmp((a_str, a_num_info), (b_str, b_num_info)) + } + SortMode::GeneralNumeric => { + let a_float = &a_line_data.parsed_floats + [a.index * global_settings.precomputed.floats_per_line + parsed_float_index]; + let b_float = &b_line_data.parsed_floats + [b.index * global_settings.precomputed.floats_per_line + parsed_float_index]; + parsed_float_index += 1; + general_numeric_compare(a_float, b_float) + } SortMode::Month => month_compare(a_str, b_str), SortMode::Version => version_compare(a_str, b_str), SortMode::Default => custom_str_cmp( @@ -1470,7 +1493,7 @@ fn get_leading_gen(input: &str) -> Range { } #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] -enum GeneralF64ParseResult { +pub enum GeneralF64ParseResult { Invalid, NaN, NegInfinity, @@ -1497,8 +1520,8 @@ fn general_f64_parse(a: &str) -> GeneralF64ParseResult { /// Compares two floats, with errors and non-numerics assumed to be -inf. /// Stops coercing at the first non-numeric char. /// We explicitly need to convert to f64 in this case. -fn general_numeric_compare(a: GeneralF64ParseResult, b: GeneralF64ParseResult) -> Ordering { - a.partial_cmp(&b).unwrap() +fn general_numeric_compare(a: &GeneralF64ParseResult, b: &GeneralF64ParseResult) -> Ordering { + a.partial_cmp(b).unwrap() } fn get_rand_string() -> String { @@ -1646,6 +1669,12 @@ mod tests { use super::*; + fn tokenize_helper(line: &str, separator: Option) -> Vec { + let mut buffer = vec![]; + tokenize(line, separator, &mut buffer); + buffer + } + #[test] fn test_get_hash() { let a = "Ted".to_string(); @@ -1689,20 +1718,23 @@ mod tests { #[test] fn test_tokenize_fields() { let line = "foo bar b x"; - assert_eq!(tokenize(line, None), vec![0..3, 3..7, 7..9, 9..14,],); + assert_eq!(tokenize_helper(line, None), vec![0..3, 3..7, 7..9, 9..14,],); } #[test] fn test_tokenize_fields_leading_whitespace() { let line = " foo bar b x"; - assert_eq!(tokenize(line, None), vec![0..7, 7..11, 11..13, 13..18,]); + assert_eq!( + tokenize_helper(line, None), + vec![0..7, 7..11, 11..13, 13..18,] + ); } #[test] fn test_tokenize_fields_custom_separator() { let line = "aaa foo bar b x"; assert_eq!( - tokenize(line, Some('a')), + tokenize_helper(line, Some('a')), vec![0..0, 1..1, 2..2, 3..9, 10..18,] ); } @@ -1710,11 +1742,11 @@ mod tests { #[test] fn test_tokenize_fields_trailing_custom_separator() { let line = "a"; - assert_eq!(tokenize(line, Some('a')), vec![0..0]); + assert_eq!(tokenize_helper(line, Some('a')), vec![0..0]); let line = "aa"; - assert_eq!(tokenize(line, Some('a')), vec![0..0, 1..1]); + assert_eq!(tokenize_helper(line, Some('a')), vec![0..0, 1..1]); let line = "..a..a"; - assert_eq!(tokenize(line, Some('a')), vec![0..2, 3..5]); + assert_eq!(tokenize_helper(line, Some('a')), vec![0..2, 3..5]); } #[test] @@ -1722,13 +1754,7 @@ mod tests { fn test_line_size() { // We should make sure to not regress the size of the Line struct because // it is unconditional overhead for every line we sort. - assert_eq!(std::mem::size_of::(), 32); - // These are the fields of Line: - assert_eq!(std::mem::size_of::<&str>(), 16); - assert_eq!(std::mem::size_of::>(), 16); - - // How big is a selection? Constant cost all lines pay when we need selections. - assert_eq!(std::mem::size_of::(), 24); + assert_eq!(std::mem::size_of::(), 24); } #[test] From ce0801db909c979ad2cce37d25879159d71dbbc2 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 22 Jun 2021 15:31:49 +0200 Subject: [PATCH 275/320] tests/mv: test uutils mv instead of system util Calling `cmd_keepenv("mv")` spawned the system `mv` instead of the uutils `mv`. Also, `keepenv` isn't necessary because it doesn't need to inherit environment variables. We now actually check the stderr, because previously the result of `ends_with` was not used, making the test pass even when it shouldn't. I disabled the test on windows because `mkdir` does not support `-m` on windows, making the test fail because there will be no permission error. On FreeBSD there isn't a permission error either, and `mv` succeeds. --- tests/by-util/test_mv.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 2f35bf5eb..d8733a4f0 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -729,6 +729,7 @@ fn test_mv_verbose() { } #[test] +#[cfg(target_os = "linux")] // mkdir does not support -m on windows. Freebsd doesn't return a permission error either. fn test_mv_permission_error() { let scene = TestScenario::new("mkdir"); let folder1 = "bar"; @@ -738,12 +739,11 @@ fn test_mv_permission_error() { scene.ucmd().arg("-m777").arg(folder2).succeeds(); scene - .cmd_keepenv(util_name!()) + .ccmd("mv") .arg(folder2) .arg(folder_to_move) - .run() - .stderr_str() - .ends_with("Permission denied"); + .fails() + .stderr_contains("Permission denied"); } // Todo: From d60afb89472c0166d4c884239d8b5784f518d1b4 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 22 Jun 2021 16:02:50 +0200 Subject: [PATCH 276/320] mkdir: note that -m is not supported on windows --- src/uu/mkdir/src/mkdir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index e8a8ef2db..c5ff8b76c 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -40,7 +40,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(OPT_MODE) .short("m") .long(OPT_MODE) - .help("set file mode") + .help("set file mode (not implemented on windows)") .default_value("755"), ) .arg( From c0be9796112439b8e26315572a4f7cfa4964b079 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Tue, 22 Jun 2021 00:22:30 +0200 Subject: [PATCH 277/320] fix some issues with locale (replace "LANGUAGE" with "LC_ALL") MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `LANGUAGE=C` is not enough, `LC_ALL=C` is needed as the environment variable that overrides all the other localization settings. e.g. ```bash $ LANGUAGE=C id foobar id: ‘foobar’: no such user $ LC_ALL=C id foobar id: 'foobar': no such user ``` * replace `LANGUAGE` with `LC_ALL` as environment variable in the tests * fix the the date string of affected uutils * replace `‘` and `’` with `'` --- src/uu/base32/src/base_common.rs | 4 +-- src/uu/chown/src/chown.rs | 4 +-- src/uu/cp/src/cp.rs | 2 +- src/uu/date/src/date.rs | 6 ++-- src/uu/dircolors/src/dircolors.rs | 4 +-- src/uu/du/src/du.rs | 14 ++++----- src/uu/id/src/id.rs | 2 +- src/uu/ls/src/ls.rs | 4 +-- src/uu/mknod/src/mknod.rs | 2 +- src/uu/mktemp/src/mktemp.rs | 2 +- src/uu/mv/src/mv.rs | 22 +++++++------- src/uu/numfmt/src/format.rs | 6 ++-- src/uu/numfmt/src/numfmt.rs | 2 +- src/uu/pinky/src/pinky.rs | 2 +- src/uu/split/src/split.rs | 2 +- src/uu/stat/src/stat.rs | 2 +- src/uu/test/src/parser.rs | 4 +-- src/uu/test/src/test.rs | 8 +++--- src/uu/tr/src/tr.rs | 2 +- src/uu/truncate/src/truncate.rs | 2 +- src/uu/who/src/who.rs | 6 ++-- src/uucore/src/lib/parser/parse_size.rs | 38 ++++++++++++------------- tests/by-util/test_base32.rs | 4 +-- tests/by-util/test_base64.rs | 4 +-- tests/by-util/test_chown.rs | 4 +-- tests/by-util/test_date.rs | 2 +- tests/by-util/test_du.rs | 2 +- tests/by-util/test_head.rs | 10 +++---- tests/by-util/test_id.rs | 4 +-- tests/by-util/test_ls.rs | 2 +- tests/by-util/test_mv.rs | 18 ++++++------ tests/by-util/test_numfmt.rs | 20 ++++++------- tests/by-util/test_pinky.rs | 2 +- tests/by-util/test_split.rs | 8 +++--- tests/by-util/test_stat.rs | 2 +- tests/by-util/test_stdbuf.rs | 4 +-- tests/by-util/test_tail.rs | 10 +++---- tests/by-util/test_test.rs | 10 +++---- tests/by-util/test_truncate.rs | 14 ++++----- tests/by-util/test_users.rs | 2 +- tests/by-util/test_who.rs | 13 ++++----- 41 files changed, 135 insertions(+), 140 deletions(-) diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 256b674e2..a606351ce 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -39,7 +39,7 @@ impl Config { Some(mut values) => { let name = values.next().unwrap(); if values.len() != 0 { - return Err(format!("extra operand ‘{}’", name)); + return Err(format!("extra operand '{}'", name)); } if name == "-" { @@ -58,7 +58,7 @@ impl Config { .value_of(options::WRAP) .map(|num| { num.parse::() - .map_err(|e| format!("Invalid wrap size: ‘{}’: {}", num, e)) + .map_err(|e| format!("Invalid wrap size: '{}': {}", num, e)) }) .transpose()?; diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index ab9f10dba..7fc7f04d3 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -281,7 +281,7 @@ fn parse_spec(spec: &str) -> Result<(Option, Option), String> { let uid = if usr_only || usr_grp { Some( Passwd::locate(args[0]) - .map_err(|_| format!("invalid user: ‘{}’", spec))? + .map_err(|_| format!("invalid user: '{}'", spec))? .uid(), ) } else { @@ -290,7 +290,7 @@ fn parse_spec(spec: &str) -> Result<(Option, Option), String> { let gid = if grp_only || usr_grp { Some( Group::locate(args[1]) - .map_err(|_| format!("invalid group: ‘{}’", spec))? + .map_err(|_| format!("invalid group: '{}'", spec))? .gid(), ) } else { diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 851117bde..0d7946b06 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1254,7 +1254,7 @@ fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> { Some(name) => dest.join(name).into(), None => crash!( EXIT_ERR, - "cannot stat ‘{}’: No such file or directory", + "cannot stat '{}': No such file or directory", source.display() ), } diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 8a0e3ef3a..11c3eb31f 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -210,7 +210,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let format = if let Some(form) = matches.value_of(OPT_FORMAT) { if !form.starts_with('+') { - eprintln!("date: invalid date ‘{}’", form); + eprintln!("date: invalid date '{}'", form); return 1; } let form = form[1..].to_string(); @@ -239,7 +239,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let set_to = match matches.value_of(OPT_SET).map(parse_date) { None => None, Some(Err((input, _err))) => { - eprintln!("date: invalid date ‘{}’", input); + eprintln!("date: invalid date '{}'", input); return 1; } Some(Ok(date)) => Some(date), @@ -305,7 +305,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { println!("{}", formatted); } Err((input, _err)) => { - println!("date: invalid date ‘{}’", input); + println!("date: invalid date '{}'", input); } } } diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 2fa2e8b91..8a01d77d6 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -123,7 +123,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if matches.is_present(options::PRINT_DATABASE) { if !files.is_empty() { show_usage_error!( - "extra operand ‘{}’\nfile operands cannot be combined with \ + "extra operand '{}'\nfile operands cannot be combined with \ --print-database (-p)", files[0] ); @@ -155,7 +155,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { result = parse(INTERNAL_DB.lines(), out_format, "") } else { if files.len() > 1 { - show_usage_error!("extra operand ‘{}’", files[1]); + show_usage_error!("extra operand '{}'", files[1]); return 1; } match File::open(files[0]) { diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index fa6c34165..623faf62c 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -274,7 +274,7 @@ fn du( Err(e) => { safe_writeln!( stderr(), - "{}: cannot read directory ‘{}‘: {}", + "{}: cannot read directory '{}': {}", options.program_name, my_stat.path.display(), e @@ -318,9 +318,7 @@ fn du( let error_message = "Permission denied"; show_error_custom_description!(description, "{}", error_message) } - _ => { - show_error!("cannot access '{}': {}", entry.path().display(), error) - } + _ => show_error!("cannot access '{}': {}", entry.path().display(), error), }, }, Err(error) => show_error!("{}", error), @@ -594,9 +592,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let files = match matches.value_of(options::FILE) { Some(_) => matches.values_of(options::FILE).unwrap().collect(), - None => { - vec!["."] - } + None => vec!["."], }; let block_size = u64::try_from(read_block_size(matches.value_of(options::BLOCK_SIZE))).unwrap(); @@ -693,8 +689,8 @@ Try '{} --help' for more information.", time } else { show_error!( - "Invalid argument ‘{}‘ for --time. -‘birth‘ and ‘creation‘ arguments are not supported on this platform.", + "Invalid argument '{}' for --time. +'birth' and 'creation' arguments are not supported on this platform.", s ); return 1; diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 9037745eb..176240f0c 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -269,7 +269,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match Passwd::locate(users[i].as_str()) { Ok(p) => Some(p), Err(_) => { - show_error!("‘{}’: no such user", users[i]); + show_error!("'{}': no such user", users[i]); exit_code = 1; if i + 1 >= users.len() { break; diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 0bffa2e52..e01bba8dc 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -373,7 +373,7 @@ impl Config { .value_of(options::WIDTH) .map(|x| { x.parse::().unwrap_or_else(|_e| { - show_error!("invalid line width: ‘{}’", x); + show_error!("invalid line width: '{}'", x); exit(2); }) }) @@ -756,7 +756,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 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 \ - change time (the ‘ctime’ in the inode) instead of the modification time. When \ + 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(&[ diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index e5e6ef1fa..a1f361e55 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -210,7 +210,7 @@ fn valid_type(tpe: String) -> Result<(), String> { if vec!['b', 'c', 'u', 'p'].contains(&first_char) { Ok(()) } else { - Err(format!("invalid device type ‘{}’", tpe)) + Err(format!("invalid device type '{}'", tpe)) } }) } diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index e04de8702..b0bc3474b 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -154,7 +154,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if matches.is_present(OPT_TMPDIR) && PathBuf::from(prefix).is_absolute() { show_error!( - "invalid template, ‘{}’; with --tmpdir, it may not be absolute", + "invalid template, '{}'; with --tmpdir, it may not be absolute", template ); return 1; diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index bb402737e..d709a2117 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -230,7 +230,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { // lacks permission to access metadata. if source.symlink_metadata().is_err() { show_error!( - "cannot stat ‘{}’: No such file or directory", + "cannot stat '{}': No such file or directory", source.display() ); return 1; @@ -240,7 +240,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { if b.no_target_dir { if !source.is_dir() { show_error!( - "cannot overwrite directory ‘{}’ with non-directory", + "cannot overwrite directory '{}' with non-directory", target.display() ); return 1; @@ -249,7 +249,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { return match rename(source, target, &b) { Err(e) => { show_error!( - "cannot move ‘{}’ to ‘{}’: {}", + "cannot move '{}' to '{}': {}", source.display(), target.display(), e.to_string() @@ -263,7 +263,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { return move_files_into_dir(&[source.clone()], target, &b); } else if target.exists() && source.is_dir() { show_error!( - "cannot overwrite non-directory ‘{}’ with directory ‘{}’", + "cannot overwrite non-directory '{}' with directory '{}'", target.display(), source.display() ); @@ -278,7 +278,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { _ => { if b.no_target_dir { show_error!( - "mv: extra operand ‘{}’\n\ + "mv: extra operand '{}'\n\ Try '{} --help' for more information.", files[2].display(), executable!() @@ -294,7 +294,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 { if !target_dir.is_dir() { - show_error!("target ‘{}’ is not a directory", target_dir.display()); + show_error!("target '{}' is not a directory", target_dir.display()); return 1; } @@ -304,7 +304,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3 Some(name) => target_dir.join(name), None => { show_error!( - "cannot stat ‘{}’: No such file or directory", + "cannot stat '{}': No such file or directory", sourcepath.display() ); @@ -315,7 +315,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3 if let Err(e) = rename(sourcepath, &targetpath, b) { show_error!( - "cannot move ‘{}’ to ‘{}’: {}", + "cannot move '{}' to '{}': {}", sourcepath.display(), targetpath.display(), e.to_string() @@ -338,7 +338,7 @@ fn rename(from: &Path, to: &Path, b: &Behavior) -> io::Result<()> { match b.overwrite { OverwriteMode::NoClobber => return Ok(()), OverwriteMode::Interactive => { - println!("{}: overwrite ‘{}’? ", executable!(), to.display()); + println!("{}: overwrite '{}'? ", executable!(), to.display()); if !read_yes() { return Ok(()); } @@ -371,9 +371,9 @@ fn rename(from: &Path, to: &Path, b: &Behavior) -> io::Result<()> { rename_with_fallback(from, to)?; if b.verbose { - print!("‘{}’ -> ‘{}’", from.display(), to.display()); + print!("'{}' -> '{}'", from.display(), to.display()); match backup_path { - Some(path) => println!(" (backup: ‘{}’)", path.display()), + Some(path) => println!(" (backup: '{}')", path.display()), None => println!(), } } diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index ee692d8f0..54e122215 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -62,7 +62,7 @@ impl<'a> Iterator for WhitespaceSplitter<'a> { fn parse_suffix(s: &str) -> Result<(f64, Option)> { if s.is_empty() { - return Err("invalid number: ‘’".to_string()); + return Err("invalid number: ''".to_string()); } let with_i = s.ends_with('i'); @@ -80,7 +80,7 @@ fn parse_suffix(s: &str) -> Result<(f64, Option)> { Some('Z') => Ok(Some((RawSuffix::Z, with_i))), Some('Y') => Ok(Some((RawSuffix::Y, with_i))), Some('0'..='9') => Ok(None), - _ => Err(format!("invalid suffix in input: ‘{}’", s)), + _ => Err(format!("invalid suffix in input: '{}'", s)), }?; let suffix_len = match suffix { @@ -91,7 +91,7 @@ fn parse_suffix(s: &str) -> Result<(f64, Option)> { let number = s[..s.len() - suffix_len] .parse::() - .map_err(|_| format!("invalid number: ‘{}’", s))?; + .map_err(|_| format!("invalid number: '{}'", s))?; Ok((number, suffix)) } diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 086336437..88cb008cc 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -114,7 +114,7 @@ fn parse_options(args: &ArgMatches) -> Result { 0 => Err(value), _ => Ok(n), }) - .map_err(|value| format!("invalid header value ‘{}’", value)) + .map_err(|value| format!("invalid header value '{}'", value)) } }?; diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index d15730b32..33dcff274 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -234,7 +234,7 @@ fn idle_string(when: i64) -> String { } fn time_string(ut: &Utmpx) -> String { - time::strftime("%Y-%m-%d %H:%M", &ut.login_time()).unwrap() + time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C } impl Pinky { diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 0d5543d8b..ad5c083aa 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -234,7 +234,7 @@ impl LineSplitter { fn new(settings: &Settings) -> LineSplitter { LineSplitter { lines_per_split: settings.strategy_param.parse().unwrap_or_else(|_| { - crash!(1, "invalid number of lines: ‘{}’", settings.strategy_param) + crash!(1, "invalid number of lines: '{}'", settings.strategy_param) }), } } diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 4e1d9d2c9..7bf3db4c2 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -24,7 +24,7 @@ use std::{cmp, fs, iter}; macro_rules! check_bound { ($str: ident, $bound:expr, $beg: expr, $end: expr) => { if $end >= $bound { - return Err(format!("‘{}’: invalid directive", &$str[$beg..$end])); + return Err(format!("'{}': invalid directive", &$str[$beg..$end])); } }; } diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index d4302bd67..5eec781ba 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -167,7 +167,7 @@ impl Parser { self.expr(); match self.next_token() { Symbol::Literal(s) if s == ")" => (), - _ => panic!("expected ‘)’"), + _ => panic!("expected ')'"), } } } @@ -314,7 +314,7 @@ impl Parser { self.expr(); match self.tokens.next() { - Some(token) => Err(format!("extra argument ‘{}’", token.to_string_lossy())), + Some(token) => Err(format!("extra argument '{}'", token.to_string_lossy())), None => Ok(()), } } diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 97a244cdc..107ad2df4 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -88,7 +88,7 @@ fn eval(stack: &mut Vec) -> Result { return Ok(true); } _ => { - return Err(format!("missing argument after ‘{:?}’", op)); + return Err(format!("missing argument after '{:?}'", op)); } }; @@ -140,7 +140,7 @@ fn eval(stack: &mut Vec) -> Result { } fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result { - let format_err = |value| format!("invalid integer ‘{}’", value); + let format_err = |value| format!("invalid integer '{}'", value); let a = a.to_string_lossy(); let a: i64 = a.parse().map_err(|_| format_err(a))?; @@ -156,7 +156,7 @@ fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result { "-ge" => a >= b, "-lt" => a < b, "-le" => a <= b, - _ => return Err(format!("unknown operator ‘{}’", operator)), + _ => return Err(format!("unknown operator '{}'", operator)), }) } @@ -164,7 +164,7 @@ fn isatty(fd: &OsStr) -> Result { let fd = fd.to_string_lossy(); fd.parse() - .map_err(|_| format!("invalid integer ‘{}’", fd)) + .map_err(|_| format!("invalid integer '{}'", fd)) .map(|i| { #[cfg(not(target_os = "redox"))] unsafe { diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 3c362dcec..9916af7db 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -311,7 +311,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !(delete_flag || squeeze_flag) && sets.len() < 2 { show_error!( - "missing operand after ‘{}’\nTry `{} --help` for more information.", + "missing operand after '{}'\nTry `{} --help` for more information.", sets[0], executable!() ); diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index f81a95ab2..8ef246833 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -210,7 +210,7 @@ fn truncate_reference_and_size( let mode = match parse_mode_and_size(size_string) { Ok(m) => match m { TruncateMode::Absolute(_) => { - crash!(1, "you must specify a relative ‘--size’ with ‘--reference’") + crash!(1, "you must specify a relative '--size' with '--reference'") } _ => m, }, diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 44f565438..047452240 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -300,7 +300,7 @@ fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> { } fn time_string(ut: &Utmpx) -> String { - time::strftime("%Y-%m-%d %H:%M", &ut.login_time()).unwrap() + time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C } #[inline] @@ -523,8 +523,8 @@ impl Who { buf.push_str(&msg); } buf.push_str(&format!(" {:<12}", line)); - // "%Y-%m-%d %H:%M" - let time_size = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2; + // "%b %e %H:%M" (LC_ALL=C) + let time_size = 3 + 2 + 2 + 1 + 2; buf.push_str(&format!(" {:<1$}", time, time_size)); if !self.short_output { diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 58213adef..ec0b08c9e 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -18,7 +18,7 @@ use std::fmt; /// /// # Errors /// -/// Will return `ParseSizeError` if it’s not possible to parse this +/// Will return `ParseSizeError` if it's not possible to parse this /// string into a number, e.g. if the string does not begin with a /// numeral, or if the unit is not one of the supported units described /// in the preceding section. @@ -109,19 +109,19 @@ impl fmt::Display for ParseSizeError { impl ParseSizeError { fn parse_failure(s: &str) -> ParseSizeError { - // stderr on linux (GNU coreutils 8.32) + // stderr on linux (GNU coreutils 8.32) (LC_ALL=C) // has to be handled in the respective uutils because strings differ, e.g.: // // `NUM` - // head: invalid number of bytes: ‘1fb’ - // tail: invalid number of bytes: ‘1fb’ + // head: invalid number of bytes: '1fb' + // tail: invalid number of bytes: '1fb' // // `SIZE` - // split: invalid number of bytes: ‘1fb’ - // truncate: Invalid number: ‘1fb’ + // split: invalid number of bytes: '1fb' + // truncate: Invalid number: '1fb' // // `MODE` - // stdbuf: invalid mode ‘1fb’ + // stdbuf: invalid mode '1fb' // // `SIZE` // sort: invalid suffix in --buffer-size argument '1fb' @@ -140,27 +140,27 @@ impl ParseSizeError { // --width // --strings // etc. - ParseSizeError::ParseFailure(format!("‘{}’", s)) + ParseSizeError::ParseFailure(format!("'{}'", s)) } fn size_too_big(s: &str) -> ParseSizeError { - // stderr on linux (GNU coreutils 8.32) + // stderr on linux (GNU coreutils 8.32) (LC_ALL=C) // has to be handled in the respective uutils because strings differ, e.g.: // - // head: invalid number of bytes: ‘1Y’: Value too large for defined data type - // tail: invalid number of bytes: ‘1Y’: Value too large for defined data type - // split: invalid number of bytes: ‘1Y’: Value too large for defined data type - // truncate: Invalid number: ‘1Y’: Value too large for defined data type - // stdbuf: invalid mode ‘1Y’: Value too large for defined data type + // head: invalid number of bytes: '1Y': Value too large for defined data type + // tail: invalid number of bytes: '1Y': Value too large for defined data type + // split: invalid number of bytes: '1Y': Value too large for defined data type + // truncate: Invalid number: '1Y': Value too large for defined data type + // stdbuf: invalid mode '1Y': Value too large for defined data type // sort: -S argument '1Y' too large // du: -B argument '1Y' too large // od: -N argument '1Y' too large // etc. // // stderr on macos (brew - GNU coreutils 8.32) also differs for the same version, e.g.: - // ghead: invalid number of bytes: ‘1Y’: Value too large to be stored in data type - // gtail: invalid number of bytes: ‘1Y’: Value too large to be stored in data type - ParseSizeError::SizeTooBig(format!("‘{}’: Value too large for defined data type", s)) + // ghead: invalid number of bytes: '1Y': Value too large to be stored in data type + // gtail: invalid number of bytes: '1Y': Value too large to be stored in data type + ParseSizeError::SizeTooBig(format!("'{}': Value too large for defined data type", s)) } } @@ -227,7 +227,7 @@ mod tests { )); assert_eq!( - ParseSizeError::SizeTooBig("‘1Y’: Value too large for defined data type".to_string()), + ParseSizeError::SizeTooBig("'1Y': Value too large for defined data type".to_string()), parse_size("1Y").unwrap_err() ); } @@ -262,7 +262,7 @@ mod tests { for &test_string in &test_strings { assert_eq!( parse_size(test_string).unwrap_err(), - ParseSizeError::ParseFailure(format!("‘{}’", test_string)) + ParseSizeError::ParseFailure(format!("'{}'", test_string)) ); } } diff --git a/tests/by-util/test_base32.rs b/tests/by-util/test_base32.rs index 8e3e780c5..38ead28f1 100644 --- a/tests/by-util/test_base32.rs +++ b/tests/by-util/test_base32.rs @@ -103,7 +103,7 @@ fn test_wrap_bad_arg() { .arg(wrap_param) .arg("b") .fails() - .stderr_only("base32: Invalid wrap size: ‘b’: invalid digit found in string\n"); + .stderr_only("base32: Invalid wrap size: 'b': invalid digit found in string\n"); } } @@ -114,7 +114,7 @@ fn test_base32_extra_operand() { .arg("a.txt") .arg("a.txt") .fails() - .stderr_only("base32: extra operand ‘a.txt’"); + .stderr_only("base32: extra operand 'a.txt'"); } #[test] diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index 236f53fb1..7c7f19205 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -89,7 +89,7 @@ fn test_wrap_bad_arg() { .arg(wrap_param) .arg("b") .fails() - .stderr_only("base64: Invalid wrap size: ‘b’: invalid digit found in string\n"); + .stderr_only("base64: Invalid wrap size: 'b': invalid digit found in string\n"); } } @@ -100,7 +100,7 @@ fn test_base64_extra_operand() { .arg("a.txt") .arg("a.txt") .fails() - .stderr_only("base64: extra operand ‘a.txt’"); + .stderr_only("base64: extra operand 'a.txt'"); } #[test] diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index c8a8ea538..86365f51b 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -172,14 +172,14 @@ fn test_chown_only_colon() { // expected: // $ chown -v :: file.txt 2>out_err ; echo $? ; cat out_err // 1 - // chown: invalid group: ‘::’ + // chown: invalid group: '::' scene .ucmd() .arg("::") .arg("--verbose") .arg(file1) .fails() - .stderr_contains(&"invalid group: ‘::’"); + .stderr_contains(&"invalid group: '::'"); } #[test] diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 72747fa66..a7a5fa583 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -117,7 +117,7 @@ fn test_date_format_without_plus() { new_ucmd!() .arg("%s") .fails() - .stderr_contains("date: invalid date ‘%s’") + .stderr_contains("date: invalid date '%s'") .code_is(1); } diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index ffe449880..67036be44 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -355,7 +355,7 @@ fn test_du_no_permission() { let result = scene.ucmd().arg(SUB_DIR_LINKS).run(); // TODO: replace with ".fails()" once `du` is fixed result.stderr_contains( - "du: cannot read directory ‘subdir/links‘: Permission denied (os error 13)", + "du: cannot read directory 'subdir/links': Permission denied (os error 13)", ); #[cfg(target_os = "linux")] diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 2c4b66696..8065cb490 100755 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -255,21 +255,21 @@ fn test_head_invalid_num() { new_ucmd!() .args(&["-c", "1024R", "emptyfile.txt"]) .fails() - .stderr_is("head: invalid number of bytes: ‘1024R’"); + .stderr_is("head: invalid number of bytes: '1024R'"); new_ucmd!() .args(&["-n", "1024R", "emptyfile.txt"]) .fails() - .stderr_is("head: invalid number of lines: ‘1024R’"); + .stderr_is("head: invalid number of lines: '1024R'"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["-c", "1Y", "emptyfile.txt"]) .fails() - .stderr_is("head: invalid number of bytes: ‘1Y’: Value too large for defined data type"); + .stderr_is("head: invalid number of bytes: '1Y': Value too large for defined data type"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["-n", "1Y", "emptyfile.txt"]) .fails() - .stderr_is("head: invalid number of lines: ‘1Y’: Value too large for defined data type"); + .stderr_is("head: invalid number of lines: '1Y': Value too large for defined data type"); #[cfg(target_pointer_width = "32")] { let sizes = ["1000G", "10T"]; @@ -279,7 +279,7 @@ fn test_head_invalid_num() { .fails() .code_is(1) .stderr_only(format!( - "head: invalid number of bytes: ‘{}’: Value too large for defined data type", + "head: invalid number of bytes: '{}': Value too large for defined data type", size )); } diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index b4b929a2c..102ae2aa1 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -432,7 +432,7 @@ fn check_coreutil_version(util_name: &str, version_expected: &str) -> String { let scene = TestScenario::new(util_name); let version_check = scene .cmd_keepenv(&util_name) - .env("LANGUAGE", "C") + .env("LC_ALL", "C") .arg("--version") .run(); version_check @@ -476,7 +476,7 @@ fn expected_result(args: &[&str]) -> Result { let scene = TestScenario::new(util_name); let result = scene .cmd_keepenv(util_name) - .env("LANGUAGE", "C") + .env("LC_ALL", "C") .args(args) .run(); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index f8aa4453b..2a6e827f5 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -168,7 +168,7 @@ fn test_ls_width() { .ucmd() .args(&option.split(' ').collect::>()) .fails() - .stderr_only("ls: invalid line width: ‘1a’"); + .stderr_only("ls: invalid line width: '1a'"); } } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 2f35bf5eb..0c33eaf11 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -614,7 +614,7 @@ fn test_mv_overwrite_nonempty_dir() { // Not same error as GNU; the error message is a rust builtin // TODO: test (and implement) correct error message (or at least decide whether to do so) // Current: "mv: couldn't rename path (Directory not empty; from=a; to=b)" - // GNU: "mv: cannot move ‘a’ to ‘b’: Directory not empty" + // GNU: "mv: cannot move 'a' to 'b': Directory not empty" // Verbose output for the move should not be shown on failure let result = ucmd.arg("-vT").arg(dir_a).arg(dir_b).fails(); @@ -638,7 +638,7 @@ fn test_mv_backup_dir() { .arg(dir_b) .succeeds() .stdout_only(format!( - "‘{}’ -> ‘{}’ (backup: ‘{}~’)\n", + "'{}' -> '{}' (backup: '{}~')\n", dir_a, dir_b, dir_b )); @@ -672,7 +672,7 @@ fn test_mv_errors() { // $ at.touch file && at.mkdir dir // $ mv -T file dir - // err == mv: cannot overwrite directory ‘dir’ with non-directory + // err == mv: cannot overwrite directory 'dir' with non-directory scene .ucmd() .arg("-T") @@ -680,13 +680,13 @@ fn test_mv_errors() { .arg(dir) .fails() .stderr_is(format!( - "mv: cannot overwrite directory ‘{}’ with non-directory\n", + "mv: cannot overwrite directory '{}' with non-directory\n", dir )); // $ at.mkdir dir && at.touch file // $ mv dir file - // err == mv: cannot overwrite non-directory ‘file’ with directory ‘dir’ + // err == mv: cannot overwrite non-directory 'file' with directory 'dir' assert!(!scene .ucmd() .arg(dir) @@ -713,7 +713,7 @@ fn test_mv_verbose() { .arg(file_a) .arg(file_b) .succeeds() - .stdout_only(format!("‘{}’ -> ‘{}’\n", file_a, file_b)); + .stdout_only(format!("'{}' -> '{}'\n", file_a, file_b)); at.touch(file_a); scene @@ -723,7 +723,7 @@ fn test_mv_verbose() { .arg(file_b) .succeeds() .stdout_only(format!( - "‘{}’ -> ‘{}’ (backup: ‘{}~’)\n", + "'{}' -> '{}' (backup: '{}~')\n", file_a, file_b, file_b )); } @@ -756,5 +756,5 @@ fn test_mv_permission_error() { // -r--r--r-- 1 user user 0 okt 25 11:21 b // $ // $ mv -v a b -// mv: try to overwrite ‘b’, overriding mode 0444 (r--r--r--)? y -// ‘a’ -> ‘b’ +// mv: try to overwrite 'b', overriding mode 0444 (r--r--r--)? y +// 'a' -> 'b' diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index bb29d431e..e64182fcb 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -35,7 +35,7 @@ fn test_from_iec_i_requires_suffix() { new_ucmd!() .args(&["--from=iec-i", "1024"]) .fails() - .stderr_is("numfmt: missing 'i' suffix in input: ‘1024’ (e.g Ki/Mi/Gi)"); + .stderr_is("numfmt: missing 'i' suffix in input: '1024' (e.g Ki/Mi/Gi)"); } #[test] @@ -123,7 +123,7 @@ fn test_header_error_if_non_numeric() { new_ucmd!() .args(&["--header=two"]) .run() - .stderr_is("numfmt: invalid header value ‘two’"); + .stderr_is("numfmt: invalid header value 'two'"); } #[test] @@ -131,7 +131,7 @@ fn test_header_error_if_0() { new_ucmd!() .args(&["--header=0"]) .run() - .stderr_is("numfmt: invalid header value ‘0’"); + .stderr_is("numfmt: invalid header value '0'"); } #[test] @@ -139,7 +139,7 @@ fn test_header_error_if_negative() { new_ucmd!() .args(&["--header=-3"]) .run() - .stderr_is("numfmt: invalid header value ‘-3’"); + .stderr_is("numfmt: invalid header value '-3'"); } #[test] @@ -187,7 +187,7 @@ fn test_should_report_invalid_empty_number_on_empty_stdin() { .args(&["--from=auto"]) .pipe_in("\n") .run() - .stderr_is("numfmt: invalid number: ‘’\n"); + .stderr_is("numfmt: invalid number: ''\n"); } #[test] @@ -196,7 +196,7 @@ fn test_should_report_invalid_empty_number_on_blank_stdin() { .args(&["--from=auto"]) .pipe_in(" \t \n") .run() - .stderr_is("numfmt: invalid number: ‘’\n"); + .stderr_is("numfmt: invalid number: ''\n"); } #[test] @@ -205,14 +205,14 @@ fn test_should_report_invalid_suffix_on_stdin() { .args(&["--from=auto"]) .pipe_in("1k") .run() - .stderr_is("numfmt: invalid suffix in input: ‘1k’\n"); + .stderr_is("numfmt: invalid suffix in input: '1k'\n"); // GNU numfmt reports this one as “invalid number” new_ucmd!() .args(&["--from=auto"]) .pipe_in("NaN") .run() - .stderr_is("numfmt: invalid suffix in input: ‘NaN’\n"); + .stderr_is("numfmt: invalid suffix in input: 'NaN'\n"); } #[test] @@ -222,7 +222,7 @@ fn test_should_report_invalid_number_with_interior_junk() { .args(&["--from=auto"]) .pipe_in("1x0K") .run() - .stderr_is("numfmt: invalid number: ‘1x0K’\n"); + .stderr_is("numfmt: invalid number: '1x0K'\n"); } #[test] @@ -461,7 +461,7 @@ fn test_delimiter_overrides_whitespace_separator() { .args(&["-d,"]) .pipe_in("1 234,56") .fails() - .stderr_is("numfmt: invalid number: ‘1 234’\n"); + .stderr_is("numfmt: invalid number: '1 234'\n"); } #[test] diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index 8b50ec2bd..bc2833a42 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -106,7 +106,7 @@ fn expected_result(args: &[&str]) -> String { #[allow(clippy::needless_borrow)] TestScenario::new(&util_name) .cmd_keepenv(util_name) - .env("LANGUAGE", "C") + .env("LC_ALL", "C") .args(args) .succeeds() .stdout_move_str() diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index a1350534f..229925a1c 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -309,7 +309,7 @@ fn test_split_lines_number() { .args(&["--lines", "2fb", "file"]) .fails() .code_is(1) - .stderr_only("split: invalid number of lines: ‘2fb’"); + .stderr_only("split: invalid number of lines: '2fb'"); } #[test] @@ -318,13 +318,13 @@ fn test_split_invalid_bytes_size() { .args(&["-b", "1024R"]) .fails() .code_is(1) - .stderr_only("split: invalid number of bytes: ‘1024R’"); + .stderr_only("split: invalid number of bytes: '1024R'"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["-b", "1Y"]) .fails() .code_is(1) - .stderr_only("split: invalid number of bytes: ‘1Y’: Value too large for defined data type"); + .stderr_only("split: invalid number of bytes: '1Y': Value too large for defined data type"); #[cfg(target_pointer_width = "32")] { let sizes = ["1000G", "10T"]; @@ -334,7 +334,7 @@ fn test_split_invalid_bytes_size() { .fails() .code_is(1) .stderr_only(format!( - "split: invalid number of bytes: ‘{}’: Value too large for defined data type", + "split: invalid number of bytes: '{}': Value too large for defined data type", size )); } diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 37328d5ae..ddf78815f 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -317,7 +317,7 @@ fn expected_result(args: &[&str]) -> String { #[allow(clippy::needless_borrow)] TestScenario::new(&util_name) .cmd_keepenv(util_name) - .env("LANGUAGE", "C") + .env("LC_ALL", "C") .args(args) .succeeds() .stdout_move_str() diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index fc1b9324a..66892ea0f 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -63,12 +63,12 @@ fn test_stdbuf_invalid_mode_fails() { .args(&[*option, "1024R", "head"]) .fails() .code_is(125) - .stderr_only("stdbuf: invalid mode ‘1024R’"); + .stderr_only("stdbuf: invalid mode '1024R'"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&[*option, "1Y", "head"]) .fails() .code_is(125) - .stderr_contains("stdbuf: invalid mode ‘1Y’: Value too large for defined data type"); + .stderr_contains("stdbuf: invalid mode '1Y': Value too large for defined data type"); } } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 8478944e2..e8dd63317 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -364,21 +364,21 @@ fn test_tail_invalid_num() { new_ucmd!() .args(&["-c", "1024R", "emptyfile.txt"]) .fails() - .stderr_is("tail: invalid number of bytes: ‘1024R’"); + .stderr_is("tail: invalid number of bytes: '1024R'"); new_ucmd!() .args(&["-n", "1024R", "emptyfile.txt"]) .fails() - .stderr_is("tail: invalid number of lines: ‘1024R’"); + .stderr_is("tail: invalid number of lines: '1024R'"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["-c", "1Y", "emptyfile.txt"]) .fails() - .stderr_is("tail: invalid number of bytes: ‘1Y’: Value too large for defined data type"); + .stderr_is("tail: invalid number of bytes: '1Y': Value too large for defined data type"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["-n", "1Y", "emptyfile.txt"]) .fails() - .stderr_is("tail: invalid number of lines: ‘1Y’: Value too large for defined data type"); + .stderr_is("tail: invalid number of lines: '1Y': Value too large for defined data type"); #[cfg(target_pointer_width = "32")] { let sizes = ["1000G", "10T"]; @@ -388,7 +388,7 @@ fn test_tail_invalid_num() { .fails() .code_is(1) .stderr_only(format!( - "tail: invalid number of bytes: ‘{}’: Value too large for defined data type", + "tail: invalid number of bytes: '{}': Value too large for defined data type", size )); } diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 36e825f2d..1867927da 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -165,7 +165,7 @@ fn test_dangling_string_comparison_is_error() { .args(&["missing_something", "="]) .run() .status_code(2) - .stderr_is("test: missing argument after ‘=’"); + .stderr_is("test: missing argument after '='"); } #[test] @@ -265,7 +265,7 @@ fn test_float_inequality_is_error() { .args(&["123.45", "-ge", "6"]) .run() .status_code(2) - .stderr_is("test: invalid integer ‘123.45’"); + .stderr_is("test: invalid integer '123.45'"); } #[test] @@ -283,7 +283,7 @@ fn test_invalid_utf8_integer_compare() { cmd.run() .status_code(2) - .stderr_is("test: invalid integer ‘fo�o’"); + .stderr_is("test: invalid integer 'fo�o'"); let mut cmd = new_ucmd!(); cmd.raw.arg(arg); @@ -291,7 +291,7 @@ fn test_invalid_utf8_integer_compare() { cmd.run() .status_code(2) - .stderr_is("test: invalid integer ‘fo�o’"); + .stderr_is("test: invalid integer 'fo�o'"); } #[test] @@ -674,7 +674,7 @@ fn test_erroneous_parenthesized_expression() { .args(&["a", "!=", "(", "b", "-a", "b", ")", "!=", "c"]) .run() .status_code(2) - .stderr_is("test: extra argument ‘b’"); + .stderr_is("test: extra argument 'b'"); } #[test] diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 2da59035e..4b2e9e502 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -249,7 +249,7 @@ fn test_size_and_reference() { #[test] fn test_error_filename_only() { - // truncate: you must specify either ‘--size’ or ‘--reference’ + // truncate: you must specify either '--size' or '--reference' new_ucmd!().args(&["file"]).fails().stderr_contains( "error: The following required arguments were not provided: --reference @@ -262,15 +262,15 @@ fn test_invalid_numbers() { new_ucmd!() .args(&["-s", "0X", "file"]) .fails() - .stderr_contains("Invalid number: ‘0X’"); + .stderr_contains("Invalid number: '0X'"); new_ucmd!() .args(&["-s", "0XB", "file"]) .fails() - .stderr_contains("Invalid number: ‘0XB’"); + .stderr_contains("Invalid number: '0XB'"); new_ucmd!() .args(&["-s", "0B", "file"]) .fails() - .stderr_contains("Invalid number: ‘0B’"); + .stderr_contains("Invalid number: '0B'"); } #[test] @@ -299,13 +299,13 @@ fn test_truncate_bytes_size() { .args(&["--size", "1024R", "file"]) .fails() .code_is(1) - .stderr_only("truncate: Invalid number: ‘1024R’"); + .stderr_only("truncate: Invalid number: '1024R'"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["--size", "1Y", "file"]) .fails() .code_is(1) - .stderr_only("truncate: Invalid number: ‘1Y’: Value too large for defined data type"); + .stderr_only("truncate: Invalid number: '1Y': Value too large for defined data type"); #[cfg(target_pointer_width = "32")] { let sizes = ["1000G", "10T"]; @@ -315,7 +315,7 @@ fn test_truncate_bytes_size() { .fails() .code_is(1) .stderr_only(format!( - "truncate: Invalid number: ‘{}’: Value too large for defined data type", + "truncate: Invalid number: '{}': Value too large for defined data type", size )); } diff --git a/tests/by-util/test_users.rs b/tests/by-util/test_users.rs index 68bdf9a5e..1bcbdbdc1 100644 --- a/tests/by-util/test_users.rs +++ b/tests/by-util/test_users.rs @@ -17,7 +17,7 @@ fn test_users_check_name() { #[allow(clippy::needless_borrow)] let expected = TestScenario::new(&util_name) .cmd_keepenv(util_name) - .env("LANGUAGE", "C") + .env("LC_ALL", "C") .succeeds() .stdout_move_str(); diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 4907d2306..9315a5956 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -158,13 +158,12 @@ fn test_users() { let mut v_actual: Vec<&str> = actual.split_whitespace().collect(); let mut v_expect: Vec<&str> = expect.split_whitespace().collect(); - // TODO: `--users` differs from GNU's output on macOS - // Diff < left / right > : - // <"runner console 2021-05-20 22:03 00:08 196\n" - // >"runner console 2021-05-20 22:03 old 196\n" + // TODO: `--users` sometimes differs from GNU's output on macOS (race condition?) + // actual: "runner console Jun 23 06:37 00:34 196\n" + // expect: "runner console Jun 23 06:37 old 196\n" if cfg!(target_os = "macos") { - v_actual.remove(4); - v_expect.remove(4); + v_actual.remove(5); + v_expect.remove(5); } assert_eq!(v_actual, v_expect); @@ -242,7 +241,7 @@ fn expected_result(args: &[&str]) -> String { #[allow(clippy::needless_borrow)] TestScenario::new(&util_name) .cmd_keepenv(util_name) - .env("LANGUAGE", "C") + .env("LC_ALL", "C") .args(args) .succeeds() .stdout_move_str() From 4b3da59b0eb89ebd840d158cbab756ecdc82e936 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 23 Jun 2021 12:27:01 +0200 Subject: [PATCH 278/320] id: refactor identifiers * change of identifier names and spelling according to the suggestions in the review of #2446 --- src/uu/id/src/id.rs | 8 ++++---- tests/by-util/test_id.rs | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 9037745eb..1c967ec12 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -13,11 +13,11 @@ // http://www.opensource.apple.com/source/shell_cmds/shell_cmds-118/id/id.c // // * This was partially rewritten in order for stdout/stderr/exit_code -// to be conform with GNU coreutils (8.32) testsuite for `id`. +// to be conform with GNU coreutils (8.32) test suite for `id`. // // * This supports multiple users (a feature that was introduced in coreutils 8.31) // -// * This passes GNU's coreutils Testsuite (8.32) +// * This passes GNU's coreutils Test suite (8.32) // for "tests/id/uid.sh" and "tests/id/zero/sh". // // * Option '--zero' does not exist for BSD's `id`, therefore '--zero' is only @@ -26,7 +26,7 @@ // * Help text based on BSD's `id` manpage and GNU's `id` manpage. // -// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag testsuite +// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag #![allow(non_camel_case_types)] #![allow(dead_code)] @@ -242,7 +242,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!(1, "cannot print only names or real IDs in default format"); } if (state.zflag) && default_format { - // NOTE: GNU testsuite "id/zero.sh" needs this stderr output: + // NOTE: GNU test suite "id/zero.sh" needs this stderr output: crash!(1, "option --zero not permitted in default format"); } diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index b4b929a2c..b0f48fe70 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -1,6 +1,6 @@ use crate::common::util::*; -// spell-checker:ignore (ToDO) testsuite coreutil +// spell-checker:ignore (ToDO) coreutil // These tests run the GNU coreutils `(g)id` binary in `$PATH` in order to gather reference values. // If the `(g)id` in `$PATH` doesn't include a coreutils version string, @@ -8,8 +8,8 @@ use crate::common::util::*; // The reference version is 8.32. Here 8.30 was chosen because right now there's no // ubuntu image for github action available with a higher version than 8.30. -const VERSION_EXPECTED: &str = "8.30"; // Version expected for the reference `id` in $PATH -const VERSION_MULTIPLE_USERS: &str = "8.31"; +const VERSION_MIN: &str = "8.30"; // minimum Version for the reference `id` in $PATH +const VERSION_MIN_MULTIPLE_USERS: &str = "8.31"; // this feature was introduced in GNU's coreutils 8.31 const UUTILS_WARNING: &str = "uutils-tests-warning"; const UUTILS_INFO: &str = "uutils-tests-info"; @@ -210,13 +210,13 @@ fn test_id_multiple_users() { let util_name = util_name!(); #[cfg(all(unix, not(target_os = "linux")))] let util_name = &format!("g{}", util_name!()); - let version_check_string = check_coreutil_version(util_name, VERSION_MULTIPLE_USERS); + let version_check_string = check_coreutil_version(util_name, VERSION_MIN_MULTIPLE_USERS); if version_check_string.starts_with(UUTILS_WARNING) { println!("{}\ntest skipped", version_check_string); return; } - // Same typical users that GNU testsuite is using. + // Same typical users that GNU test suite is using. let test_users = ["root", "man", "postfix", "sshd", &whoami()]; let scene = TestScenario::new(util_name!()); @@ -278,7 +278,7 @@ fn test_id_multiple_users_non_existing() { let util_name = util_name!(); #[cfg(all(unix, not(target_os = "linux")))] let util_name = &format!("g{}", util_name!()); - let version_check_string = check_coreutil_version(util_name, VERSION_MULTIPLE_USERS); + let version_check_string = check_coreutil_version(util_name, VERSION_MIN_MULTIPLE_USERS); if version_check_string.starts_with(UUTILS_WARNING) { println!("{}\ntest skipped", version_check_string); return; @@ -467,7 +467,7 @@ fn expected_result(args: &[&str]) -> Result { #[cfg(all(unix, not(target_os = "linux")))] let util_name = &format!("g{}", util_name!()); - let version_check_string = check_coreutil_version(util_name, VERSION_EXPECTED); + let version_check_string = check_coreutil_version(util_name, VERSION_MIN); if version_check_string.starts_with(UUTILS_WARNING) { return Err(version_check_string); } From 11f36eae3b1944b40af7ca51af1a10458bb50865 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 21 Jun 2021 20:55:57 +0200 Subject: [PATCH 279/320] tests/groups: fix/add tests for (multiple) username(s) --- src/uu/groups/Cargo.toml | 2 +- src/uu/groups/src/groups.rs | 4 +- tests/by-util/test_groups.rs | 202 ++++++++++++++++++++++++++++------- 3 files changed, 164 insertions(+), 44 deletions(-) diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index e7ce52650..4a5a537e5 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -3,7 +3,7 @@ name = "uu_groups" version = "0.0.6" authors = ["uutils developers"] license = "MIT" -description = "groups ~ (uutils) print the groups a user is in" +description = "groups ~ (uutils) display group memberships for USERNAME" homepage = "https://github.com/uutils/coreutils" repository = "https://github.com/uutils/coreutils/tree/master/src/uu/groups" diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 22e7b8918..6585f3d16 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -7,7 +7,7 @@ // file that was distributed with this source code. // // ============================================================================ -// Testsuite summary for GNU coreutils 8.32.162-4eda +// Test suite summary for GNU coreutils 8.32.162-4eda // ============================================================================ // PASS: tests/misc/groups-dash.sh // PASS: tests/misc/groups-process-all.sh @@ -52,7 +52,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - let mut exit_code = 1; + let mut exit_code = 0; if users.is_empty() { println!( diff --git a/tests/by-util/test_groups.rs b/tests/by-util/test_groups.rs index c1b98aea1..9bd0cd12a 100644 --- a/tests/by-util/test_groups.rs +++ b/tests/by-util/test_groups.rs @@ -1,56 +1,176 @@ use crate::common::util::*; +// spell-checker:ignore (ToDO) coreutil + +// These tests run the GNU coreutils `(g)groups` binary in `$PATH` in order to gather reference values. +// If the `(g)groups` in `$PATH` doesn't include a coreutils version string, +// or the version is too low, the test is skipped. + +// The reference version is 8.32. Here 8.30 was chosen because right now there's no +// ubuntu image for github action available with a higher version than 8.30. +const VERSION_MIN: &str = "8.30"; // minimum Version for the reference `groups` in $PATH +const VERSION_MIN_MULTIPLE_USERS: &str = "8.31"; // this feature was introduced in GNU's coreutils 8.31 +const UUTILS_WARNING: &str = "uutils-tests-warning"; +const UUTILS_INFO: &str = "uutils-tests-info"; + +macro_rules! unwrap_or_return { + ( $e:expr ) => { + match $e { + Ok(x) => x, + Err(e) => { + println!("{}: test skipped: {}", UUTILS_INFO, e); + return; + } + } + }; +} + +fn whoami() -> String { + // Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. + // + // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" + // whoami: cannot find name for user ID 1001 + // id --name: cannot find name for user ID 1001 + // id --name: cannot find name for group ID 116 + // + // However, when running "id" from within "/bin/bash" it looks fine: + // id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)" + // whoami: "runner" + + // Use environment variable to get current user instead of + // invoking `whoami` and fall back to user "nobody" on error. + std::env::var("USER").unwrap_or_else(|e| { + println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e); + "nobody".to_string() + }) +} + #[test] #[cfg(unix)] fn test_groups() { - if !is_ci() { - new_ucmd!().succeeds().stdout_is(expected_result(&[])); - } else { - // TODO: investigate how this could be tested in CI - // stderr = groups: cannot find name for group ID 116 - println!("test skipped:"); - } + let result = new_ucmd!().run(); + let exp_result = unwrap_or_return!(expected_result(&[])); + + result + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); } #[test] #[cfg(unix)] -#[ignore = "fixme: 'groups USERNAME' needs more debugging"] fn test_groups_username() { - let scene = TestScenario::new(util_name!()); - let whoami_result = scene.cmd("whoami").run(); + let test_users = [&whoami()[..]]; - let username = if whoami_result.succeeded() { - whoami_result.stdout_move_str() - } else if is_ci() { - String::from("docker") - } else { - println!("test skipped:"); + let result = new_ucmd!().args(&test_users).run(); + let exp_result = unwrap_or_return!(expected_result(&test_users)); + + result + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); +} + +#[test] +#[cfg(unix)] +fn test_groups_username_multiple() { + // TODO: [2021-06; jhscheer] refactor this as `let util_name = host_name_for(util_name!())` when that function is added to 'tests/common' + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(all(unix, not(target_os = "linux")))] + let util_name = &format!("g{}", util_name!()); + let version_check_string = check_coreutil_version(util_name, VERSION_MIN_MULTIPLE_USERS); + if version_check_string.starts_with(UUTILS_WARNING) { + println!("{}\ntest skipped", version_check_string); return; + } + let test_users = ["root", "man", "postfix", "sshd", &whoami()]; + + let result = new_ucmd!().args(&test_users).run(); + let exp_result = unwrap_or_return!(expected_result(&test_users)); + + result + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); +} + +fn check_coreutil_version(util_name: &str, version_expected: &str) -> String { + // example: + // $ id --version | head -n 1 + // id (GNU coreutils) 8.32.162-4eda + let scene = TestScenario::new(util_name); + let version_check = scene + .cmd_keepenv(&util_name) + .env("LC_ALL", "C") + .arg("--version") + .run(); + version_check + .stdout_str() + .split('\n') + .collect::>() + .get(0) + .map_or_else( + || format!("{}: unexpected output format for reference coreutil: '{} --version'", UUTILS_WARNING, util_name), + |s| { + if s.contains(&format!("(GNU coreutils) {}", version_expected)) { + s.to_string() + } else if s.contains("(GNU coreutils)") { + let version_found = s.split_whitespace().last().unwrap()[..4].parse::().unwrap_or_default(); + let version_expected = version_expected.parse::().unwrap_or_default(); + if version_found > version_expected { + format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, version_expected, version_found) + } else { + format!("{}: version for the reference coreutil '{}' does not match; expected: {}, found: {}", UUTILS_WARNING, util_name, version_expected, version_found) } + } else { + format!("{}: no coreutils version string found for reference coreutils '{} --version'", UUTILS_WARNING, util_name) + } + }, + ) +} + +#[allow(clippy::needless_borrow)] +#[cfg(unix)] +fn expected_result(args: &[&str]) -> Result { + // TODO: [2021-06; jhscheer] refactor this as `let util_name = host_name_for(util_name!())` when that function is added to 'tests/common' + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(all(unix, not(target_os = "linux")))] + let util_name = &format!("g{}", util_name!()); + + let version_check_string = check_coreutil_version(util_name, VERSION_MIN); + if version_check_string.starts_with(UUTILS_WARNING) { + return Err(version_check_string); + } + println!("{}", version_check_string); + + let scene = TestScenario::new(util_name); + let result = scene + .cmd_keepenv(util_name) + .env("LC_ALL", "C") + .args(args) + .run(); + + let (stdout, stderr): (String, String) = if cfg!(target_os = "linux") { + ( + result.stdout_str().to_string(), + result.stderr_str().to_string(), + ) + } else { + // strip 'g' prefix from results: + let from = util_name.to_string() + ":"; + let to = &from[1..]; + ( + result.stdout_str().replace(&from, to), + result.stderr_str().replace(&from, to), + ) }; - // TODO: stdout should be in the form: "username : group1 group2 group3" - - scene - .ucmd() - .arg(&username) - .succeeds() - .stdout_is(expected_result(&[&username])); -} - -#[cfg(unix)] -fn expected_result(args: &[&str]) -> String { - // We want to use GNU id. On most linux systems, this is "id", but on - // bsd-like systems (e.g. FreeBSD, MacOS), it is commonly "gid". - #[cfg(any(target_os = "linux"))] - let util_name = "id"; - #[cfg(not(target_os = "linux"))] - let util_name = "gid"; - - TestScenario::new(util_name) - .cmd_keepenv(util_name) - .env("LANGUAGE", "C") - .args(args) - .args(&["-Gn"]) - .succeeds() - .stdout_move_str() + Ok(CmdResult::new( + Some(result.tmpd()), + Some(result.code()), + result.succeeded(), + stdout.as_bytes(), + stderr.as_bytes(), + )) } From 8884666ce0531ba3751e96bfd46bd710a2e55538 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 23 Jun 2021 10:00:27 -0500 Subject: [PATCH 280/320] maint/CICD ~ fix dependency display errors (relax network lockout) --- .github/workflows/CICD.yml | 4 ++-- .github/workflows/FixPR.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index a8046269a..1aa9cc50a 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -198,7 +198,7 @@ jobs: echo "## dependency list" cargo fetch --locked --quiet ## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors - RUSTUP_TOOLCHAIN=stable cargo-tree tree --frozen --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique + RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique - name: Test uses: actions-rs/cargo@v1 with: @@ -418,7 +418,7 @@ jobs: # dependencies echo "## dependency list" cargo fetch --locked --quiet - cargo-tree tree --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all --no-dev-dependencies --no-indent | grep -vE "$PWD" | sort --unique + cargo-tree tree --locked --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all --no-dev-dependencies --no-indent | grep -vE "$PWD" | sort --unique - name: Build uses: actions-rs/cargo@v1 with: diff --git a/.github/workflows/FixPR.yml b/.github/workflows/FixPR.yml index 17470df26..5c87d5db0 100644 --- a/.github/workflows/FixPR.yml +++ b/.github/workflows/FixPR.yml @@ -73,7 +73,7 @@ jobs: echo "## dependency list" cargo fetch --locked --quiet ## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors - RUSTUP_TOOLCHAIN=stable cargo-tree tree --frozen --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique + RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique - name: Commit any changes (to '${{ env.BRANCH_TARGET }}') uses: EndBug/add-and-commit@v7 with: From 17a959853e938f9b3af316b5f362a854d976bb1f Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 23 Jun 2021 10:05:49 -0500 Subject: [PATCH 281/320] maint/CICD ~ suppress useless `rustup` notices --- .github/workflows/CICD.yml | 4 ++-- .github/workflows/FixPR.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 1aa9cc50a..9c0e94bb2 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -189,7 +189,7 @@ jobs: # tooling info display echo "## tooling" which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true - rustup -V + rustup -V 2>/dev/null rustup show active-toolchain cargo -V rustc -V @@ -410,7 +410,7 @@ jobs: # tooling info display echo "## tooling" which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true - rustup -V + rustup -V 2>/dev/null rustup show active-toolchain cargo -V rustc -V diff --git a/.github/workflows/FixPR.yml b/.github/workflows/FixPR.yml index 5c87d5db0..cfcb6e597 100644 --- a/.github/workflows/FixPR.yml +++ b/.github/workflows/FixPR.yml @@ -64,7 +64,7 @@ jobs: ## tooling info display echo "## tooling" which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true - rustup -V + rustup -V 2>/dev/null rustup show active-toolchain cargo -V rustc -V From 42fed9186dbec1ea0058f8191a1f1ff3ff7d555c Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 23 Jun 2021 12:03:35 -0500 Subject: [PATCH 282/320] maint/docs ~ add ToDO for change from `cargo-tree` to `cargo tree` --- .github/workflows/CICD.yml | 2 ++ .github/workflows/FixPR.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 9c0e94bb2..b2d77a5a4 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -7,6 +7,8 @@ name: CICD # spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rustc rustfmt rustup shopt xargs # spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend runtest tempfile testsuite uutils +# ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45 + env: PROJECT_NAME: coreutils PROJECT_DESC: "Core universal (cross-platform) utilities" diff --git a/.github/workflows/FixPR.yml b/.github/workflows/FixPR.yml index cfcb6e597..d3f8a86b8 100644 --- a/.github/workflows/FixPR.yml +++ b/.github/workflows/FixPR.yml @@ -2,6 +2,8 @@ name: FixPR # Trigger automated fixes for PRs being merged (with associated commits) +# ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45 + env: BRANCH_TARGET: master From b881c4ef925430d7366b97adab10489d69043997 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 23 Jun 2021 12:10:48 -0500 Subject: [PATCH 283/320] docs ~ add 'Jan Scheer' to spell-checker exceptions word list --- .vscode/cspell.dictionaries/people.wordlist.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/cspell.dictionaries/people.wordlist.txt b/.vscode/cspell.dictionaries/people.wordlist.txt index 0a091633f..0c5893eae 100644 --- a/.vscode/cspell.dictionaries/people.wordlist.txt +++ b/.vscode/cspell.dictionaries/people.wordlist.txt @@ -58,6 +58,9 @@ Haitao Li Inokentiy Babushkin Inokentiy Babushkin +Jan Scheer * jhscheer + Jan + Scheer Jeremiah Peschka Jeremiah Peschka From 2990ebd0aab2d50dee97ed3876fd05bfe328fb4a Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 23 Jun 2021 17:01:42 -0500 Subject: [PATCH 284/320] docs ~ fix addition of 'jhscheer' to spell-checker exceptions word list --- .vscode/cspell.dictionaries/people.wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/cspell.dictionaries/people.wordlist.txt b/.vscode/cspell.dictionaries/people.wordlist.txt index 0c5893eae..d7665585b 100644 --- a/.vscode/cspell.dictionaries/people.wordlist.txt +++ b/.vscode/cspell.dictionaries/people.wordlist.txt @@ -61,6 +61,7 @@ Inokentiy Babushkin Jan Scheer * jhscheer Jan Scheer + jhscheer Jeremiah Peschka Jeremiah Peschka From b03d4c02bb70960d609f4ac15f3dac0a4e0000ec Mon Sep 17 00:00:00 2001 From: James Vasile Date: Thu, 24 Jun 2021 10:44:47 -0400 Subject: [PATCH 285/320] Fix typo: duplicated word --- docs/uutils.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/uutils.rst b/docs/uutils.rst index 19af87fef..e3b8c6a1a 100644 --- a/docs/uutils.rst +++ b/docs/uutils.rst @@ -16,7 +16,7 @@ Synopsis Description ----------- -``uutils`` is a program that contains that other coreutils commands, somewhat +``uutils`` is a program that contains other coreutils commands, somewhat similar to Busybox. --help, -h print a help menu for PROGRAM displaying accepted options and From 8bebfbb3e648c99d519f3ad55ea73c49d49668d7 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 24 Jun 2021 18:33:33 +0200 Subject: [PATCH 286/320] sort: don't store slices for general numeric sort Gerenal numeric sort works by comparing pre-parsed floating point numbers. That means that we don't have to store the &str the float was parsed from. As a result, memory usage was slightly reduced for general numeric sort. --- src/uu/sort/src/sort.rs | 73 ++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 2512d65d1..202ab5d1a 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -227,11 +227,8 @@ impl GlobalSettings { /// afterwards. fn init_precomputed(&mut self) { self.precomputed.needs_tokens = self.selectors.iter().any(|s| s.needs_tokens); - self.precomputed.selections_per_line = self - .selectors - .iter() - .filter(|s| !s.is_default_selection) - .count(); + self.precomputed.selections_per_line = + self.selectors.iter().filter(|s| s.needs_selection).count(); self.precomputed.num_infos_per_line = self .selectors .iter() @@ -362,10 +359,10 @@ impl Default for KeySettings { Self::from(&GlobalSettings::default()) } } -enum NumCache { +enum Selection<'a> { AsF64(GeneralF64ParseResult), - WithInfo(NumInfo), - None, + WithNumInfo(&'a str, NumInfo), + Str(&'a str), } type Field = Range; @@ -392,17 +389,22 @@ impl<'a> Line<'a> { if settings.precomputed.needs_tokens { tokenize(line, settings.separator, token_buffer); } - for (selection, num_cache) in settings + for (selector, selection) in settings .selectors .iter() - .filter(|selector| !selector.is_default_selection) - .map(|selector| selector.get_selection(line, token_buffer)) + .map(|selector| (selector, selector.get_selection(line, token_buffer))) { - line_data.selections.push(selection); - match num_cache { - NumCache::AsF64(parsed_float) => line_data.parsed_floats.push(parsed_float), - NumCache::WithInfo(num_info) => line_data.num_infos.push(num_info), - NumCache::None => (), + match selection { + Selection::AsF64(parsed_float) => line_data.parsed_floats.push(parsed_float), + Selection::WithNumInfo(str, num_info) => { + line_data.num_infos.push(num_info); + line_data.selections.push(str); + } + Selection::Str(str) => { + if selector.needs_selection { + line_data.selections.push(str) + } + } } } Self { line, index } @@ -667,8 +669,10 @@ struct FieldSelector { to: Option, settings: KeySettings, needs_tokens: bool, - // Whether the selection for each line is going to be the whole line with no NumCache - is_default_selection: bool, + // Whether this selector operates on a sub-slice of a line. + // Selections are therefore not needed when this selector matches the whole line + // or the sort mode is general-numeric. + needs_selection: bool, } impl Default for FieldSelector { @@ -678,7 +682,7 @@ impl Default for FieldSelector { to: None, settings: Default::default(), needs_tokens: false, - is_default_selection: true, + needs_selection: false, } } } @@ -774,14 +778,12 @@ impl FieldSelector { Err("invalid character index 0 for the start position of a field".to_string()) } else { Ok(Self { - is_default_selection: from.field == 1 - && from.char == 1 - && to.is_none() - && !matches!( - settings.mode, - SortMode::Numeric | SortMode::GeneralNumeric | SortMode::HumanNumeric - ) - && !from.ignore_blanks, + needs_selection: (from.field != 1 + || from.char != 1 + || to.is_some() + || matches!(settings.mode, SortMode::Numeric | SortMode::HumanNumeric) + || from.ignore_blanks) + && !matches!(settings.mode, SortMode::GeneralNumeric), needs_tokens: from.field != 1 || from.char == 0 || to.is_some(), from, to, @@ -792,7 +794,7 @@ impl FieldSelector { /// Get the selection that corresponds to this selector for the line. /// If needs_fields returned false, tokens may be empty. - fn get_selection<'a>(&self, line: &'a str, tokens: &[Field]) -> (&'a str, NumCache) { + fn get_selection<'a>(&self, line: &'a str, tokens: &[Field]) -> Selection<'a> { // `get_range` expects `None` when we don't need tokens and would get confused by an empty vector. let tokens = if self.needs_tokens { Some(tokens) @@ -800,9 +802,7 @@ impl FieldSelector { None }; let mut range = &line[self.get_range(line, tokens)]; - let num_cache = if self.settings.mode == SortMode::Numeric - || self.settings.mode == SortMode::HumanNumeric - { + if self.settings.mode == SortMode::Numeric || self.settings.mode == SortMode::HumanNumeric { // Parse NumInfo for this number. let (info, num_range) = NumInfo::parse( range, @@ -813,15 +813,14 @@ impl FieldSelector { ); // Shorten the range to what we need to pass to numeric_str_cmp later. range = &range[num_range]; - NumCache::WithInfo(info) + Selection::WithNumInfo(range, info) } else if self.settings.mode == SortMode::GeneralNumeric { // Parse this number as f64, as this is the requirement for general numeric sorting. - NumCache::AsF64(general_f64_parse(&range[get_leading_gen(range)])) + Selection::AsF64(general_f64_parse(&range[get_leading_gen(range)])) } else { // This is not a numeric sort, so we don't need a NumCache. - NumCache::None - }; - (range, num_cache) + Selection::Str(range) + } } /// Look up the range in the line that corresponds to this selector. @@ -1366,7 +1365,7 @@ fn compare_by<'a>( let mut num_info_index = 0; let mut parsed_float_index = 0; for selector in &global_settings.selectors { - let (a_str, b_str) = if selector.is_default_selection { + let (a_str, b_str) = if !selector.needs_selection { // We can select the whole line. (a.line, b.line) } else { From 548a895cd6bd9f014533bb3bd1e58b8410beb40a Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 25 Jun 2021 18:19:00 +0200 Subject: [PATCH 287/320] sort: compatibility of human-numeric sort Closes #1985. This makes human-numeric sort follow the same algorithm as GNU's/FreeBSD's sort. As documented by GNU in https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html, we first compare by sign, then by si unit and finally by the numeric value. --- src/uu/sort/src/numeric_str_cmp.rs | 71 +++++++++++++------ src/uu/sort/src/sort.rs | 12 +++- tests/by-util/test_sort.rs | 12 +++- .../fixtures/sort/human_block_sizes.expected | 1 + .../sort/human_block_sizes.expected.debug | 3 + tests/fixtures/sort/human_block_sizes.txt | 3 +- 6 files changed, 77 insertions(+), 25 deletions(-) diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index 8cd3faab2..d753c2d9d 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -81,28 +81,12 @@ impl NumInfo { } if Self::is_invalid_char(char, &mut had_decimal_pt, &parse_settings) { - let si_unit = if parse_settings.accept_si_units { - match char { - 'K' | 'k' => 3, - 'M' => 6, - 'G' => 9, - 'T' => 12, - 'P' => 15, - 'E' => 18, - 'Z' => 21, - 'Y' => 24, - _ => 0, - } - } else { - 0 - }; return if let Some(start) = start { + let has_si_unit = parse_settings.accept_si_units + && matches!(char, 'K' | 'k' | 'M' | 'G' | 'T' | 'P' | 'E' | 'Z' | 'Y'); ( - NumInfo { - exponent: exponent + si_unit, - sign, - }, - start..idx, + NumInfo { exponent, sign }, + start..if has_si_unit { idx + 1 } else { idx }, ) } else { ( @@ -182,8 +166,53 @@ impl NumInfo { } } -/// compare two numbers as strings without parsing them as a number first. This should be more performant and can handle numbers more precisely. +fn get_unit(unit: Option) -> u8 { + if let Some(unit) = unit { + match unit { + 'K' | 'k' => 1, + 'M' => 2, + 'G' => 3, + 'T' => 4, + 'P' => 5, + 'E' => 6, + 'Z' => 7, + 'Y' => 8, + _ => 0, + } + } else { + 0 + } +} + +/// Compare two numbers according to the rules of human numeric comparison. +/// The SI-Unit takes precedence over the actual value (i.e. 2000M < 1G). +pub fn human_numeric_str_cmp( + (a, a_info): (&str, &NumInfo), + (b, b_info): (&str, &NumInfo), +) -> Ordering { + // 1. Sign + if a_info.sign != b_info.sign { + return a_info.sign.cmp(&b_info.sign); + } + // 2. Unit + let a_unit = get_unit(a.chars().next_back()); + let b_unit = get_unit(b.chars().next_back()); + let ordering = a_unit.cmp(&b_unit); + if ordering != Ordering::Equal { + if a_info.sign == Sign::Negative { + ordering.reverse() + } else { + ordering + } + } else { + // 3. Number + numeric_str_cmp((a, a_info), (b, b_info)) + } +} + +/// Compare two numbers as strings without parsing them as a number first. This should be more performant and can handle numbers more precisely. /// NumInfo is needed to provide a fast path for most numbers. +#[inline(always)] pub fn numeric_str_cmp((a, a_info): (&str, &NumInfo), (b, b_info): (&str, &NumInfo)) -> Ordering { // check for a difference in the sign if a_info.sign != b_info.sign { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 202ab5d1a..d0e574627 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -28,7 +28,7 @@ use clap::{crate_version, App, Arg}; use custom_str_cmp::custom_str_cmp; use ext_sort::ext_sort; use fnv::FnvHasher; -use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; +use numeric_str_cmp::{human_numeric_str_cmp, numeric_str_cmp, NumInfo, NumInfoParseSettings}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rayon::prelude::*; @@ -1383,7 +1383,7 @@ fn compare_by<'a>( let cmp: Ordering = match settings.mode { SortMode::Random => random_shuffle(a_str, b_str, &global_settings.salt), - SortMode::Numeric | SortMode::HumanNumeric => { + SortMode::Numeric => { let a_num_info = &a_line_data.num_infos [a.index * global_settings.precomputed.num_infos_per_line + num_info_index]; let b_num_info = &b_line_data.num_infos @@ -1391,6 +1391,14 @@ fn compare_by<'a>( num_info_index += 1; numeric_str_cmp((a_str, a_num_info), (b_str, b_num_info)) } + SortMode::HumanNumeric => { + let a_num_info = &a_line_data.num_infos + [a.index * global_settings.precomputed.num_infos_per_line + num_info_index]; + let b_num_info = &b_line_data.num_infos + [b.index * global_settings.precomputed.num_infos_per_line + num_info_index]; + num_info_index += 1; + human_numeric_str_cmp((a_str, a_num_info), (b_str, b_num_info)) + } SortMode::GeneralNumeric => { let a_float = &a_line_data.parsed_floats [a.index * global_settings.precomputed.floats_per_line + parsed_float_index]; diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 01fafae00..3e841f630 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -456,10 +456,20 @@ fn test_human_block_sizes2() { .arg(human_numeric_sort_param) .pipe_in(input) .succeeds() - .stdout_only("-8T\n0.8M\n8981K\n21G\n909991M\n"); + .stdout_only("-8T\n8981K\n0.8M\n909991M\n21G\n"); } } +#[test] +fn test_human_numeric_zero_stable() { + let input = "0M\n0K\n-0K\n-P\n-0M\n"; + new_ucmd!() + .arg("-hs") + .pipe_in(input) + .succeeds() + .stdout_only(input); +} + #[test] fn test_month_default2() { for month_sort_param in &["-M", "--month-sort", "--sort=month"] { diff --git a/tests/fixtures/sort/human_block_sizes.expected b/tests/fixtures/sort/human_block_sizes.expected index 0e4fdfbb6..5b4f8bb83 100644 --- a/tests/fixtures/sort/human_block_sizes.expected +++ b/tests/fixtures/sort/human_block_sizes.expected @@ -1,3 +1,4 @@ +0K K 844K 981K diff --git a/tests/fixtures/sort/human_block_sizes.expected.debug b/tests/fixtures/sort/human_block_sizes.expected.debug index cde98628e..398ff9db4 100644 --- a/tests/fixtures/sort/human_block_sizes.expected.debug +++ b/tests/fixtures/sort/human_block_sizes.expected.debug @@ -1,3 +1,6 @@ +0K +__ +__ K ^ no match for key _ diff --git a/tests/fixtures/sort/human_block_sizes.txt b/tests/fixtures/sort/human_block_sizes.txt index 9cc2b3c6c..a5adb9b5e 100644 --- a/tests/fixtures/sort/human_block_sizes.txt +++ b/tests/fixtures/sort/human_block_sizes.txt @@ -9,4 +9,5 @@ 844K 981K 13M -K \ No newline at end of file +K +0K \ No newline at end of file From 004b5d1b386d35019dd114c88162cadb62e0b031 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 25 Jun 2021 19:35:33 +0200 Subject: [PATCH 288/320] format: formatting --- src/uu/numfmt/src/format.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index 4e8cd8f06..e44446818 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -79,7 +79,7 @@ fn parse_suffix(s: &str) -> Result<(f64, Option)> { Some('Y') => Some((RawSuffix::Y, with_i)), Some('0'..='9') => None, _ => return Err(format!("invalid suffix in input: '{}'", s)), - }; + }; let suffix_len = match suffix { None => 0, From 0531153fa6331407093474753d5178994e7d1895 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 15 Jun 2021 15:35:02 +0200 Subject: [PATCH 289/320] uutils: move clap::App creation to separate functions --- Cargo.lock | 7 + src/uu/arch/src/arch.rs | 13 +- src/uu/base32/src/base32.rs | 5 + src/uu/base32/src/base_common.rs | 17 +- src/uu/base64/src/base64.rs | 1 + src/uu/basename/src/basename.rs | 52 ++--- src/uu/cat/src/cat.rs | 115 +++++----- src/uu/chgrp/src/chgrp.rs | 159 +++++++------- src/uu/chmod/src/chmod.rs | 108 ++++----- src/uu/chown/src/chown.rs | 192 ++++++++-------- src/uu/chroot/src/chroot.rs | 99 +++++---- src/uu/cksum/src/cksum.rs | 17 +- src/uu/comm/src/comm.rs | 22 +- src/uu/cp/src/cp.rs | 18 +- src/uu/csplit/src/csplit.rs | 56 ++--- src/uu/cut/src/cut.rs | 167 +++++++------- src/uu/date/src/date.rs | 132 +++++------ src/uu/df/src/df.rs | 230 +++++++++---------- src/uu/dircolors/src/dircolors.rs | 62 +++--- src/uu/dirname/src/dirname.rs | 24 +- src/uu/du/src/du.rs | 354 +++++++++++++++--------------- src/uu/echo/src/echo.rs | 37 ++-- src/uu/env/src/env.rs | 4 +- src/uu/expand/src/expand.rs | 14 +- src/uu/expr/Cargo.toml | 1 + src/uu/expr/src/expr.rs | 13 +- src/uu/factor/src/cli.rs | 13 +- src/uu/false/Cargo.toml | 1 + src/uu/false/src/false.rs | 9 + src/uu/fmt/src/fmt.rs | 248 ++++++++++----------- src/uu/fold/src/fold.rs | 55 ++--- src/uu/groups/src/groups.rs | 24 +- src/uu/hashsum/src/hashsum.rs | 232 ++++++++++---------- src/uu/head/src/head.rs | 4 +- src/uu/hostid/Cargo.toml | 1 + src/uu/hostid/src/hostid.rs | 15 +- src/uu/hostname/src/hostname.rs | 32 +-- src/uu/id/src/id.rs | 200 ++++++++--------- src/uu/install/src/install.rs | 52 ++--- src/uu/join/src/join.rs | 129 +++++------ src/uu/kill/src/kill.rs | 66 +++--- src/uu/link/src/link.rs | 28 +-- src/uu/ln/src/ln.rs | 122 +++++----- src/uu/logname/src/logname.rs | 12 +- src/uu/ls/src/ls.rs | 27 ++- src/uu/mkdir/src/mkdir.rs | 56 ++--- src/uu/mkfifo/src/mkfifo.rs | 45 ++-- src/uu/mknod/src/mknod.rs | 87 ++++---- src/uu/mktemp/src/mktemp.rs | 112 +++++----- src/uu/more/src/more.rs | 83 +++---- src/uu/mv/src/mv.rs | 104 +++++---- src/uu/nice/src/nice.rs | 30 +-- src/uu/nl/src/nl.rs | 109 ++++----- src/uu/nohup/src/nohup.rs | 28 +-- src/uu/nproc/src/nproc.rs | 38 ++-- src/uu/numfmt/src/numfmt.rs | 38 ++-- src/uu/od/src/od.rs | 76 ++++--- src/uu/paste/src/paste.rs | 29 +-- src/uu/pathchk/src/pathchk.rs | 44 ++-- src/uu/pinky/src/pinky.rs | 112 +++++----- src/uu/pr/Cargo.toml | 1 + src/uu/pr/src/pr.rs | 6 + src/uu/printenv/src/printenv.rs | 36 +-- src/uu/printf/Cargo.toml | 1 + src/uu/printf/src/printf.rs | 16 +- src/uu/ptx/src/ptx.rs | 41 ++-- src/uu/pwd/src/pwd.rs | 36 +-- src/uu/readlink/src/readlink.rs | 128 +++++------ src/uu/realpath/src/realpath.rs | 52 ++--- src/uu/relpath/src/relpath.rs | 42 ++-- src/uu/rm/src/rm.rs | 124 ++++++----- src/uu/rmdir/src/rmdir.rs | 40 ++-- src/uu/seq/src/seq.rs | 74 ++++--- src/uu/shred/src/shred.rs | 115 +++++----- src/uu/shuf/src/shuf.rs | 117 +++++----- src/uu/sleep/src/sleep.rs | 22 +- src/uu/sort/src/sort.rs | 322 +++++++++++++-------------- src/uu/split/src/split.rs | 157 ++++++------- src/uu/stat/src/stat.rs | 28 ++- src/uu/stdbuf/src/stdbuf.rs | 62 +++--- src/uu/sum/src/sum.rs | 39 ++-- src/uu/sync/src/sync.rs | 42 ++-- src/uu/tac/src/tac.rs | 47 ++-- src/uu/tail/src/tail.rs | 140 ++++++------ src/uu/tee/src/tee.rs | 40 ++-- src/uu/test/Cargo.toml | 1 + src/uu/test/src/test.rs | 8 + src/uu/timeout/src/timeout.rs | 35 +-- src/uu/touch/src/touch.rs | 150 ++++++------- src/uu/tr/src/tr.rs | 80 +++---- src/uu/true/Cargo.toml | 1 + src/uu/true/src/true.rs | 9 + src/uu/truncate/src/truncate.rs | 78 +++---- src/uu/tsort/src/tsort.rs | 23 +- src/uu/tty/src/tty.rs | 28 +-- src/uu/uname/src/uname.rs | 88 ++++---- src/uu/unexpand/src/unexpand.rs | 15 +- src/uu/uniq/src/uniq.rs | 84 +++---- src/uu/unlink/src/unlink.rs | 14 +- src/uu/uptime/src/uptime.rs | 24 +- src/uu/users/src/users.rs | 12 +- src/uu/wc/src/wc.rs | 60 ++--- src/uu/who/src/who.rs | 190 ++++++++-------- src/uu/whoami/src/whoami.rs | 8 +- src/uu/yes/src/yes.rs | 8 +- 105 files changed, 3571 insertions(+), 3253 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a059c1cd5..5f96f7f8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1959,6 +1959,7 @@ dependencies = [ name = "uu_expr" version = "0.0.6" dependencies = [ + "clap", "libc", "num-bigint", "num-traits", @@ -1986,6 +1987,7 @@ dependencies = [ name = "uu_false" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] @@ -2051,6 +2053,7 @@ dependencies = [ name = "uu_hostid" version = "0.0.6" dependencies = [ + "clap", "libc", "uucore", "uucore_procs", @@ -2327,6 +2330,7 @@ name = "uu_pr" version = "0.0.6" dependencies = [ "chrono", + "clap", "getopts", "itertools 0.10.0", "quick-error 2.0.1", @@ -2349,6 +2353,7 @@ dependencies = [ name = "uu_printf" version = "0.0.6" dependencies = [ + "clap", "itertools 0.8.2", "uucore", "uucore_procs", @@ -2585,6 +2590,7 @@ dependencies = [ name = "uu_test" version = "0.0.6" dependencies = [ + "clap", "libc", "redox_syscall 0.1.57", "uucore", @@ -2628,6 +2634,7 @@ dependencies = [ name = "uu_true" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index eddd24502..955e57389 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -17,13 +17,16 @@ static ABOUT: &str = "Display machine architecture"; static SUMMARY: &str = "Determine architecture name for current machine."; pub fn uumain(args: impl uucore::Args) -> i32 { - App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .after_help(SUMMARY) - .get_matches_from(args); + uu_app().get_matches_from(args); let uts = return_if_err!(1, PlatformInfo::new()); println!("{}", uts.machine().trim()); 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .after_help(SUMMARY) +} diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index e6a01cb34..9a29717ac 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -10,6 +10,7 @@ extern crate uucore; use std::io::{stdin, Read}; +use clap::App; use uucore::encoding::Format; pub mod base_common; @@ -56,3 +57,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } + +pub fn uu_app() -> App<'static, 'static> { + base_common::base_app(executable!(), VERSION, ABOUT) +} diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index a606351ce..4fc8b495b 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -78,10 +78,17 @@ pub fn parse_base_cmd_args( about: &str, usage: &str, ) -> Result { - let app = App::new(name) + let app = base_app(name, version, about).usage(usage); + let arg_list = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); + Config::from(app.get_matches_from(arg_list)) +} + +pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static, 'a> { + App::new(name) .version(version) .about(about) - .usage(usage) // Format arguments. .arg( Arg::with_name(options::DECODE) @@ -106,11 +113,7 @@ pub fn parse_base_cmd_args( ) // "multiple" arguments are used to check whether there is more than one // file passed in. - .arg(Arg::with_name(options::FILE).index(1).multiple(true)); - let arg_list = args - .collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(); - Config::from(app.get_matches_from(arg_list)) + .arg(Arg::with_name(options::FILE).index(1).multiple(true)) } pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box { diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 0dd831027..71ed44e6e 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -10,6 +10,7 @@ extern crate uucore; use uu_base32::base_common; +pub use uu_base32::uu_app; use uucore::encoding::Format; diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 098a3e2b2..5450ee3f2 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -40,31 +40,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // // Argument parsing // - let matches = App::new(executable!()) - .version(crate_version!()) - .about(SUMMARY) - .usage(&usage[..]) - .arg( - Arg::with_name(options::MULTIPLE) - .short("a") - .long(options::MULTIPLE) - .help("support multiple arguments and treat each as a NAME"), - ) - .arg(Arg::with_name(options::NAME).multiple(true).hidden(true)) - .arg( - Arg::with_name(options::SUFFIX) - .short("s") - .long(options::SUFFIX) - .value_name("SUFFIX") - .help("remove a trailing SUFFIX; implies -a"), - ) - .arg( - Arg::with_name(options::ZERO) - .short("z") - .long(options::ZERO) - .help("end each output line with NUL, not newline"), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); // too few arguments if !matches.is_present(options::NAME) { @@ -116,6 +92,32 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(SUMMARY) + .arg( + Arg::with_name(options::MULTIPLE) + .short("a") + .long(options::MULTIPLE) + .help("support multiple arguments and treat each as a NAME"), + ) + .arg(Arg::with_name(options::NAME).multiple(true).hidden(true)) + .arg( + Arg::with_name(options::SUFFIX) + .short("s") + .long(options::SUFFIX) + .value_name("SUFFIX") + .help("remove a trailing SUFFIX; implies -a"), + ) + .arg( + Arg::with_name(options::ZERO) + .short("z") + .long(options::ZERO) + .help("end each output line with NUL, not newline"), + ) +} + fn basename(fullname: &str, suffix: &str) -> String { // Remove all platform-specific path separators from the end let path = fullname.trim_end_matches(is_separator); diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 889ba424a..35a5308ed 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -169,7 +169,65 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) + let matches = uu_app().get_matches_from(args); + + let number_mode = if matches.is_present(options::NUMBER_NONBLANK) { + NumberingMode::NonEmpty + } else if matches.is_present(options::NUMBER) { + NumberingMode::All + } else { + NumberingMode::None + }; + + let show_nonprint = vec![ + options::SHOW_ALL.to_owned(), + options::SHOW_NONPRINTING_ENDS.to_owned(), + options::SHOW_NONPRINTING_TABS.to_owned(), + options::SHOW_NONPRINTING.to_owned(), + ] + .iter() + .any(|v| matches.is_present(v)); + + let show_ends = vec![ + options::SHOW_ENDS.to_owned(), + options::SHOW_ALL.to_owned(), + options::SHOW_NONPRINTING_ENDS.to_owned(), + ] + .iter() + .any(|v| matches.is_present(v)); + + let show_tabs = vec![ + options::SHOW_ALL.to_owned(), + options::SHOW_TABS.to_owned(), + options::SHOW_NONPRINTING_TABS.to_owned(), + ] + .iter() + .any(|v| matches.is_present(v)); + + let squeeze_blank = matches.is_present(options::SQUEEZE_BLANK); + let files: Vec = match matches.values_of(options::FILE) { + Some(v) => v.clone().map(|v| v.to_owned()).collect(), + None => vec!["-".to_owned()], + }; + + let options = OutputOptions { + show_ends, + number: number_mode, + show_nonprint, + show_tabs, + squeeze_blank, + }; + let success = cat_files(files, &options).is_ok(); + + if success { + 0 + } else { + 1 + } +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .name(NAME) .version(crate_version!()) .usage(SYNTAX) @@ -229,61 +287,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::SHOW_NONPRINTING) .help("use ^ and M- notation, except for LF (\\n) and TAB (\\t)"), ) - .get_matches_from(args); - - let number_mode = if matches.is_present(options::NUMBER_NONBLANK) { - NumberingMode::NonEmpty - } else if matches.is_present(options::NUMBER) { - NumberingMode::All - } else { - NumberingMode::None - }; - - let show_nonprint = vec![ - options::SHOW_ALL.to_owned(), - options::SHOW_NONPRINTING_ENDS.to_owned(), - options::SHOW_NONPRINTING_TABS.to_owned(), - options::SHOW_NONPRINTING.to_owned(), - ] - .iter() - .any(|v| matches.is_present(v)); - - let show_ends = vec![ - options::SHOW_ENDS.to_owned(), - options::SHOW_ALL.to_owned(), - options::SHOW_NONPRINTING_ENDS.to_owned(), - ] - .iter() - .any(|v| matches.is_present(v)); - - let show_tabs = vec![ - options::SHOW_ALL.to_owned(), - options::SHOW_TABS.to_owned(), - options::SHOW_NONPRINTING_TABS.to_owned(), - ] - .iter() - .any(|v| matches.is_present(v)); - - let squeeze_blank = matches.is_present(options::SQUEEZE_BLANK); - let files: Vec = match matches.values_of(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), - None => vec!["-".to_owned()], - }; - - let options = OutputOptions { - show_ends, - number: number_mode, - show_nonprint, - show_tabs, - squeeze_blank, - }; - let success = cat_files(files, &options).is_ok(); - - if success { - 0 - } else { - 1 - } } fn cat_handle( diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 454a0386c..489be59eb 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -73,84 +73,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let mut app = App::new(executable!()) - .version(VERSION) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::verbosity::CHANGES) - .short("c") - .long(options::verbosity::CHANGES) - .help("like verbose but report only when a change is made"), - ) - .arg( - Arg::with_name(options::verbosity::SILENT) - .short("f") - .long(options::verbosity::SILENT), - ) - .arg( - Arg::with_name(options::verbosity::QUIET) - .long(options::verbosity::QUIET) - .help("suppress most error messages"), - ) - .arg( - Arg::with_name(options::verbosity::VERBOSE) - .short("v") - .long(options::verbosity::VERBOSE) - .help("output a diagnostic for every file processed"), - ) - .arg( - Arg::with_name(options::dereference::DEREFERENCE) - .long(options::dereference::DEREFERENCE), - ) - .arg( - Arg::with_name(options::dereference::NO_DEREFERENCE) - .short("h") - .long(options::dereference::NO_DEREFERENCE) - .help( - "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", - ), - ) - .arg( - Arg::with_name(options::preserve_root::PRESERVE) - .long(options::preserve_root::PRESERVE) - .help("fail to operate recursively on '/'"), - ) - .arg( - Arg::with_name(options::preserve_root::NO_PRESERVE) - .long(options::preserve_root::NO_PRESERVE) - .help("do not treat '/' specially (the default)"), - ) - .arg( - Arg::with_name(options::REFERENCE) - .long(options::REFERENCE) - .value_name("RFILE") - .help("use RFILE's group rather than specifying GROUP values") - .takes_value(true) - .multiple(false), - ) - .arg( - Arg::with_name(options::RECURSIVE) - .short("R") - .long(options::RECURSIVE) - .help("operate on files and directories recursively"), - ) - .arg( - Arg::with_name(options::traverse::TRAVERSE) - .short(options::traverse::TRAVERSE) - .help("if a command line argument is a symbolic link to a directory, traverse it"), - ) - .arg( - Arg::with_name(options::traverse::NO_TRAVERSE) - .short(options::traverse::NO_TRAVERSE) - .help("do not traverse any symbolic links (default)") - .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]), - ) - .arg( - Arg::with_name(options::traverse::EVERY) - .short(options::traverse::EVERY) - .help("traverse every symbolic link to a directory encountered"), - ); + let mut app = uu_app().usage(&usage[..]); // we change the positional args based on whether // --reference was used. @@ -274,6 +197,86 @@ pub fn uumain(args: impl uucore::Args) -> i32 { executor.exec() } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .arg( + Arg::with_name(options::verbosity::CHANGES) + .short("c") + .long(options::verbosity::CHANGES) + .help("like verbose but report only when a change is made"), + ) + .arg( + Arg::with_name(options::verbosity::SILENT) + .short("f") + .long(options::verbosity::SILENT), + ) + .arg( + Arg::with_name(options::verbosity::QUIET) + .long(options::verbosity::QUIET) + .help("suppress most error messages"), + ) + .arg( + Arg::with_name(options::verbosity::VERBOSE) + .short("v") + .long(options::verbosity::VERBOSE) + .help("output a diagnostic for every file processed"), + ) + .arg( + Arg::with_name(options::dereference::DEREFERENCE) + .long(options::dereference::DEREFERENCE), + ) + .arg( + Arg::with_name(options::dereference::NO_DEREFERENCE) + .short("h") + .long(options::dereference::NO_DEREFERENCE) + .help( + "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", + ), + ) + .arg( + Arg::with_name(options::preserve_root::PRESERVE) + .long(options::preserve_root::PRESERVE) + .help("fail to operate recursively on '/'"), + ) + .arg( + Arg::with_name(options::preserve_root::NO_PRESERVE) + .long(options::preserve_root::NO_PRESERVE) + .help("do not treat '/' specially (the default)"), + ) + .arg( + Arg::with_name(options::REFERENCE) + .long(options::REFERENCE) + .value_name("RFILE") + .help("use RFILE's group rather than specifying GROUP values") + .takes_value(true) + .multiple(false), + ) + .arg( + Arg::with_name(options::RECURSIVE) + .short("R") + .long(options::RECURSIVE) + .help("operate on files and directories recursively"), + ) + .arg( + Arg::with_name(options::traverse::TRAVERSE) + .short(options::traverse::TRAVERSE) + .help("if a command line argument is a symbolic link to a directory, traverse it"), + ) + .arg( + Arg::with_name(options::traverse::NO_TRAVERSE) + .short(options::traverse::NO_TRAVERSE) + .help("do not traverse any symbolic links (default)") + .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]), + ) + .arg( + Arg::with_name(options::traverse::EVERY) + .short(options::traverse::EVERY) + .help("traverse every symbolic link to a directory encountered"), + ) +} + struct Chgrper { dest_gid: gid_t, bit_flag: u8, diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 2d5787099..d89827c97 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -61,11 +61,64 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&after_help[..]) + .get_matches_from(args); + + let changes = matches.is_present(options::CHANGES); + let quiet = matches.is_present(options::QUIET); + let verbose = matches.is_present(options::VERBOSE); + let preserve_root = matches.is_present(options::PRESERVE_ROOT); + let recursive = matches.is_present(options::RECURSIVE); + let fmode = matches + .value_of(options::REFERENCE) + .and_then(|fref| match fs::metadata(fref) { + Ok(meta) => Some(meta.mode()), + Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err), + }); + let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required + let cmode = if mode_had_minus_prefix { + // clap parsing is finished, now put prefix back + format!("-{}", modes) + } else { + modes.to_string() + }; + let mut files: Vec = matches + .values_of(options::FILE) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + let cmode = if fmode.is_some() { + // "--reference" and MODE are mutually exclusive + // if "--reference" was used MODE needs to be interpreted as another FILE + // it wasn't possible to implement this behavior directly with clap + files.push(cmode); + None + } else { + Some(cmode) + }; + + let chmoder = Chmoder { + changes, + quiet, + verbose, + preserve_root, + recursive, + fmode, + cmode, + }; + match chmoder.chmod(files) { + Ok(()) => {} + Err(e) => return e, + } + + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) .arg( Arg::with_name(options::CHANGES) .long(options::CHANGES) @@ -120,55 +173,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .required_unless(options::MODE) .multiple(true), ) - .get_matches_from(args); - - let changes = matches.is_present(options::CHANGES); - let quiet = matches.is_present(options::QUIET); - let verbose = matches.is_present(options::VERBOSE); - let preserve_root = matches.is_present(options::PRESERVE_ROOT); - let recursive = matches.is_present(options::RECURSIVE); - let fmode = matches - .value_of(options::REFERENCE) - .and_then(|fref| match fs::metadata(fref) { - Ok(meta) => Some(meta.mode()), - Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err), - }); - let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required - let cmode = if mode_had_minus_prefix { - // clap parsing is finished, now put prefix back - format!("-{}", modes) - } else { - modes.to_string() - }; - let mut files: Vec = matches - .values_of(options::FILE) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - let cmode = if fmode.is_some() { - // "--reference" and MODE are mutually exclusive - // if "--reference" was used MODE needs to be interpreted as another FILE - // it wasn't possible to implement this behavior directly with clap - files.push(cmode); - None - } else { - Some(cmode) - }; - - let chmoder = Chmoder { - changes, - quiet, - verbose, - preserve_root, - recursive, - fmode, - cmode, - }; - match chmoder.chmod(files) { - Ok(()) => {} - Err(e) => return e, - } - - 0 } // Iterate 'args' and delete the first occurrence diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 7fc7f04d3..e1d3ff22b 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -73,101 +73,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::verbosity::CHANGES) - .short("c") - .long(options::verbosity::CHANGES) - .help("like verbose but report only when a change is made"), - ) - .arg(Arg::with_name(options::dereference::DEREFERENCE).long(options::dereference::DEREFERENCE).help( - "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself", - )) - .arg( - Arg::with_name(options::dereference::NO_DEREFERENCE) - .short("h") - .long(options::dereference::NO_DEREFERENCE) - .help( - "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", - ), - ) - .arg( - Arg::with_name(options::FROM) - .long(options::FROM) - .help( - "change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute", - ) - .value_name("CURRENT_OWNER:CURRENT_GROUP"), - ) - .arg( - Arg::with_name(options::preserve_root::PRESERVE) - .long(options::preserve_root::PRESERVE) - .help("fail to operate recursively on '/'"), - ) - .arg( - Arg::with_name(options::preserve_root::NO_PRESERVE) - .long(options::preserve_root::NO_PRESERVE) - .help("do not treat '/' specially (the default)"), - ) - .arg( - Arg::with_name(options::verbosity::QUIET) - .long(options::verbosity::QUIET) - .help("suppress most error messages"), - ) - .arg( - Arg::with_name(options::RECURSIVE) - .short("R") - .long(options::RECURSIVE) - .help("operate on files and directories recursively"), - ) - .arg( - Arg::with_name(options::REFERENCE) - .long(options::REFERENCE) - .help("use RFILE's owner and group rather than specifying OWNER:GROUP values") - .value_name("RFILE") - .min_values(1), - ) - .arg(Arg::with_name(options::verbosity::SILENT).short("f").long(options::verbosity::SILENT)) - .arg( - Arg::with_name(options::traverse::TRAVERSE) - .short(options::traverse::TRAVERSE) - .help("if a command line argument is a symbolic link to a directory, traverse it") - .overrides_with_all(&[options::traverse::EVERY, options::traverse::NO_TRAVERSE]), - ) - .arg( - Arg::with_name(options::traverse::EVERY) - .short(options::traverse::EVERY) - .help("traverse every symbolic link to a directory encountered") - .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::NO_TRAVERSE]), - ) - .arg( - Arg::with_name(options::traverse::NO_TRAVERSE) - .short(options::traverse::NO_TRAVERSE) - .help("do not traverse any symbolic links (default)") - .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]), - ) - .arg( - Arg::with_name(options::verbosity::VERBOSE) - .long(options::verbosity::VERBOSE) - .help("output a diagnostic for every file processed"), - ) - .arg( - Arg::with_name(ARG_OWNER) - .multiple(false) - .takes_value(true) - .required(true), - ) - .arg( - Arg::with_name(ARG_FILES) - .multiple(true) - .takes_value(true) - .required(true) - .min_values(1), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); /* First arg is the owner/group */ let owner = matches.value_of(ARG_OWNER).unwrap(); @@ -273,6 +179,102 @@ pub fn uumain(args: impl uucore::Args) -> i32 { executor.exec() } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::verbosity::CHANGES) + .short("c") + .long(options::verbosity::CHANGES) + .help("like verbose but report only when a change is made"), + ) + .arg(Arg::with_name(options::dereference::DEREFERENCE).long(options::dereference::DEREFERENCE).help( + "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself", + )) + .arg( + Arg::with_name(options::dereference::NO_DEREFERENCE) + .short("h") + .long(options::dereference::NO_DEREFERENCE) + .help( + "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", + ), + ) + .arg( + Arg::with_name(options::FROM) + .long(options::FROM) + .help( + "change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute", + ) + .value_name("CURRENT_OWNER:CURRENT_GROUP"), + ) + .arg( + Arg::with_name(options::preserve_root::PRESERVE) + .long(options::preserve_root::PRESERVE) + .help("fail to operate recursively on '/'"), + ) + .arg( + Arg::with_name(options::preserve_root::NO_PRESERVE) + .long(options::preserve_root::NO_PRESERVE) + .help("do not treat '/' specially (the default)"), + ) + .arg( + Arg::with_name(options::verbosity::QUIET) + .long(options::verbosity::QUIET) + .help("suppress most error messages"), + ) + .arg( + Arg::with_name(options::RECURSIVE) + .short("R") + .long(options::RECURSIVE) + .help("operate on files and directories recursively"), + ) + .arg( + Arg::with_name(options::REFERENCE) + .long(options::REFERENCE) + .help("use RFILE's owner and group rather than specifying OWNER:GROUP values") + .value_name("RFILE") + .min_values(1), + ) + .arg(Arg::with_name(options::verbosity::SILENT).short("f").long(options::verbosity::SILENT)) + .arg( + Arg::with_name(options::traverse::TRAVERSE) + .short(options::traverse::TRAVERSE) + .help("if a command line argument is a symbolic link to a directory, traverse it") + .overrides_with_all(&[options::traverse::EVERY, options::traverse::NO_TRAVERSE]), + ) + .arg( + Arg::with_name(options::traverse::EVERY) + .short(options::traverse::EVERY) + .help("traverse every symbolic link to a directory encountered") + .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::NO_TRAVERSE]), + ) + .arg( + Arg::with_name(options::traverse::NO_TRAVERSE) + .short(options::traverse::NO_TRAVERSE) + .help("do not traverse any symbolic links (default)") + .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]), + ) + .arg( + Arg::with_name(options::verbosity::VERBOSE) + .long(options::verbosity::VERBOSE) + .help("output a diagnostic for every file processed"), + ) + .arg( + Arg::with_name(ARG_OWNER) + .multiple(false) + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name(ARG_FILES) + .multiple(true) + .takes_value(true) + .required(true) + .min_values(1), + ) +} + fn parse_spec(spec: &str) -> Result<(Option, Option), String> { let args = spec.split_terminator(':').collect::>(); let usr_only = args.len() == 1 && !args[0].is_empty(); diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 86d4a4900..2c0f8522c 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -36,54 +36,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(SYNTAX) - .arg( - Arg::with_name(options::NEWROOT) - .hidden(true) - .required(true) - .index(1), - ) - .arg( - Arg::with_name(options::USER) - .short("u") - .long(options::USER) - .help("User (ID or name) to switch before running the program") - .value_name("USER"), - ) - .arg( - Arg::with_name(options::GROUP) - .short("g") - .long(options::GROUP) - .help("Group (ID or name) to switch to") - .value_name("GROUP"), - ) - .arg( - Arg::with_name(options::GROUPS) - .short("G") - .long(options::GROUPS) - .help("Comma-separated list of groups to switch to") - .value_name("GROUP1,GROUP2..."), - ) - .arg( - Arg::with_name(options::USERSPEC) - .long(options::USERSPEC) - .help( - "Colon-separated user and group to switch to. \ - Same as -u USER -g GROUP. \ - Userspec has higher preference than -u and/or -g", - ) - .value_name("USER:GROUP"), - ) - .arg( - Arg::with_name(options::COMMAND) - .hidden(true) - .multiple(true) - .index(2), - ) - .get_matches_from(args); + let matches = uu_app().get_matches_from(args); let default_shell: &'static str = "/bin/sh"; let default_option: &'static str = "-i"; @@ -138,6 +91,56 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .usage(SYNTAX) + .arg( + Arg::with_name(options::NEWROOT) + .hidden(true) + .required(true) + .index(1), + ) + .arg( + Arg::with_name(options::USER) + .short("u") + .long(options::USER) + .help("User (ID or name) to switch before running the program") + .value_name("USER"), + ) + .arg( + Arg::with_name(options::GROUP) + .short("g") + .long(options::GROUP) + .help("Group (ID or name) to switch to") + .value_name("GROUP"), + ) + .arg( + Arg::with_name(options::GROUPS) + .short("G") + .long(options::GROUPS) + .help("Comma-separated list of groups to switch to") + .value_name("GROUP1,GROUP2..."), + ) + .arg( + Arg::with_name(options::USERSPEC) + .long(options::USERSPEC) + .help( + "Colon-separated user and group to switch to. \ + Same as -u USER -g GROUP. \ + Userspec has higher preference than -u and/or -g", + ) + .value_name("USER:GROUP"), + ) + .arg( + Arg::with_name(options::COMMAND) + .hidden(true) + .multiple(true) + .index(2), + ) +} + fn set_context(root: &Path, options: &clap::ArgMatches) { let userspec_str = options.value_of(options::USERSPEC); let user_str = options.value_of(options::USER).unwrap_or_default(); diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 6a812c186..e88cc78b3 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -180,13 +180,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .about(SUMMARY) - .usage(SYNTAX) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .get_matches_from(args); + let matches = uu_app().get_matches_from(args); let files: Vec = match matches.values_of(options::FILE) { Some(v) => v.clone().map(|v| v.to_owned()).collect(), @@ -217,3 +211,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { exit_code } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .name(NAME) + .version(crate_version!()) + .about(SUMMARY) + .usage(SYNTAX) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) +} diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 7a6086bb5..aa10432a2 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -137,10 +137,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + let mut f1 = open_file(matches.value_of(options::FILE_1).unwrap()).unwrap(); + let mut f2 = open_file(matches.value_of(options::FILE_2).unwrap()).unwrap(); + + comm(&mut f1, &mut f2, &matches); + + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .after_help(LONG_HELP) .arg( Arg::with_name(options::COLUMN_1) @@ -167,12 +177,4 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) .arg(Arg::with_name(options::FILE_1).required(true)) .arg(Arg::with_name(options::FILE_2).required(true)) - .get_matches_from(args); - - let mut f1 = open_file(matches.value_of(options::FILE_1).unwrap()).unwrap(); - let mut f2 = open_file(matches.value_of(options::FILE_2).unwrap()).unwrap(); - - comm(&mut f1, &mut f2, &matches); - - 0 } diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index e702179ec..12dfeab3f 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -290,13 +290,10 @@ static DEFAULT_ATTRIBUTES: &[Attribute] = &[ Attribute::Timestamps, ]; -pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); - let matches = App::new(executable!()) +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP)) - .usage(&usage[..]) .arg(Arg::with_name(options::TARGET_DIRECTORY) .short("t") .conflicts_with(options::NO_TARGET_DIRECTORY) @@ -464,6 +461,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg(Arg::with_name(options::PATHS) .multiple(true)) +} + +pub fn uumain(args: impl uucore::Args) -> i32 { + let usage = get_usage(); + let matches = uu_app() + .after_help(&*format!( + "{}\n{}", + LONG_HELP, + backup_control::BACKUP_CONTROL_LONG_HELP + )) + .usage(&usage[..]) .get_matches_from(args); let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches)); diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index d69254a3a..048ec80d8 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -711,10 +711,37 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + // get the file to split + let file_name = matches.value_of(options::FILE).unwrap(); + + // get the patterns to split on + let patterns: Vec = matches + .values_of(options::PATTERN) + .unwrap() + .map(str::to_string) + .collect(); + let patterns = return_if_err!(1, patterns::get_patterns(&patterns[..])); + let options = CsplitOptions::new(&matches); + if file_name == "-" { + let stdin = io::stdin(); + crash_if_err!(1, csplit(&options, patterns, stdin.lock())); + } else { + let file = return_if_err!(1, File::open(file_name)); + let file_metadata = return_if_err!(1, file.metadata()); + if !file_metadata.is_file() { + crash!(1, "'{}' is not a regular file", file_name); + } + crash_if_err!(1, csplit(&options, patterns, BufReader::new(file))); + }; + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(SUMMARY) - .usage(&usage[..]) .arg( Arg::with_name(options::SUFFIX_FORMAT) .short("b") @@ -768,29 +795,4 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .required(true), ) .after_help(LONG_HELP) - .get_matches_from(args); - - // get the file to split - let file_name = matches.value_of(options::FILE).unwrap(); - - // get the patterns to split on - let patterns: Vec = matches - .values_of(options::PATTERN) - .unwrap() - .map(str::to_string) - .collect(); - let patterns = return_if_err!(1, patterns::get_patterns(&patterns[..])); - let options = CsplitOptions::new(&matches); - if file_name == "-" { - let stdin = io::stdin(); - crash_if_err!(1, csplit(&options, patterns, stdin.lock())); - } else { - let file = return_if_err!(1, File::open(file_name)); - let file_metadata = return_if_err!(1, file.metadata()); - if !file_metadata.is_file() { - crash!(1, "'{}' is not a regular file", file_name); - } - crash_if_err!(1, csplit(&options, patterns, BufReader::new(file))); - }; - 0 } diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 6602b1eb1..e33b8a2fe 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -396,88 +396,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(SYNTAX) - .about(SUMMARY) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::BYTES) - .short("b") - .long(options::BYTES) - .takes_value(true) - .help("filter byte columns from the input source") - .allow_hyphen_values(true) - .value_name("LIST") - .display_order(1), - ) - .arg( - Arg::with_name(options::CHARACTERS) - .short("c") - .long(options::CHARACTERS) - .help("alias for character mode") - .takes_value(true) - .allow_hyphen_values(true) - .value_name("LIST") - .display_order(2), - ) - .arg( - Arg::with_name(options::DELIMITER) - .short("d") - .long(options::DELIMITER) - .help("specify the delimiter character that separates fields in the input source. Defaults to Tab.") - .takes_value(true) - .value_name("DELIM") - .display_order(3), - ) - .arg( - Arg::with_name(options::FIELDS) - .short("f") - .long(options::FIELDS) - .help("filter field columns from the input source") - .takes_value(true) - .allow_hyphen_values(true) - .value_name("LIST") - .display_order(4), - ) - .arg( - Arg::with_name(options::COMPLEMENT) - .long(options::COMPLEMENT) - .help("invert the filter - instead of displaying only the filtered columns, display all but those columns") - .takes_value(false) - .display_order(5), - ) - .arg( - Arg::with_name(options::ONLY_DELIMITED) - .short("s") - .long(options::ONLY_DELIMITED) - .help("in field mode, only print lines which contain the delimiter") - .takes_value(false) - .display_order(6), - ) - .arg( - Arg::with_name(options::ZERO_TERMINATED) - .short("z") - .long(options::ZERO_TERMINATED) - .help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)") - .takes_value(false) - .display_order(8), - ) - .arg( - Arg::with_name(options::OUTPUT_DELIMITER) - .long(options::OUTPUT_DELIMITER) - .help("in field mode, replace the delimiter in output lines with this option's argument") - .takes_value(true) - .value_name("NEW_DELIM") - .display_order(7), - ) - .arg( - Arg::with_name(options::FILE) - .hidden(true) - .multiple(true) - ) - .get_matches_from(args); + let matches = uu_app().get_matches_from(args); let complement = matches.is_present(options::COMPLEMENT); @@ -627,3 +546,87 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .name(NAME) + .version(crate_version!()) + .usage(SYNTAX) + .about(SUMMARY) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::BYTES) + .short("b") + .long(options::BYTES) + .takes_value(true) + .help("filter byte columns from the input source") + .allow_hyphen_values(true) + .value_name("LIST") + .display_order(1), + ) + .arg( + Arg::with_name(options::CHARACTERS) + .short("c") + .long(options::CHARACTERS) + .help("alias for character mode") + .takes_value(true) + .allow_hyphen_values(true) + .value_name("LIST") + .display_order(2), + ) + .arg( + Arg::with_name(options::DELIMITER) + .short("d") + .long(options::DELIMITER) + .help("specify the delimiter character that separates fields in the input source. Defaults to Tab.") + .takes_value(true) + .value_name("DELIM") + .display_order(3), + ) + .arg( + Arg::with_name(options::FIELDS) + .short("f") + .long(options::FIELDS) + .help("filter field columns from the input source") + .takes_value(true) + .allow_hyphen_values(true) + .value_name("LIST") + .display_order(4), + ) + .arg( + Arg::with_name(options::COMPLEMENT) + .long(options::COMPLEMENT) + .help("invert the filter - instead of displaying only the filtered columns, display all but those columns") + .takes_value(false) + .display_order(5), + ) + .arg( + Arg::with_name(options::ONLY_DELIMITED) + .short("s") + .long(options::ONLY_DELIMITED) + .help("in field mode, only print lines which contain the delimiter") + .takes_value(false) + .display_order(6), + ) + .arg( + Arg::with_name(options::ZERO_TERMINATED) + .short("z") + .long(options::ZERO_TERMINATED) + .help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)") + .takes_value(false) + .display_order(8), + ) + .arg( + Arg::with_name(options::OUTPUT_DELIMITER) + .long(options::OUTPUT_DELIMITER) + .help("in field mode, replace the delimiter in output lines with this option's argument") + .takes_value(true) + .value_name("NEW_DELIM") + .display_order(7), + ) + .arg( + Arg::with_name(options::FILE) + .hidden(true) + .multiple(true) + ) +} diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 11c3eb31f..0071b5e8c 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -142,71 +142,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { {0} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]", NAME ); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&syntax[..]) - .arg( - Arg::with_name(OPT_DATE) - .short("d") - .long(OPT_DATE) - .takes_value(true) - .help("display time described by STRING, not 'now'"), - ) - .arg( - Arg::with_name(OPT_FILE) - .short("f") - .long(OPT_FILE) - .takes_value(true) - .help("like --date; once for each line of DATEFILE"), - ) - .arg( - Arg::with_name(OPT_ISO_8601) - .short("I") - .long(OPT_ISO_8601) - .takes_value(true) - .help(ISO_8601_HELP_STRING), - ) - .arg( - Arg::with_name(OPT_RFC_EMAIL) - .short("R") - .long(OPT_RFC_EMAIL) - .help(RFC_5322_HELP_STRING), - ) - .arg( - Arg::with_name(OPT_RFC_3339) - .long(OPT_RFC_3339) - .takes_value(true) - .help(RFC_3339_HELP_STRING), - ) - .arg( - Arg::with_name(OPT_DEBUG) - .long(OPT_DEBUG) - .help("annotate the parsed date, and warn about questionable usage to stderr"), - ) - .arg( - Arg::with_name(OPT_REFERENCE) - .short("r") - .long(OPT_REFERENCE) - .takes_value(true) - .help("display the last modification time of FILE"), - ) - .arg( - Arg::with_name(OPT_SET) - .short("s") - .long(OPT_SET) - .takes_value(true) - .help(OPT_SET_HELP_STRING), - ) - .arg( - Arg::with_name(OPT_UNIVERSAL) - .short("u") - .long(OPT_UNIVERSAL) - .alias(OPT_UNIVERSAL_2) - .help("print or set Coordinated Universal Time (UTC)"), - ) - .arg(Arg::with_name(OPT_FORMAT).multiple(false)) - .get_matches_from(args); + let matches = uu_app().usage(&syntax[..]).get_matches_from(args); let format = if let Some(form) = matches.value_of(OPT_FORMAT) { if !form.starts_with('+') { @@ -314,6 +250,72 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_DATE) + .short("d") + .long(OPT_DATE) + .takes_value(true) + .help("display time described by STRING, not 'now'"), + ) + .arg( + Arg::with_name(OPT_FILE) + .short("f") + .long(OPT_FILE) + .takes_value(true) + .help("like --date; once for each line of DATEFILE"), + ) + .arg( + Arg::with_name(OPT_ISO_8601) + .short("I") + .long(OPT_ISO_8601) + .takes_value(true) + .help(ISO_8601_HELP_STRING), + ) + .arg( + Arg::with_name(OPT_RFC_EMAIL) + .short("R") + .long(OPT_RFC_EMAIL) + .help(RFC_5322_HELP_STRING), + ) + .arg( + Arg::with_name(OPT_RFC_3339) + .long(OPT_RFC_3339) + .takes_value(true) + .help(RFC_3339_HELP_STRING), + ) + .arg( + Arg::with_name(OPT_DEBUG) + .long(OPT_DEBUG) + .help("annotate the parsed date, and warn about questionable usage to stderr"), + ) + .arg( + Arg::with_name(OPT_REFERENCE) + .short("r") + .long(OPT_REFERENCE) + .takes_value(true) + .help("display the last modification time of FILE"), + ) + .arg( + Arg::with_name(OPT_SET) + .short("s") + .long(OPT_SET) + .takes_value(true) + .help(OPT_SET_HELP_STRING), + ) + .arg( + Arg::with_name(OPT_UNIVERSAL) + .short("u") + .long(OPT_UNIVERSAL) + .alias(OPT_UNIVERSAL_2) + .help("print or set Coordinated Universal Time (UTC)"), + ) + .arg(Arg::with_name(OPT_FORMAT).multiple(false)) +} + /// Return the appropriate format string for the given settings. fn make_format_string(settings: &Settings) -> &str { match settings.format { diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 0836aa43d..1092938df 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -258,120 +258,7 @@ fn use_size(free_size: u64, total_size: u64) -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(OPT_ALL) - .short("a") - .long("all") - .help("include dummy file systems"), - ) - .arg( - Arg::with_name(OPT_BLOCKSIZE) - .short("B") - .long("block-size") - .takes_value(true) - .help( - "scale sizes by SIZE before printing them; e.g.\ - '-BM' prints sizes in units of 1,048,576 bytes", - ), - ) - .arg( - Arg::with_name(OPT_DIRECT) - .long("direct") - .help("show statistics for a file instead of mount point"), - ) - .arg( - Arg::with_name(OPT_TOTAL) - .long("total") - .help("produce a grand total"), - ) - .arg( - Arg::with_name(OPT_HUMAN_READABLE) - .short("h") - .long("human-readable") - .conflicts_with(OPT_HUMAN_READABLE_2) - .help("print sizes in human readable format (e.g., 1K 234M 2G)"), - ) - .arg( - Arg::with_name(OPT_HUMAN_READABLE_2) - .short("H") - .long("si") - .conflicts_with(OPT_HUMAN_READABLE) - .help("likewise, but use powers of 1000 not 1024"), - ) - .arg( - Arg::with_name(OPT_INODES) - .short("i") - .long("inodes") - .help("list inode information instead of block usage"), - ) - .arg( - Arg::with_name(OPT_KILO) - .short("k") - .help("like --block-size=1K"), - ) - .arg( - Arg::with_name(OPT_LOCAL) - .short("l") - .long("local") - .help("limit listing to local file systems"), - ) - .arg( - Arg::with_name(OPT_NO_SYNC) - .long("no-sync") - .conflicts_with(OPT_SYNC) - .help("do not invoke sync before getting usage info (default)"), - ) - .arg( - Arg::with_name(OPT_OUTPUT) - .long("output") - .takes_value(true) - .use_delimiter(true) - .help( - "use the output format defined by FIELD_LIST,\ - or print all fields if FIELD_LIST is omitted.", - ), - ) - .arg( - Arg::with_name(OPT_PORTABILITY) - .short("P") - .long("portability") - .help("use the POSIX output format"), - ) - .arg( - Arg::with_name(OPT_SYNC) - .long("sync") - .conflicts_with(OPT_NO_SYNC) - .help("invoke sync before getting usage info"), - ) - .arg( - Arg::with_name(OPT_TYPE) - .short("t") - .long("type") - .takes_value(true) - .use_delimiter(true) - .help("limit listing to file systems of type TYPE"), - ) - .arg( - Arg::with_name(OPT_PRINT_TYPE) - .short("T") - .long("print-type") - .help("print file system type"), - ) - .arg( - Arg::with_name(OPT_EXCLUDE_TYPE) - .short("x") - .long("exclude-type") - .takes_value(true) - .use_delimiter(true) - .help("limit listing to file systems not of type TYPE"), - ) - .arg(Arg::with_name(OPT_PATHS).multiple(true)) - .help("Filesystem(s) to list") - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let paths: Vec = matches .values_of(OPT_PATHS) @@ -511,3 +398,118 @@ pub fn uumain(args: impl uucore::Args) -> i32 { EXIT_OK } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_ALL) + .short("a") + .long("all") + .help("include dummy file systems"), + ) + .arg( + Arg::with_name(OPT_BLOCKSIZE) + .short("B") + .long("block-size") + .takes_value(true) + .help( + "scale sizes by SIZE before printing them; e.g.\ + '-BM' prints sizes in units of 1,048,576 bytes", + ), + ) + .arg( + Arg::with_name(OPT_DIRECT) + .long("direct") + .help("show statistics for a file instead of mount point"), + ) + .arg( + Arg::with_name(OPT_TOTAL) + .long("total") + .help("produce a grand total"), + ) + .arg( + Arg::with_name(OPT_HUMAN_READABLE) + .short("h") + .long("human-readable") + .conflicts_with(OPT_HUMAN_READABLE_2) + .help("print sizes in human readable format (e.g., 1K 234M 2G)"), + ) + .arg( + Arg::with_name(OPT_HUMAN_READABLE_2) + .short("H") + .long("si") + .conflicts_with(OPT_HUMAN_READABLE) + .help("likewise, but use powers of 1000 not 1024"), + ) + .arg( + Arg::with_name(OPT_INODES) + .short("i") + .long("inodes") + .help("list inode information instead of block usage"), + ) + .arg( + Arg::with_name(OPT_KILO) + .short("k") + .help("like --block-size=1K"), + ) + .arg( + Arg::with_name(OPT_LOCAL) + .short("l") + .long("local") + .help("limit listing to local file systems"), + ) + .arg( + Arg::with_name(OPT_NO_SYNC) + .long("no-sync") + .conflicts_with(OPT_SYNC) + .help("do not invoke sync before getting usage info (default)"), + ) + .arg( + Arg::with_name(OPT_OUTPUT) + .long("output") + .takes_value(true) + .use_delimiter(true) + .help( + "use the output format defined by FIELD_LIST,\ + or print all fields if FIELD_LIST is omitted.", + ), + ) + .arg( + Arg::with_name(OPT_PORTABILITY) + .short("P") + .long("portability") + .help("use the POSIX output format"), + ) + .arg( + Arg::with_name(OPT_SYNC) + .long("sync") + .conflicts_with(OPT_NO_SYNC) + .help("invoke sync before getting usage info"), + ) + .arg( + Arg::with_name(OPT_TYPE) + .short("t") + .long("type") + .takes_value(true) + .use_delimiter(true) + .help("limit listing to file systems of type TYPE"), + ) + .arg( + Arg::with_name(OPT_PRINT_TYPE) + .short("T") + .long("print-type") + .help("print file system type"), + ) + .arg( + Arg::with_name(OPT_EXCLUDE_TYPE) + .short("x") + .long("exclude-type") + .takes_value(true) + .use_delimiter(true) + .help("limit listing to file systems not of type TYPE"), + ) + .arg(Arg::with_name(OPT_PATHS).multiple(true)) + .help("Filesystem(s) to list") +} diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 3200a331f..70b609e31 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -73,36 +73,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(SUMMARY) - .usage(&usage[..]) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::BOURNE_SHELL) - .long("sh") - .short("b") - .visible_alias("bourne-shell") - .help("output Bourne shell code to set LS_COLORS") - .display_order(1), - ) - .arg( - Arg::with_name(options::C_SHELL) - .long("csh") - .short("c") - .visible_alias("c-shell") - .help("output C shell code to set LS_COLORS") - .display_order(2), - ) - .arg( - Arg::with_name(options::PRINT_DATABASE) - .long("print-database") - .short("p") - .help("print the byte counts") - .display_order(3), - ) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .get_matches_from(&args); + let matches = uu_app().usage(&usage[..]).get_matches_from(&args); let files = matches .values_of(options::FILE) @@ -181,6 +152,37 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(SUMMARY) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::BOURNE_SHELL) + .long("sh") + .short("b") + .visible_alias("bourne-shell") + .help("output Bourne shell code to set LS_COLORS") + .display_order(1), + ) + .arg( + Arg::with_name(options::C_SHELL) + .long("csh") + .short("c") + .visible_alias("c-shell") + .help("output C shell code to set LS_COLORS") + .display_order(2), + ) + .arg( + Arg::with_name(options::PRINT_DATABASE) + .long("print-database") + .short("p") + .help("print the byte counts") + .display_order(3), + ) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) +} + pub trait StrUtils { /// Remove comments and trim whitespace fn purify(&self) -> &Self; diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index ad42517d4..356f2e6b1 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -38,18 +38,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_long_usage(); - let matches = App::new(executable!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&after_help[..]) - .version(crate_version!()) - .arg( - Arg::with_name(options::ZERO) - .long(options::ZERO) - .short("z") - .help("separate output with NUL rather than newline"), - ) - .arg(Arg::with_name(options::DIR).hidden(true).multiple(true)) .get_matches_from(args); let separator = if matches.is_present(options::ZERO) { @@ -92,3 +83,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .about(ABOUT) + .version(crate_version!()) + .arg( + Arg::with_name(options::ZERO) + .long(options::ZERO) + .short("z") + .help("separate output with NUL rather than newline"), + ) + .arg(Arg::with_name(options::DIR).hidden(true).multiple(true)) +} diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 623faf62c..e5f0e6efc 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -387,182 +387,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(SUMMARY) - .usage(&usage[..]) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::ALL) - .short("a") - .long(options::ALL) - .help("write counts for all files, not just directories"), - ) - .arg( - Arg::with_name(options::APPARENT_SIZE) - .long(options::APPARENT_SIZE) - .help( - "print apparent sizes, rather than disk usage \ - although the apparent size is usually smaller, it may be larger due to holes \ - in ('sparse') files, internal fragmentation, indirect blocks, and the like" - ) - .alias("app") // The GNU test suite uses this alias - ) - .arg( - Arg::with_name(options::BLOCK_SIZE) - .short("B") - .long(options::BLOCK_SIZE) - .value_name("SIZE") - .help( - "scale sizes by SIZE before printing them. \ - E.g., '-BM' prints sizes in units of 1,048,576 bytes. See SIZE format below." - ) - ) - .arg( - Arg::with_name(options::BYTES) - .short("b") - .long("bytes") - .help("equivalent to '--apparent-size --block-size=1'") - ) - .arg( - Arg::with_name(options::TOTAL) - .long("total") - .short("c") - .help("produce a grand total") - ) - .arg( - Arg::with_name(options::MAX_DEPTH) - .short("d") - .long("max-depth") - .value_name("N") - .help( - "print the total for a directory (or file, with --all) \ - only if it is N or fewer levels below the command \ - line argument; --max-depth=0 is the same as --summarize" - ) - ) - .arg( - Arg::with_name(options::HUMAN_READABLE) - .long("human-readable") - .short("h") - .help("print sizes in human readable format (e.g., 1K 234M 2G)") - ) - .arg( - Arg::with_name("inodes") - .long("inodes") - .help( - "list inode usage information instead of block usage like --block-size=1K" - ) - ) - .arg( - Arg::with_name(options::BLOCK_SIZE_1K) - .short("k") - .help("like --block-size=1K") - ) - .arg( - Arg::with_name(options::COUNT_LINKS) - .short("l") - .long("count-links") - .help("count sizes many times if hard linked") - ) - .arg( - Arg::with_name(options::DEREFERENCE) - .short("L") - .long(options::DEREFERENCE) - .help("dereference all symbolic links") - ) - // .arg( - // Arg::with_name("no-dereference") - // .short("P") - // .long("no-dereference") - // .help("don't follow any symbolic links (this is the default)") - // ) - .arg( - Arg::with_name(options::BLOCK_SIZE_1M) - .short("m") - .help("like --block-size=1M") - ) - .arg( - Arg::with_name(options::NULL) - .short("0") - .long("null") - .help("end each output line with 0 byte rather than newline") - ) - .arg( - Arg::with_name(options::SEPARATE_DIRS) - .short("S") - .long("separate-dirs") - .help("do not include size of subdirectories") - ) - .arg( - Arg::with_name(options::SUMMARIZE) - .short("s") - .long("summarize") - .help("display only a total for each argument") - ) - .arg( - Arg::with_name(options::SI) - .long(options::SI) - .help("like -h, but use powers of 1000 not 1024") - ) - .arg( - Arg::with_name(options::ONE_FILE_SYSTEM) - .short("x") - .long(options::ONE_FILE_SYSTEM) - .help("skip directories on different file systems") - ) - .arg( - Arg::with_name(options::THRESHOLD) - .short("t") - .long(options::THRESHOLD) - .alias("th") - .value_name("SIZE") - .number_of_values(1) - .allow_hyphen_values(true) - .help("exclude entries smaller than SIZE if positive, \ - or entries greater than SIZE if negative") - ) - // .arg( - // Arg::with_name("") - // .short("x") - // .long("exclude-from") - // .value_name("FILE") - // .help("exclude files that match any pattern in FILE") - // ) - // .arg( - // Arg::with_name("exclude") - // .long("exclude") - // .value_name("PATTERN") - // .help("exclude files that match PATTERN") - // ) - .arg( - Arg::with_name(options::TIME) - .long(options::TIME) - .value_name("WORD") - .require_equals(true) - .min_values(0) - .possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"]) - .help( - "show time of the last modification of any file in the \ - directory, or any of its subdirectories. If WORD is given, show time as WORD instead \ - of modification time: atime, access, use, ctime, status, birth or creation" - ) - ) - .arg( - Arg::with_name(options::TIME_STYLE) - .long(options::TIME_STYLE) - .value_name("STYLE") - .help( - "show times using style STYLE: \ - full-iso, long-iso, iso, +FORMAT FORMAT is interpreted like 'date'" - ) - ) - .arg( - Arg::with_name(options::FILE) - .hidden(true) - .multiple(true) - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let summarize = matches.is_present(options::SUMMARIZE); @@ -743,6 +568,183 @@ Try '{} --help' for more information.", 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(SUMMARY) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::ALL) + .short("a") + .long(options::ALL) + .help("write counts for all files, not just directories"), + ) + .arg( + Arg::with_name(options::APPARENT_SIZE) + .long(options::APPARENT_SIZE) + .help( + "print apparent sizes, rather than disk usage \ + although the apparent size is usually smaller, it may be larger due to holes \ + in ('sparse') files, internal fragmentation, indirect blocks, and the like" + ) + .alias("app") // The GNU test suite uses this alias + ) + .arg( + Arg::with_name(options::BLOCK_SIZE) + .short("B") + .long(options::BLOCK_SIZE) + .value_name("SIZE") + .help( + "scale sizes by SIZE before printing them. \ + E.g., '-BM' prints sizes in units of 1,048,576 bytes. See SIZE format below." + ) + ) + .arg( + Arg::with_name(options::BYTES) + .short("b") + .long("bytes") + .help("equivalent to '--apparent-size --block-size=1'") + ) + .arg( + Arg::with_name(options::TOTAL) + .long("total") + .short("c") + .help("produce a grand total") + ) + .arg( + Arg::with_name(options::MAX_DEPTH) + .short("d") + .long("max-depth") + .value_name("N") + .help( + "print the total for a directory (or file, with --all) \ + only if it is N or fewer levels below the command \ + line argument; --max-depth=0 is the same as --summarize" + ) + ) + .arg( + Arg::with_name(options::HUMAN_READABLE) + .long("human-readable") + .short("h") + .help("print sizes in human readable format (e.g., 1K 234M 2G)") + ) + .arg( + Arg::with_name("inodes") + .long("inodes") + .help( + "list inode usage information instead of block usage like --block-size=1K" + ) + ) + .arg( + Arg::with_name(options::BLOCK_SIZE_1K) + .short("k") + .help("like --block-size=1K") + ) + .arg( + Arg::with_name(options::COUNT_LINKS) + .short("l") + .long("count-links") + .help("count sizes many times if hard linked") + ) + .arg( + Arg::with_name(options::DEREFERENCE) + .short("L") + .long(options::DEREFERENCE) + .help("dereference all symbolic links") + ) + // .arg( + // Arg::with_name("no-dereference") + // .short("P") + // .long("no-dereference") + // .help("don't follow any symbolic links (this is the default)") + // ) + .arg( + Arg::with_name(options::BLOCK_SIZE_1M) + .short("m") + .help("like --block-size=1M") + ) + .arg( + Arg::with_name(options::NULL) + .short("0") + .long("null") + .help("end each output line with 0 byte rather than newline") + ) + .arg( + Arg::with_name(options::SEPARATE_DIRS) + .short("S") + .long("separate-dirs") + .help("do not include size of subdirectories") + ) + .arg( + Arg::with_name(options::SUMMARIZE) + .short("s") + .long("summarize") + .help("display only a total for each argument") + ) + .arg( + Arg::with_name(options::SI) + .long(options::SI) + .help("like -h, but use powers of 1000 not 1024") + ) + .arg( + Arg::with_name(options::ONE_FILE_SYSTEM) + .short("x") + .long(options::ONE_FILE_SYSTEM) + .help("skip directories on different file systems") + ) + .arg( + Arg::with_name(options::THRESHOLD) + .short("t") + .long(options::THRESHOLD) + .alias("th") + .value_name("SIZE") + .number_of_values(1) + .allow_hyphen_values(true) + .help("exclude entries smaller than SIZE if positive, \ + or entries greater than SIZE if negative") + ) + // .arg( + // Arg::with_name("") + // .short("x") + // .long("exclude-from") + // .value_name("FILE") + // .help("exclude files that match any pattern in FILE") + // ) + // .arg( + // Arg::with_name("exclude") + // .long("exclude") + // .value_name("PATTERN") + // .help("exclude files that match PATTERN") + // ) + .arg( + Arg::with_name(options::TIME) + .long(options::TIME) + .value_name("WORD") + .require_equals(true) + .min_values(0) + .possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"]) + .help( + "show time of the last modification of any file in the \ + directory, or any of its subdirectories. If WORD is given, show time as WORD instead \ + of modification time: atime, access, use, ctime, status, birth or creation" + ) + ) + .arg( + Arg::with_name(options::TIME_STYLE) + .long(options::TIME_STYLE) + .value_name("STYLE") + .help( + "show times using style STYLE: \ + full-iso, long-iso, iso, +FORMAT FORMAT is interpreted like 'date'" + ) + ) + .arg( + Arg::with_name(options::FILE) + .hidden(true) + .multiple(true) + ) +} + #[derive(Clone, Copy)] enum Threshold { Lower(u64), diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index d83a4fe06..8c976c2b4 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -117,7 +117,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) + let matches = uu_app().get_matches_from(args); + + let no_newline = matches.is_present(options::NO_NEWLINE); + let escaped = matches.is_present(options::ENABLE_BACKSLASH_ESCAPE); + let values: Vec = match matches.values_of(options::STRING) { + Some(s) => s.map(|s| s.to_string()).collect(), + None => vec!["".to_string()], + }; + + match execute(no_newline, escaped, values) { + Ok(_) => 0, + Err(f) => { + show_error!("{}", f); + 1 + } + } +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .name(NAME) // TrailingVarArg specifies the final positional argument is a VarArg // and it doesn't attempts the parse any further args. @@ -154,22 +173,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .multiple(true) .allow_hyphen_values(true), ) - .get_matches_from(args); - - let no_newline = matches.is_present(options::NO_NEWLINE); - let escaped = matches.is_present(options::ENABLE_BACKSLASH_ESCAPE); - let values: Vec = match matches.values_of(options::STRING) { - Some(s) => s.map(|s| s.to_string()).collect(), - None => vec!["".to_string()], - }; - - match execute(no_newline, escaped, values) { - Ok(_) => 0, - Err(f) => { - show_error!("{}", f); - 1 - } - } } fn execute(no_newline: bool, escaped: bool, free: Vec) -> io::Result<()> { diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 0ea66d7e9..51ff92801 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -114,7 +114,7 @@ fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b (progname, &args[..]) } -fn create_app() -> App<'static, 'static> { +pub fn uu_app() -> App<'static, 'static> { App::new(crate_name!()) .version(crate_version!()) .author(crate_authors!()) @@ -158,7 +158,7 @@ fn create_app() -> App<'static, 'static> { } fn run_env(args: impl uucore::Args) -> Result<(), i32> { - let app = create_app(); + let app = uu_app(); let matches = app.get_matches_from(args); let ignore_env = matches.is_present("ignore-environment"); diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index d9d669e7c..66c3eb259 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -108,10 +108,16 @@ impl Options { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + expand(Options::new(&matches)); + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .after_help(LONG_HELP) .arg( Arg::with_name(options::INITIAL) @@ -138,10 +144,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .hidden(true) .takes_value(true) ) - .get_matches_from(args); - - expand(Options::new(&matches)); - 0 } fn open(path: String) -> BufReader> { diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index ed992bf71..0906856d1 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/expr.rs" [dependencies] +clap = "2.33.3" libc = "0.2.42" num-bigint = "0.4.0" num-traits = "0.2.14" diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 8238917f7..92c15565d 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -8,13 +8,20 @@ #[macro_use] extern crate uucore; +use clap::{crate_version, App, Arg}; use uucore::InvalidEncodingHandling; mod syntax_tree; mod tokens; -static NAME: &str = "expr"; -static VERSION: &str = env!("CARGO_PKG_VERSION"); +const VERSION: &str = "version"; +const HELP: &str = "help"; + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .arg(Arg::with_name(VERSION).long(VERSION)) + .arg(Arg::with_name(HELP).long(HELP)) +} pub fn uumain(args: impl uucore::Args) -> i32 { let args = args @@ -133,5 +140,5 @@ Environment variables: } fn print_version() { - println!("{} {}", NAME, VERSION); + println!("{} {}", executable!(), crate_version!()); } diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs index af5e3cdb0..0f5d21362 100644 --- a/src/uu/factor/src/cli.rs +++ b/src/uu/factor/src/cli.rs @@ -36,11 +36,7 @@ fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box i32 { - let matches = App::new(executable!()) - .version(crate_version!()) - .about(SUMMARY) - .arg(Arg::with_name(options::NUMBER).multiple(true)) - .get_matches_from(args); + let matches = uu_app().get_matches_from(args); let stdout = stdout(); let mut w = io::BufWriter::new(stdout.lock()); @@ -68,3 +64,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(SUMMARY) + .arg(Arg::with_name(options::NUMBER).multiple(true)) +} diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index d7cbcd13a..644051d59 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/false.rs" [dependencies] +clap = "2.33.3" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index 917c43fa0..aaeb6b751 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -5,6 +5,15 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +use clap::{App, AppSettings}; +use uucore::executable; + pub fn uumain(_: impl uucore::Args) -> i32 { 1 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .setting(AppSettings::DisableHelpFlags) + .setting(AppSettings::DisableVersion) +} diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 91f59e076..9eceaa56c 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -77,129 +77,7 @@ pub struct FmtOptions { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(OPT_CROWN_MARGIN) - .short("c") - .long(OPT_CROWN_MARGIN) - .help( - "First and second line of paragraph - may have different indentations, in which - case the first line's indentation is preserved, - and each subsequent line's indentation matches the second line.", - ), - ) - .arg( - Arg::with_name(OPT_TAGGED_PARAGRAPH) - .short("t") - .long("tagged-paragraph") - .help( - "Like -c, except that the first and second line of a paragraph *must* - have different indentation or they are treated as separate paragraphs.", - ), - ) - .arg( - Arg::with_name(OPT_PRESERVE_HEADERS) - .short("m") - .long("preserve-headers") - .help( - "Attempt to detect and preserve mail headers in the input. - Be careful when combining this flag with -p.", - ), - ) - .arg( - Arg::with_name(OPT_SPLIT_ONLY) - .short("s") - .long("split-only") - .help("Split lines only, do not reflow."), - ) - .arg( - Arg::with_name(OPT_UNIFORM_SPACING) - .short("u") - .long("uniform-spacing") - .help( - "Insert exactly one - space between words, and two between sentences. - Sentence breaks in the input are detected as [?!.] - followed by two spaces or a newline; other punctuation - is not interpreted as a sentence break.", - ), - ) - .arg( - Arg::with_name(OPT_PREFIX) - .short("p") - .long("prefix") - .help( - "Reformat only lines - beginning with PREFIX, reattaching PREFIX to reformatted lines. - Unless -x is specified, leading whitespace will be ignored - when matching PREFIX.", - ) - .value_name("PREFIX"), - ) - .arg( - Arg::with_name(OPT_SKIP_PREFIX) - .short("P") - .long("skip-prefix") - .help( - "Do not reformat lines - beginning with PSKIP. Unless -X is specified, leading whitespace - will be ignored when matching PSKIP", - ) - .value_name("PSKIP"), - ) - .arg( - Arg::with_name(OPT_EXACT_PREFIX) - .short("x") - .long("exact-prefix") - .help( - "PREFIX must match at the - beginning of the line with no preceding whitespace.", - ), - ) - .arg( - Arg::with_name(OPT_EXACT_SKIP_PREFIX) - .short("X") - .long("exact-skip-prefix") - .help( - "PSKIP must match at the - beginning of the line with no preceding whitespace.", - ), - ) - .arg( - Arg::with_name(OPT_WIDTH) - .short("w") - .long("width") - .help("Fill output lines up to a maximum of WIDTH columns, default 79.") - .value_name("WIDTH"), - ) - .arg( - Arg::with_name(OPT_GOAL) - .short("g") - .long("goal") - .help("Goal width, default ~0.94*WIDTH. Must be less than WIDTH.") - .value_name("GOAL"), - ) - .arg(Arg::with_name(OPT_QUICK).short("q").long("quick").help( - "Break lines more quickly at the - expense of a potentially more ragged appearance.", - )) - .arg( - Arg::with_name(OPT_TAB_WIDTH) - .short("T") - .long("tab-width") - .help( - "Treat tabs as TABWIDTH spaces for - determining line length, default 8. Note that this is used only for - calculating line lengths; tabs are preserved in the output.", - ) - .value_name("TABWIDTH"), - ) - .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let mut files: Vec = matches .values_of(ARG_FILES) @@ -331,3 +209,127 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_CROWN_MARGIN) + .short("c") + .long(OPT_CROWN_MARGIN) + .help( + "First and second line of paragraph + may have different indentations, in which + case the first line's indentation is preserved, + and each subsequent line's indentation matches the second line.", + ), + ) + .arg( + Arg::with_name(OPT_TAGGED_PARAGRAPH) + .short("t") + .long("tagged-paragraph") + .help( + "Like -c, except that the first and second line of a paragraph *must* + have different indentation or they are treated as separate paragraphs.", + ), + ) + .arg( + Arg::with_name(OPT_PRESERVE_HEADERS) + .short("m") + .long("preserve-headers") + .help( + "Attempt to detect and preserve mail headers in the input. + Be careful when combining this flag with -p.", + ), + ) + .arg( + Arg::with_name(OPT_SPLIT_ONLY) + .short("s") + .long("split-only") + .help("Split lines only, do not reflow."), + ) + .arg( + Arg::with_name(OPT_UNIFORM_SPACING) + .short("u") + .long("uniform-spacing") + .help( + "Insert exactly one + space between words, and two between sentences. + Sentence breaks in the input are detected as [?!.] + followed by two spaces or a newline; other punctuation + is not interpreted as a sentence break.", + ), + ) + .arg( + Arg::with_name(OPT_PREFIX) + .short("p") + .long("prefix") + .help( + "Reformat only lines + beginning with PREFIX, reattaching PREFIX to reformatted lines. + Unless -x is specified, leading whitespace will be ignored + when matching PREFIX.", + ) + .value_name("PREFIX"), + ) + .arg( + Arg::with_name(OPT_SKIP_PREFIX) + .short("P") + .long("skip-prefix") + .help( + "Do not reformat lines + beginning with PSKIP. Unless -X is specified, leading whitespace + will be ignored when matching PSKIP", + ) + .value_name("PSKIP"), + ) + .arg( + Arg::with_name(OPT_EXACT_PREFIX) + .short("x") + .long("exact-prefix") + .help( + "PREFIX must match at the + beginning of the line with no preceding whitespace.", + ), + ) + .arg( + Arg::with_name(OPT_EXACT_SKIP_PREFIX) + .short("X") + .long("exact-skip-prefix") + .help( + "PSKIP must match at the + beginning of the line with no preceding whitespace.", + ), + ) + .arg( + Arg::with_name(OPT_WIDTH) + .short("w") + .long("width") + .help("Fill output lines up to a maximum of WIDTH columns, default 79.") + .value_name("WIDTH"), + ) + .arg( + Arg::with_name(OPT_GOAL) + .short("g") + .long("goal") + .help("Goal width, default ~0.94*WIDTH. Must be less than WIDTH.") + .value_name("GOAL"), + ) + .arg(Arg::with_name(OPT_QUICK).short("q").long("quick").help( + "Break lines more quickly at the + expense of a potentially more ragged appearance.", + )) + .arg( + Arg::with_name(OPT_TAB_WIDTH) + .short("T") + .long("tab-width") + .help( + "Treat tabs as TABWIDTH spaces for + determining line length, default 8. Note that this is used only for + calculating line lengths; tabs are preserved in the output.", + ) + .value_name("TABWIDTH"), + ) + .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) +} diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 118f7f5f9..1dbc8cdc7 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -36,7 +36,35 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .accept_any(); let (args, obs_width) = handle_obsolete(&args[..]); - let matches = App::new(executable!()) + let matches = uu_app().get_matches_from(args); + + let bytes = matches.is_present(options::BYTES); + let spaces = matches.is_present(options::SPACES); + let poss_width = match matches.value_of(options::WIDTH) { + Some(v) => Some(v.to_owned()), + None => obs_width, + }; + + let width = match poss_width { + Some(inp_width) => match inp_width.parse::() { + Ok(width) => width, + Err(e) => crash!(1, "illegal width value (\"{}\"): {}", inp_width, e), + }, + None => 80, + }; + + let files = match matches.values_of(options::FILE) { + Some(v) => v.map(|v| v.to_owned()).collect(), + None => vec!["-".to_owned()], + }; + + fold(files, bytes, spaces, width); + + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .name(NAME) .version(crate_version!()) .usage(SYNTAX) @@ -68,31 +96,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true), ) .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .get_matches_from(args); - - let bytes = matches.is_present(options::BYTES); - let spaces = matches.is_present(options::SPACES); - let poss_width = match matches.value_of(options::WIDTH) { - Some(v) => Some(v.to_owned()), - None => obs_width, - }; - - let width = match poss_width { - Some(inp_width) => match inp_width.parse::() { - Ok(width) => width, - Err(e) => crash!(1, "illegal width value (\"{}\"): {}", inp_width, e), - }, - None => 80, - }; - - let files = match matches.values_of(options::FILE) { - Some(v) => v.map(|v| v.to_owned()).collect(), - None => vec!["-".to_owned()], - }; - - fold(files, bytes, spaces, width); - - 0 } fn handle_obsolete(args: &[String]) -> (Vec, Option) { diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 6585f3d16..a40d1a490 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -35,17 +35,7 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::USERS) - .multiple(true) - .takes_value(true) - .value_name(options::USERS), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let users: Vec = matches .values_of(options::USERS) @@ -93,3 +83,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } exit_code } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::USERS) + .multiple(true) + .takes_value(true) + .value_name(options::USERS), + ) +} diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index a007473ab..d9feb6648 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -285,119 +285,7 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { // Default binary in Windows, text mode otherwise let binary_flag_default = cfg!(windows); - let binary_help = format!( - "read in binary mode{}", - if binary_flag_default { - " (default)" - } else { - "" - } - ); - - let text_help = format!( - "read in text mode{}", - if binary_flag_default { - "" - } else { - " (default)" - } - ); - - let mut app = App::new(executable!()) - .version(crate_version!()) - .about("Compute and check message digests.") - .arg( - Arg::with_name("binary") - .short("b") - .long("binary") - .help(&binary_help), - ) - .arg( - Arg::with_name("check") - .short("c") - .long("check") - .help("read hashsums from the FILEs and check them"), - ) - .arg( - Arg::with_name("tag") - .long("tag") - .help("create a BSD-style checksum"), - ) - .arg( - Arg::with_name("text") - .short("t") - .long("text") - .help(&text_help) - .conflicts_with("binary"), - ) - .arg( - Arg::with_name("quiet") - .short("q") - .long("quiet") - .help("don't print OK for each successfully verified file"), - ) - .arg( - Arg::with_name("status") - .short("s") - .long("status") - .help("don't output anything, status code shows success"), - ) - .arg( - Arg::with_name("strict") - .long("strict") - .help("exit non-zero for improperly formatted checksum lines"), - ) - .arg( - Arg::with_name("warn") - .short("w") - .long("warn") - .help("warn about improperly formatted checksum lines"), - ) - // Needed for variable-length output sums (e.g. SHAKE) - .arg( - Arg::with_name("bits") - .long("bits") - .help("set the size of the output (only for SHAKE)") - .takes_value(true) - .value_name("BITS") - // XXX: should we actually use validators? they're not particularly efficient - .validator(is_valid_bit_num), - ) - .arg( - Arg::with_name("FILE") - .index(1) - .multiple(true) - .value_name("FILE"), - ); - - if !is_custom_binary(&binary_name) { - let algorithms = &[ - ("md5", "work with MD5"), - ("sha1", "work with SHA1"), - ("sha224", "work with SHA224"), - ("sha256", "work with SHA256"), - ("sha384", "work with SHA384"), - ("sha512", "work with SHA512"), - ("sha3", "work with SHA3"), - ("sha3-224", "work with SHA3-224"), - ("sha3-256", "work with SHA3-256"), - ("sha3-384", "work with SHA3-384"), - ("sha3-512", "work with SHA3-512"), - ( - "shake128", - "work with SHAKE128 using BITS for the output size", - ), - ( - "shake256", - "work with SHAKE256 using BITS for the output size", - ), - ("b2sum", "work with BLAKE2"), - ]; - - for (name, desc) in algorithms { - app = app.arg(Arg::with_name(name).long(name).help(desc)); - } - } + let app = uu_app(&binary_name); // FIXME: this should use get_matches_from_safe() and crash!(), but at the moment that just // causes "error: " to be printed twice (once from crash!() and once from clap). With @@ -445,6 +333,124 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { } } +pub fn uu_app_common() -> App<'static, 'static> { + #[cfg(windows)] + const BINARY_HELP: &str = "read in binary mode (default)"; + #[cfg(not(windows))] + const BINARY_HELP: &str = "read in binary mode"; + #[cfg(windows)] + const TEXT_HELP: &str = "read in text mode"; + #[cfg(not(windows))] + const TEXT_HELP: &str = "read in text mode (default)"; + App::new(executable!()) + .version(crate_version!()) + .about("Compute and check message digests.") + .arg( + Arg::with_name("binary") + .short("b") + .long("binary") + .help(BINARY_HELP), + ) + .arg( + Arg::with_name("check") + .short("c") + .long("check") + .help("read hashsums from the FILEs and check them"), + ) + .arg( + Arg::with_name("tag") + .long("tag") + .help("create a BSD-style checksum"), + ) + .arg( + Arg::with_name("text") + .short("t") + .long("text") + .help(TEXT_HELP) + .conflicts_with("binary"), + ) + .arg( + Arg::with_name("quiet") + .short("q") + .long("quiet") + .help("don't print OK for each successfully verified file"), + ) + .arg( + Arg::with_name("status") + .short("s") + .long("status") + .help("don't output anything, status code shows success"), + ) + .arg( + Arg::with_name("strict") + .long("strict") + .help("exit non-zero for improperly formatted checksum lines"), + ) + .arg( + Arg::with_name("warn") + .short("w") + .long("warn") + .help("warn about improperly formatted checksum lines"), + ) + // Needed for variable-length output sums (e.g. SHAKE) + .arg( + Arg::with_name("bits") + .long("bits") + .help("set the size of the output (only for SHAKE)") + .takes_value(true) + .value_name("BITS") + // XXX: should we actually use validators? they're not particularly efficient + .validator(is_valid_bit_num), + ) + .arg( + Arg::with_name("FILE") + .index(1) + .multiple(true) + .value_name("FILE"), + ) +} + +pub fn uu_app_custom() -> App<'static, 'static> { + let mut app = uu_app_common(); + let algorithms = &[ + ("md5", "work with MD5"), + ("sha1", "work with SHA1"), + ("sha224", "work with SHA224"), + ("sha256", "work with SHA256"), + ("sha384", "work with SHA384"), + ("sha512", "work with SHA512"), + ("sha3", "work with SHA3"), + ("sha3-224", "work with SHA3-224"), + ("sha3-256", "work with SHA3-256"), + ("sha3-384", "work with SHA3-384"), + ("sha3-512", "work with SHA3-512"), + ( + "shake128", + "work with SHAKE128 using BITS for the output size", + ), + ( + "shake256", + "work with SHAKE256 using BITS for the output size", + ), + ("b2sum", "work with BLAKE2"), + ]; + + for (name, desc) in algorithms { + app = app.arg(Arg::with_name(name).long(name).help(desc)); + } + app +} + +// hashsum is handled differently in build.rs, therefore this is not the same +// as in other utilities. +fn uu_app(binary_name: &str) -> App<'static, 'static> { + if !is_custom_binary(binary_name) { + uu_app_custom() + } else { + uu_app_common() + } +} + #[allow(clippy::cognitive_complexity)] fn hashsum<'a, I>(mut options: Options, files: I) -> Result<(), i32> where diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index aceecd941..e17e17034 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -40,7 +40,7 @@ mod take; use lines::zlines; use take::take_all_but; -fn app<'a>() -> App<'a, 'a> { +pub fn uu_app() -> App<'static, 'static> { App::new(executable!()) .version(crate_version!()) .about(ABOUT) @@ -167,7 +167,7 @@ impl HeadOptions { ///Construct options from matches pub fn get_from(args: impl uucore::Args) -> Result { - let matches = app().get_matches_from(arg_iterate(args)?); + let matches = uu_app().get_matches_from(arg_iterate(args)?); let mut options = HeadOptions::new(); diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index ab6954104..ab8b43f05 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/hostid.rs" [dependencies] +clap = "2.33.3" libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index 551866521..e9fc08379 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -10,12 +10,10 @@ #[macro_use] extern crate uucore; +use clap::{crate_version, App}; use libc::c_long; -use uucore::InvalidEncodingHandling; static SYNTAX: &str = "[options]"; -static SUMMARY: &str = ""; -static LONG_HELP: &str = ""; // currently rust libc interface doesn't include gethostid extern "C" { @@ -23,14 +21,17 @@ extern "C" { } pub fn uumain(args: impl uucore::Args) -> i32 { - app!(SYNTAX, SUMMARY, LONG_HELP).parse( - args.collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(), - ); + uu_app().get_matches_from(args); hostid(); 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .usage(SYNTAX) +} + fn hostid() { /* * POSIX says gethostid returns a "32-bit identifier" but is silent diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index ff312fb58..fe477d7b5 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -52,10 +52,25 @@ fn get_usage() -> String { } fn execute(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + match matches.value_of(OPT_HOST) { + None => display_hostname(&matches), + Some(host) => { + if let Err(err) = hostname::set(host) { + show_error!("{}", err); + 1 + } else { + 0 + } + } + } +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .arg( Arg::with_name(OPT_DOMAIN) .short("d") @@ -80,19 +95,6 @@ fn execute(args: impl uucore::Args) -> i32 { possible", )) .arg(Arg::with_name(OPT_HOST)) - .get_matches_from(args); - - match matches.value_of(OPT_HOST) { - None => display_hostname(&matches), - Some(host) => { - if let Err(err) = hostname::set(host) { - show_error!("{}", err); - 1 - } else { - 0 - } - } - } } fn display_hostname(matches: &ArgMatches) -> i32 { diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index e6233f0b7..d5acc97f3 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -115,106 +115,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_description(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&after_help[..]) - .arg( - Arg::with_name(options::OPT_AUDIT) - .short("A") - .conflicts_with_all(&[ - options::OPT_GROUP, - options::OPT_EFFECTIVE_USER, - options::OPT_HUMAN_READABLE, - options::OPT_PASSWORD, - options::OPT_GROUPS, - options::OPT_ZERO, - ]) - .help( - "Display the process audit user ID and other process audit properties,\n\ - which requires privilege (not available on Linux).", - ), - ) - .arg( - Arg::with_name(options::OPT_EFFECTIVE_USER) - .short("u") - .long(options::OPT_EFFECTIVE_USER) - .conflicts_with(options::OPT_GROUP) - .help("Display only the effective user ID as a number."), - ) - .arg( - Arg::with_name(options::OPT_GROUP) - .short("g") - .long(options::OPT_GROUP) - .help("Display only the effective group ID as a number"), - ) - .arg( - Arg::with_name(options::OPT_GROUPS) - .short("G") - .long(options::OPT_GROUPS) - .conflicts_with_all(&[ - options::OPT_GROUP, - options::OPT_EFFECTIVE_USER, - options::OPT_HUMAN_READABLE, - options::OPT_PASSWORD, - options::OPT_AUDIT, - ]) - .help( - "Display only the different group IDs as white-space separated numbers, \ - in no particular order.", - ), - ) - .arg( - Arg::with_name(options::OPT_HUMAN_READABLE) - .short("p") - .help("Make the output human-readable. Each display is on a separate line."), - ) - .arg( - Arg::with_name(options::OPT_NAME) - .short("n") - .long(options::OPT_NAME) - .help( - "Display the name of the user or group ID for the -G, -g and -u options \ - instead of the number.\nIf any of the ID numbers cannot be mapped into \ - names, the number will be displayed as usual.", - ), - ) - .arg( - Arg::with_name(options::OPT_PASSWORD) - .short("P") - .help("Display the id as a password file entry."), - ) - .arg( - Arg::with_name(options::OPT_REAL_ID) - .short("r") - .long(options::OPT_REAL_ID) - .help( - "Display the real ID for the -G, -g and -u options instead of \ - the effective ID.", - ), - ) - .arg( - Arg::with_name(options::OPT_ZERO) - .short("z") - .long(options::OPT_ZERO) - .help( - "delimit entries with NUL characters, not whitespace;\n\ - not permitted in default format", - ), - ) - .arg( - Arg::with_name(options::OPT_CONTEXT) - .short("Z") - .long(options::OPT_CONTEXT) - .help("NotImplemented: print only the security context of the process"), - ) - .arg( - Arg::with_name(options::ARG_USERS) - .multiple(true) - .takes_value(true) - .value_name(options::ARG_USERS), - ) .get_matches_from(args); let users: Vec = matches @@ -385,6 +288,107 @@ pub fn uumain(args: impl uucore::Args) -> i32 { exit_code } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::OPT_AUDIT) + .short("A") + .conflicts_with_all(&[ + options::OPT_GROUP, + options::OPT_EFFECTIVE_USER, + options::OPT_HUMAN_READABLE, + options::OPT_PASSWORD, + options::OPT_GROUPS, + options::OPT_ZERO, + ]) + .help( + "Display the process audit user ID and other process audit properties,\n\ + which requires privilege (not available on Linux).", + ), + ) + .arg( + Arg::with_name(options::OPT_EFFECTIVE_USER) + .short("u") + .long(options::OPT_EFFECTIVE_USER) + .conflicts_with(options::OPT_GROUP) + .help("Display only the effective user ID as a number."), + ) + .arg( + Arg::with_name(options::OPT_GROUP) + .short("g") + .long(options::OPT_GROUP) + .help("Display only the effective group ID as a number"), + ) + .arg( + Arg::with_name(options::OPT_GROUPS) + .short("G") + .long(options::OPT_GROUPS) + .conflicts_with_all(&[ + options::OPT_GROUP, + options::OPT_EFFECTIVE_USER, + options::OPT_HUMAN_READABLE, + options::OPT_PASSWORD, + options::OPT_AUDIT, + ]) + .help( + "Display only the different group IDs as white-space separated numbers, \ + in no particular order.", + ), + ) + .arg( + Arg::with_name(options::OPT_HUMAN_READABLE) + .short("p") + .help("Make the output human-readable. Each display is on a separate line."), + ) + .arg( + Arg::with_name(options::OPT_NAME) + .short("n") + .long(options::OPT_NAME) + .help( + "Display the name of the user or group ID for the -G, -g and -u options \ + instead of the number.\nIf any of the ID numbers cannot be mapped into \ + names, the number will be displayed as usual.", + ), + ) + .arg( + Arg::with_name(options::OPT_PASSWORD) + .short("P") + .help("Display the id as a password file entry."), + ) + .arg( + Arg::with_name(options::OPT_REAL_ID) + .short("r") + .long(options::OPT_REAL_ID) + .help( + "Display the real ID for the -G, -g and -u options instead of \ + the effective ID.", + ), + ) + .arg( + Arg::with_name(options::OPT_ZERO) + .short("z") + .long(options::OPT_ZERO) + .help( + "delimit entries with NUL characters, not whitespace;\n\ + not permitted in default format", + ), + ) + .arg( + Arg::with_name(options::OPT_CONTEXT) + .short("Z") + .long(options::OPT_CONTEXT) + .help("NotImplemented: print only the security context of the process"), + ) + .arg( + Arg::with_name(options::ARG_USERS) + .multiple(true) + .takes_value(true) + .value_name(options::ARG_USERS), + ) +} + fn pretty(possible_pw: Option) { if let Some(p) = possible_pw { print!("uid\t{}\ngroups\t", p.name()); diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 3992ac25e..e45797750 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -98,10 +98,35 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + let paths: Vec = matches + .values_of(ARG_FILES) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + if let Err(s) = check_unimplemented(&matches) { + show_error!("Unimplemented feature: {}", s); + return 2; + } + + let behavior = match behavior(&matches) { + Ok(x) => x, + Err(ret) => { + return ret; + } + }; + + match behavior.main_function { + MainFunction::Directory => directory(paths, behavior), + MainFunction::Standard => standard(paths, behavior), + } +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .arg( Arg::with_name(OPT_BACKUP) .long(OPT_BACKUP) @@ -228,29 +253,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .value_name("CONTEXT") ) .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1)) - .get_matches_from(args); - - let paths: Vec = matches - .values_of(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - if let Err(s) = check_unimplemented(&matches) { - show_error!("Unimplemented feature: {}", s); - return 2; - } - - let behavior = match behavior(&matches) { - Ok(x) => x, - Err(ret) => { - return ret; - } - }; - - match behavior.main_function { - MainFunction::Directory => directory(paths, behavior), - MainFunction::Standard => standard(paths, behavior), - } } /// Check for unimplemented command line arguments. diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 4cdfe2141..60721f212 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -442,7 +442,72 @@ impl<'a> State<'a> { } pub fn uumain(args: impl uucore::Args) -> i32 { - let matches = App::new(NAME) + let matches = uu_app().get_matches_from(args); + + let keys = parse_field_number_option(matches.value_of("j")); + let key1 = parse_field_number_option(matches.value_of("1")); + let key2 = parse_field_number_option(matches.value_of("2")); + + let mut settings: Settings = Default::default(); + + if let Some(value) = matches.value_of("v") { + settings.print_unpaired = parse_file_number(value); + settings.print_joined = false; + } else if let Some(value) = matches.value_of("a") { + settings.print_unpaired = parse_file_number(value); + } + + settings.ignore_case = matches.is_present("i"); + settings.key1 = get_field_number(keys, key1); + settings.key2 = get_field_number(keys, key2); + + if let Some(value) = matches.value_of("t") { + settings.separator = match value.len() { + 0 => Sep::Line, + 1 => Sep::Char(value.chars().next().unwrap()), + _ => crash!(1, "multi-character tab {}", value), + }; + } + + if let Some(format) = matches.value_of("o") { + if format == "auto" { + settings.autoformat = true; + } else { + settings.format = format + .split(|c| c == ' ' || c == ',' || c == '\t') + .map(Spec::parse) + .collect(); + } + } + + if let Some(empty) = matches.value_of("e") { + settings.empty = empty.to_string(); + } + + if matches.is_present("nocheck-order") { + settings.check_order = CheckOrder::Disabled; + } + + if matches.is_present("check-order") { + settings.check_order = CheckOrder::Enabled; + } + + if matches.is_present("header") { + settings.headers = true; + } + + let file1 = matches.value_of("file1").unwrap(); + let file2 = matches.value_of("file2").unwrap(); + + if file1 == "-" && file2 == "-" { + crash!(1, "both files cannot be standard input"); + } + + exec(file1, file2, &settings) +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(NAME) .version(crate_version!()) .about( "For each pair of input lines with identical join fields, write a line to @@ -542,68 +607,6 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2", .value_name("FILE2") .hidden(true), ) - .get_matches_from(args); - - let keys = parse_field_number_option(matches.value_of("j")); - let key1 = parse_field_number_option(matches.value_of("1")); - let key2 = parse_field_number_option(matches.value_of("2")); - - let mut settings: Settings = Default::default(); - - if let Some(value) = matches.value_of("v") { - settings.print_unpaired = parse_file_number(value); - settings.print_joined = false; - } else if let Some(value) = matches.value_of("a") { - settings.print_unpaired = parse_file_number(value); - } - - settings.ignore_case = matches.is_present("i"); - settings.key1 = get_field_number(keys, key1); - settings.key2 = get_field_number(keys, key2); - - if let Some(value) = matches.value_of("t") { - settings.separator = match value.len() { - 0 => Sep::Line, - 1 => Sep::Char(value.chars().next().unwrap()), - _ => crash!(1, "multi-character tab {}", value), - }; - } - - if let Some(format) = matches.value_of("o") { - if format == "auto" { - settings.autoformat = true; - } else { - settings.format = format - .split(|c| c == ' ' || c == ',' || c == '\t') - .map(Spec::parse) - .collect(); - } - } - - if let Some(empty) = matches.value_of("e") { - settings.empty = empty.to_string(); - } - - if matches.is_present("nocheck-order") { - settings.check_order = CheckOrder::Disabled; - } - - if matches.is_present("check-order") { - settings.check_order = CheckOrder::Enabled; - } - - if matches.is_present("header") { - settings.headers = true; - } - - let file1 = matches.value_of("file1").unwrap(); - let file2 = matches.value_of("file2").unwrap(); - - if file1 == "-" && file2 == "-" { - crash!(1, "both files cannot be standard input"); - } - - exec(file1, file2, &settings) } fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 { diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index c48864564..92868efdb 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -43,38 +43,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let (args, obs_signal) = handle_obsolete(args); let usage = format!("{} [OPTIONS]... PID...", executable!()); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::LIST) - .short("l") - .long(options::LIST) - .help("Lists signals") - .conflicts_with(options::TABLE) - .conflicts_with(options::TABLE_OLD), - ) - .arg( - Arg::with_name(options::TABLE) - .short("t") - .long(options::TABLE) - .help("Lists table of signals"), - ) - .arg(Arg::with_name(options::TABLE_OLD).short("L").hidden(true)) - .arg( - Arg::with_name(options::SIGNAL) - .short("s") - .long(options::SIGNAL) - .help("Sends given signal") - .takes_value(true), - ) - .arg( - Arg::with_name(options::PIDS_OR_SIGNALS) - .hidden(true) - .multiple(true), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) { Mode::Table @@ -106,6 +75,39 @@ pub fn uumain(args: impl uucore::Args) -> i32 { EXIT_OK } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::LIST) + .short("l") + .long(options::LIST) + .help("Lists signals") + .conflicts_with(options::TABLE) + .conflicts_with(options::TABLE_OLD), + ) + .arg( + Arg::with_name(options::TABLE) + .short("t") + .long(options::TABLE) + .help("Lists table of signals"), + ) + .arg(Arg::with_name(options::TABLE_OLD).short("L").hidden(true)) + .arg( + Arg::with_name(options::SIGNAL) + .short("s") + .long(options::SIGNAL) + .help("Sends given signal") + .takes_value(true), + ) + .arg( + Arg::with_name(options::PIDS_OR_SIGNALS) + .hidden(true) + .multiple(true), + ) +} + fn handle_obsolete(mut args: Vec) -> (Vec, Option) { let mut i = 0; while i < args.len() { diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 08401ebaf..ad7702044 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -32,19 +32,7 @@ pub fn normalize_error_message(e: Error) -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::FILES) - .hidden(true) - .required(true) - .min_values(2) - .max_values(2) - .takes_value(true), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let files: Vec<_> = matches .values_of_os(options::FILES) @@ -61,3 +49,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::FILES) + .hidden(true) + .required(true) + .min_values(2) + .max_values(2) + .takes_value(true), + ) +} diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 29cab58e5..b08eba97a 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -97,11 +97,71 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let long_usage = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&long_usage[..]) + .get_matches_from(args); + + /* the list of files */ + + let paths: Vec = matches + .values_of(ARG_FILES) + .unwrap() + .map(PathBuf::from) + .collect(); + + let overwrite_mode = if matches.is_present(options::FORCE) { + OverwriteMode::Force + } else if matches.is_present(options::INTERACTIVE) { + OverwriteMode::Interactive + } else { + OverwriteMode::NoClobber + }; + + let backup_mode = if matches.is_present(options::B) { + BackupMode::ExistingBackup + } else if matches.is_present(options::BACKUP) { + match matches.value_of(options::BACKUP) { + None => BackupMode::ExistingBackup, + Some(mode) => match mode { + "simple" | "never" => BackupMode::SimpleBackup, + "numbered" | "t" => BackupMode::NumberedBackup, + "existing" | "nil" => BackupMode::ExistingBackup, + "none" | "off" => BackupMode::NoBackup, + _ => panic!(), // cannot happen as it is managed by clap + }, + } + } else { + BackupMode::NoBackup + }; + + let backup_suffix = if matches.is_present(options::SUFFIX) { + matches.value_of(options::SUFFIX).unwrap() + } else { + "~" + }; + + let settings = Settings { + overwrite: overwrite_mode, + backup: backup_mode, + suffix: backup_suffix.to_string(), + symbolic: matches.is_present(options::SYMBOLIC), + relative: matches.is_present(options::RELATIVE), + target_dir: matches + .value_of(options::TARGET_DIRECTORY) + .map(String::from), + no_target_dir: matches.is_present(options::NO_TARGET_DIRECTORY), + no_dereference: matches.is_present(options::NO_DEREFERENCE), + verbose: matches.is_present(options::VERBOSE), + }; + + exec(&paths[..], &settings) +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) .arg(Arg::with_name(options::B).short(options::B).help( "make a backup of each file that would otherwise be overwritten or \ removed", @@ -198,62 +258,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .required(true) .min_values(1), ) - .get_matches_from(args); - - /* the list of files */ - - let paths: Vec = matches - .values_of(ARG_FILES) - .unwrap() - .map(PathBuf::from) - .collect(); - - let overwrite_mode = if matches.is_present(options::FORCE) { - OverwriteMode::Force - } else if matches.is_present(options::INTERACTIVE) { - OverwriteMode::Interactive - } else { - OverwriteMode::NoClobber - }; - - let backup_mode = if matches.is_present(options::B) { - BackupMode::ExistingBackup - } else if matches.is_present(options::BACKUP) { - match matches.value_of(options::BACKUP) { - None => BackupMode::ExistingBackup, - Some(mode) => match mode { - "simple" | "never" => BackupMode::SimpleBackup, - "numbered" | "t" => BackupMode::NumberedBackup, - "existing" | "nil" => BackupMode::ExistingBackup, - "none" | "off" => BackupMode::NoBackup, - _ => panic!(), // cannot happen as it is managed by clap - }, - } - } else { - BackupMode::NoBackup - }; - - let backup_suffix = if matches.is_present(options::SUFFIX) { - matches.value_of(options::SUFFIX).unwrap() - } else { - "~" - }; - - let settings = Settings { - overwrite: overwrite_mode, - backup: backup_mode, - suffix: backup_suffix.to_string(), - symbolic: matches.is_present(options::SYMBOLIC), - relative: matches.is_present(options::RELATIVE), - target_dir: matches - .value_of(options::TARGET_DIRECTORY) - .map(String::from), - no_target_dir: matches.is_present(options::NO_TARGET_DIRECTORY), - no_dereference: matches.is_present(options::NO_DEREFERENCE), - verbose: matches.is_present(options::VERBOSE), - }; - - exec(&paths[..], &settings) } fn exec(files: &[PathBuf], settings: &Settings) -> i32 { diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index ba5880403..4a6f43418 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -45,11 +45,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .accept_any(); let usage = get_usage(); - let _ = App::new(executable!()) - .version(crate_version!()) - .about(SUMMARY) - .usage(&usage[..]) - .get_matches_from(args); + let _ = uu_app().usage(&usage[..]).get_matches_from(args); match get_userlogin() { Some(userlogin) => println!("{}", userlogin), @@ -58,3 +54,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(SUMMARY) +} diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 01bc27055..6ca3f4bbe 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -558,10 +558,22 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let app = App::new(executable!()) + let app = uu_app().usage(&usage[..]); + + let matches = app.get_matches_from(args); + + let locs = matches + .values_of(options::PATHS) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_else(|| vec![String::from(".")]); + + list(locs, Config::from(matches)) +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) // Format arguments .arg( @@ -1095,16 +1107,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // Positional arguments .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)) - .after_help(AFTER_HELP); - - let matches = app.get_matches_from(args); - - let locs = matches - .values_of(options::PATHS) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_else(|| vec![String::from(".")]); - - list(locs, Config::from(matches)) + .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 c5ff8b76c..82d561213 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -32,10 +32,37 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // Linux-specific options, not implemented // opts.optflag("Z", "context", "set SELinux security context" + // " of each created directory to CTX"), - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + let dirs: Vec = matches + .values_of(ARG_DIRS) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + let verbose = matches.is_present(OPT_VERBOSE); + let recursive = matches.is_present(OPT_PARENTS); + + // Translate a ~str in octal form to u16, default to 755 + // Not tested on Windows + let mode_match = matches.value_of(OPT_MODE); + let mode: u16 = match mode_match { + Some(m) => { + let res: Option = u16::from_str_radix(m, 8).ok(); + match res { + Some(r) => r, + _ => crash!(1, "no mode given"), + } + } + _ => 0o755_u16, + }; + + exec(dirs, recursive, mode, verbose) +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .arg( Arg::with_name(OPT_MODE) .short("m") @@ -62,31 +89,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .min_values(1), ) - .get_matches_from(args); - - let dirs: Vec = matches - .values_of(ARG_DIRS) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - let verbose = matches.is_present(OPT_VERBOSE); - let recursive = matches.is_present(OPT_PARENTS); - - // Translate a ~str in octal form to u16, default to 755 - // Not tested on Windows - let mode_match = matches.value_of(OPT_MODE); - let mode: u16 = match mode_match { - Some(m) => { - let res: Option = u16::from_str_radix(m, 8).ok(); - match res { - Some(r) => r, - _ => crash!(1, "no mode given"), - } - } - _ => 0o755_u16, - }; - - exec(dirs, recursive, mode, verbose) } /** diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index b8a6bbe38..ad12e230d 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -29,27 +29,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(USAGE) - .about(SUMMARY) - .arg( - Arg::with_name(options::MODE) - .short("m") - .long(options::MODE) - .help("file permissions for the fifo") - .default_value("0666") - .value_name("0666"), - ) - .arg( - Arg::with_name(options::SE_LINUX_SECURITY_CONTEXT) - .short(options::SE_LINUX_SECURITY_CONTEXT) - .help("set the SELinux security context to default type") - ) - .arg(Arg::with_name(options::CONTEXT).long(options::CONTEXT).value_name("CTX").help("like -Z, or if CTX is specified then set the SELinux\nor SMACK security context to CTX")) - .arg(Arg::with_name(options::FIFO).hidden(true).multiple(true)) - .get_matches_from(args); + let matches = uu_app().get_matches_from(args); if matches.is_present(options::CONTEXT) { crash!(1, "--context is not implemented"); @@ -88,3 +68,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 { exit_code } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .name(NAME) + .version(crate_version!()) + .usage(USAGE) + .about(SUMMARY) + .arg( + Arg::with_name(options::MODE) + .short("m") + .long(options::MODE) + .help("file permissions for the fifo") + .default_value("0666") + .value_name("0666"), + ) + .arg( + Arg::with_name(options::SE_LINUX_SECURITY_CONTEXT) + .short(options::SE_LINUX_SECURITY_CONTEXT) + .help("set the SELinux security context to default type") + ) + .arg(Arg::with_name(options::CONTEXT).long(options::CONTEXT).value_name("CTX").help("like -Z, or if CTX is specified then set the SELinux\nor SMACK security context to CTX")) + .arg(Arg::with_name(options::FIFO).hidden(true).multiple(true)) +} diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index a1f361e55..8cc7db908 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -89,48 +89,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // opts.optflag("Z", "", "set the SELinux security context to default type"); // opts.optopt("", "context", "like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX"); - let matches = App::new(executable!()) - .version(crate_version!()) - .usage(USAGE) - .after_help(LONG_HELP) - .about(ABOUT) - .arg( - Arg::with_name("mode") - .short("m") - .long("mode") - .value_name("MODE") - .help("set file permission bits to MODE, not a=rw - umask"), - ) - .arg( - Arg::with_name("name") - .value_name("NAME") - .help("name of the new file") - .required(true) - .index(1), - ) - .arg( - Arg::with_name("type") - .value_name("TYPE") - .help("type of the new file (b, c, u or p)") - .required(true) - .validator(valid_type) - .index(2), - ) - .arg( - Arg::with_name("major") - .value_name("MAJOR") - .help("major file type") - .validator(valid_u64) - .index(3), - ) - .arg( - Arg::with_name("minor") - .value_name("MINOR") - .help("minor file type") - .validator(valid_u64) - .index(4), - ) - .get_matches_from(args); + let matches = uu_app().get_matches_from(args); let mode = match get_mode(&matches) { Ok(mode) => mode, @@ -185,6 +144,50 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .usage(USAGE) + .after_help(LONG_HELP) + .about(ABOUT) + .arg( + Arg::with_name("mode") + .short("m") + .long("mode") + .value_name("MODE") + .help("set file permission bits to MODE, not a=rw - umask"), + ) + .arg( + Arg::with_name("name") + .value_name("NAME") + .help("name of the new file") + .required(true) + .index(1), + ) + .arg( + Arg::with_name("type") + .value_name("TYPE") + .help("type of the new file (b, c, u or p)") + .required(true) + .validator(valid_type) + .index(2), + ) + .arg( + Arg::with_name("major") + .value_name("MAJOR") + .help("major file type") + .validator(valid_u64) + .index(3), + ) + .arg( + Arg::with_name("minor") + .value_name("MINOR") + .help("minor file type") + .validator(valid_u64) + .index(4), + ) +} + fn get_mode(matches: &ArgMatches) -> Result { match matches.value_of("mode") { None => Ok(MODE_RW_UGO), diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index a052766e8..bbccf6628 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -40,61 +40,7 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(OPT_DIRECTORY) - .short("d") - .long(OPT_DIRECTORY) - .help("Make a directory instead of a file"), - ) - .arg( - Arg::with_name(OPT_DRY_RUN) - .short("u") - .long(OPT_DRY_RUN) - .help("do not create anything; merely print a name (unsafe)"), - ) - .arg( - Arg::with_name(OPT_QUIET) - .short("q") - .long("quiet") - .help("Fail silently if an error occurs."), - ) - .arg( - Arg::with_name(OPT_SUFFIX) - .long(OPT_SUFFIX) - .help( - "append SUFFIX to TEMPLATE; SUFFIX must not contain a path separator. \ - This option is implied if TEMPLATE does not end with X.", - ) - .value_name("SUFFIX"), - ) - .arg( - Arg::with_name(OPT_TMPDIR) - .short("p") - .long(OPT_TMPDIR) - .help( - "interpret TEMPLATE relative to DIR; if DIR is not specified, use \ - $TMPDIR ($TMP on windows) if set, else /tmp. With this option, TEMPLATE must not \ - be an absolute name; unlike with -t, TEMPLATE may contain \ - slashes, but mktemp creates only the final component", - ) - .value_name("DIR"), - ) - .arg(Arg::with_name(OPT_T).short(OPT_T).help( - "Generate a template (using the supplied prefix and TMPDIR (TMP on windows) if set) \ - to create a filename template [deprecated]", - )) - .arg( - Arg::with_name(ARG_TEMPLATE) - .multiple(false) - .takes_value(true) - .max_values(1) - .default_value(DEFAULT_TEMPLATE), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let template = matches.value_of(ARG_TEMPLATE).unwrap(); let tmpdir = matches.value_of(OPT_TMPDIR).unwrap_or_default(); @@ -171,6 +117,62 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_DIRECTORY) + .short("d") + .long(OPT_DIRECTORY) + .help("Make a directory instead of a file"), + ) + .arg( + Arg::with_name(OPT_DRY_RUN) + .short("u") + .long(OPT_DRY_RUN) + .help("do not create anything; merely print a name (unsafe)"), + ) + .arg( + Arg::with_name(OPT_QUIET) + .short("q") + .long("quiet") + .help("Fail silently if an error occurs."), + ) + .arg( + Arg::with_name(OPT_SUFFIX) + .long(OPT_SUFFIX) + .help( + "append SUFFIX to TEMPLATE; SUFFIX must not contain a path separator. \ + This option is implied if TEMPLATE does not end with X.", + ) + .value_name("SUFFIX"), + ) + .arg( + Arg::with_name(OPT_TMPDIR) + .short("p") + .long(OPT_TMPDIR) + .help( + "interpret TEMPLATE relative to DIR; if DIR is not specified, use \ + $TMPDIR ($TMP on windows) if set, else /tmp. With this option, TEMPLATE must not \ + be an absolute name; unlike with -t, TEMPLATE may contain \ + slashes, but mktemp creates only the final component", + ) + .value_name("DIR"), + ) + .arg(Arg::with_name(OPT_T).short(OPT_T).help( + "Generate a template (using the supplied prefix and TMPDIR (TMP on windows) if set) \ + to create a filename template [deprecated]", + )) + .arg( + Arg::with_name(ARG_TEMPLATE) + .multiple(false) + .takes_value(true) + .max_values(1) + .default_value(DEFAULT_TEMPLATE), + ) +} + fn parse_template(temp: &str) -> Option<(&str, usize, &str)> { let right = match temp.rfind('X') { Some(r) => r + 1, diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index d7fba5080..8f25cd7e4 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -51,7 +51,49 @@ pub mod options { const MULTI_FILE_TOP_PROMPT: &str = "::::::::::::::\n{}\n::::::::::::::\n"; pub fn uumain(args: impl uucore::Args) -> i32 { - let matches = App::new(executable!()) + let matches = uu_app().get_matches_from(args); + + let mut buff = String::new(); + let silent = matches.is_present(options::SILENT); + if let Some(files) = matches.values_of(options::FILES) { + let mut stdout = setup_term(); + let length = files.len(); + + let mut files_iter = files.peekable(); + while let (Some(file), next_file) = (files_iter.next(), files_iter.peek()) { + let file = Path::new(file); + if file.is_dir() { + terminal::disable_raw_mode().unwrap(); + show_usage_error!("'{}' is a directory.", file.display()); + return 1; + } + if !file.exists() { + terminal::disable_raw_mode().unwrap(); + show_error!("cannot open {}: No such file or directory", file.display()); + return 1; + } + if length > 1 { + buff.push_str(&MULTI_FILE_TOP_PROMPT.replace("{}", file.to_str().unwrap())); + } + let mut reader = BufReader::new(File::open(file).unwrap()); + reader.read_to_string(&mut buff).unwrap(); + more(&buff, &mut stdout, next_file.copied(), silent); + buff.clear(); + } + reset_term(&mut stdout); + } else if atty::isnt(atty::Stream::Stdin) { + stdin().read_to_string(&mut buff).unwrap(); + let mut stdout = setup_term(); + more(&buff, &mut stdout, None, silent); + reset_term(&mut stdout); + } else { + show_usage_error!("bad usage"); + } + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .about("A file perusal filter for CRT viewing.") .version(crate_version!()) .arg( @@ -138,45 +180,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .multiple(true) .help("Path to the files to be read"), ) - .get_matches_from(args); - - let mut buff = String::new(); - let silent = matches.is_present(options::SILENT); - if let Some(files) = matches.values_of(options::FILES) { - let mut stdout = setup_term(); - let length = files.len(); - - let mut files_iter = files.peekable(); - while let (Some(file), next_file) = (files_iter.next(), files_iter.peek()) { - let file = Path::new(file); - if file.is_dir() { - terminal::disable_raw_mode().unwrap(); - show_usage_error!("'{}' is a directory.", file.display()); - return 1; - } - if !file.exists() { - terminal::disable_raw_mode().unwrap(); - show_error!("cannot open {}: No such file or directory", file.display()); - return 1; - } - if length > 1 { - buff.push_str(&MULTI_FILE_TOP_PROMPT.replace("{}", file.to_str().unwrap())); - } - let mut reader = BufReader::new(File::open(file).unwrap()); - reader.read_to_string(&mut buff).unwrap(); - more(&buff, &mut stdout, next_file.copied(), silent); - buff.clear(); - } - reset_term(&mut stdout); - } else if atty::isnt(atty::Stream::Stdin) { - stdin().read_to_string(&mut buff).unwrap(); - let mut stdout = setup_term(); - more(&buff, &mut stdout, None, silent); - reset_term(&mut stdout); - } else { - show_usage_error!("bad usage"); - } - 0 } #[cfg(not(target_os = "fuchsia"))] diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index d709a2117..4a761861f 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -70,11 +70,64 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) + let matches = uu_app() + .after_help(&*format!( + "{}\n{}", + LONG_HELP, + backup_control::BACKUP_CONTROL_LONG_HELP + )) + .usage(&usage[..]) + .get_matches_from(args); + + let files: Vec = matches + .values_of(ARG_FILES) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + let overwrite_mode = determine_overwrite_mode(&matches); + let backup_mode = backup_control::determine_backup_mode( + matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP), + matches.value_of(OPT_BACKUP), + ); + + if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup { + show_usage_error!("options --backup and --no-clobber are mutually exclusive"); + return 1; + } + + let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)); + + let behavior = Behavior { + overwrite: overwrite_mode, + backup: backup_mode, + suffix: backup_suffix, + update: matches.is_present(OPT_UPDATE), + target_dir: matches.value_of(OPT_TARGET_DIRECTORY).map(String::from), + no_target_dir: matches.is_present(OPT_NO_TARGET_DIRECTORY), + verbose: matches.is_present(OPT_VERBOSE), + }; + + let paths: Vec = { + fn strip_slashes(p: &Path) -> &Path { + p.components().as_path() + } + let to_owned = |p: &Path| p.to_owned(); + let paths = files.iter().map(Path::new); + + if matches.is_present(OPT_STRIP_TRAILING_SLASHES) { + paths.map(strip_slashes).map(to_owned).collect() + } else { + paths.map(to_owned).collect() + } + }; + + exec(&paths[..], behavior) +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP)) - .usage(&usage[..]) .arg( Arg::with_name(OPT_BACKUP) .long(OPT_BACKUP) @@ -153,51 +206,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .min_values(2) .required(true) ) - .get_matches_from(args); - - let files: Vec = matches - .values_of(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - let overwrite_mode = determine_overwrite_mode(&matches); - let backup_mode = backup_control::determine_backup_mode( - matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP), - matches.value_of(OPT_BACKUP), - ); - - if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup { - show_usage_error!("options --backup and --no-clobber are mutually exclusive"); - return 1; - } - - let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)); - - let behavior = Behavior { - overwrite: overwrite_mode, - backup: backup_mode, - suffix: backup_suffix, - update: matches.is_present(OPT_UPDATE), - target_dir: matches.value_of(OPT_TARGET_DIRECTORY).map(String::from), - no_target_dir: matches.is_present(OPT_NO_TARGET_DIRECTORY), - verbose: matches.is_present(OPT_VERBOSE), - }; - - let paths: Vec = { - fn strip_slashes(p: &Path) -> &Path { - p.components().as_path() - } - let to_owned = |p: &Path| p.to_owned(); - let paths = files.iter().map(Path::new); - - if matches.is_present(OPT_STRIP_TRAILING_SLASHES) { - paths.map(strip_slashes).map(to_owned).collect() - } else { - paths.map(to_owned).collect() - } - }; - - exec(&paths[..], behavior) } fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode { diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 77baad0ca..d5a4094d1 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -46,20 +46,7 @@ process).", pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .setting(AppSettings::TrailingVarArg) - .version(crate_version!()) - .usage(&usage[..]) - .arg( - Arg::with_name(options::ADJUSTMENT) - .short("n") - .long(options::ADJUSTMENT) - .help("add N to the niceness (default is 10)") - .takes_value(true) - .allow_hyphen_values(true), - ) - .arg(Arg::with_name(options::COMMAND).multiple(true)) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let mut niceness = unsafe { nix::errno::Errno::clear(); @@ -120,3 +107,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 126 } } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .setting(AppSettings::TrailingVarArg) + .version(crate_version!()) + .arg( + Arg::with_name(options::ADJUSTMENT) + .short("n") + .long(options::ADJUSTMENT) + .help("add N to the niceness (default is 10)") + .takes_value(true) + .allow_hyphen_values(true), + ) + .arg(Arg::with_name(options::COMMAND).multiple(true)) +} diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index a3181e11f..81e76aa26 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -88,7 +88,62 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) + let matches = uu_app().get_matches_from(args); + + // A mutable settings object, initialized with the defaults. + let mut settings = Settings { + header_numbering: NumberingStyle::NumberForNone, + body_numbering: NumberingStyle::NumberForAll, + footer_numbering: NumberingStyle::NumberForNone, + section_delimiter: ['\\', ':'], + starting_line_number: 1, + line_increment: 1, + join_blank_lines: 1, + number_width: 6, + number_format: NumberFormat::Right, + renumber: true, + number_separator: String::from("\t"), + }; + + // Update the settings from the command line options, and terminate the + // program if some options could not successfully be parsed. + let parse_errors = helper::parse_options(&mut settings, &matches); + if !parse_errors.is_empty() { + show_error!("Invalid arguments supplied."); + for message in &parse_errors { + println!("{}", message); + } + return 1; + } + + let mut read_stdin = false; + let files: Vec = match matches.values_of(options::FILE) { + Some(v) => v.clone().map(|v| v.to_owned()).collect(), + None => vec!["-".to_owned()], + }; + + for file in &files { + if file == "-" { + // If both file names and '-' are specified, we choose to treat first all + // regular files, and then read from stdin last. + read_stdin = true; + continue; + } + let path = Path::new(file); + let reader = File::open(path).unwrap(); + let mut buffer = BufReader::new(reader); + nl(&mut buffer, &settings); + } + + if read_stdin { + let mut buffer = BufReader::new(stdin()); + nl(&mut buffer, &settings); + } + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .name(NAME) .version(crate_version!()) .usage(USAGE) @@ -169,58 +224,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("use NUMBER columns for line numbers") .value_name("NUMBER"), ) - .get_matches_from(args); - - // A mutable settings object, initialized with the defaults. - let mut settings = Settings { - header_numbering: NumberingStyle::NumberForNone, - body_numbering: NumberingStyle::NumberForAll, - footer_numbering: NumberingStyle::NumberForNone, - section_delimiter: ['\\', ':'], - starting_line_number: 1, - line_increment: 1, - join_blank_lines: 1, - number_width: 6, - number_format: NumberFormat::Right, - renumber: true, - number_separator: String::from("\t"), - }; - - // Update the settings from the command line options, and terminate the - // program if some options could not successfully be parsed. - let parse_errors = helper::parse_options(&mut settings, &matches); - if !parse_errors.is_empty() { - show_error!("Invalid arguments supplied."); - for message in &parse_errors { - println!("{}", message); - } - return 1; - } - - let mut read_stdin = false; - let files: Vec = match matches.values_of(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), - None => vec!["-".to_owned()], - }; - - for file in &files { - if file == "-" { - // If both file names and '-' are specified, we choose to treat first all - // regular files, and then read from stdin last. - read_stdin = true; - continue; - } - let path = Path::new(file); - let reader = File::open(path).unwrap(); - let mut buffer = BufReader::new(reader); - nl(&mut buffer, &settings); - } - - if read_stdin { - let mut buffer = BufReader::new(stdin()); - nl(&mut buffer, &settings); - } - 0 } // nl implements the main functionality for an individual buffer. diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 4e6fd7a7e..acc101e4e 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -45,19 +45,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::CMD) - .hidden(true) - .required(true) - .multiple(true), - ) - .setting(AppSettings::TrailingVarArg) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); replace_fds(); @@ -82,6 +70,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::CMD) + .hidden(true) + .required(true) + .multiple(true), + ) + .setting(AppSettings::TrailingVarArg) +} + fn replace_fds() { if atty::is(atty::Stream::Stdin) { let new_stdin = match File::open(Path::new("/dev/null")) { diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index 13f1862d2..1f284685b 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -33,24 +33,7 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(OPT_ALL) - .short("") - .long(OPT_ALL) - .help("print the number of cores available to the system"), - ) - .arg( - Arg::with_name(OPT_IGNORE) - .short("") - .long(OPT_IGNORE) - .takes_value(true) - .help("ignore up to N cores"), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let mut ignore = match matches.value_of(OPT_IGNORE) { Some(numstr) => match numstr.parse() { @@ -86,6 +69,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_ALL) + .short("") + .long(OPT_ALL) + .help("print the number of cores available to the system"), + ) + .arg( + Arg::with_name(OPT_IGNORE) + .short("") + .long(OPT_IGNORE) + .takes_value(true) + .help("ignore up to N cores"), + ) +} + #[cfg(any( target_os = "linux", target_vendor = "apple", diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index b534a9789..01f12c51b 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -156,10 +156,28 @@ fn parse_options(args: &ArgMatches) -> Result { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + let result = + parse_options(&matches).and_then(|options| match matches.values_of(options::NUMBER) { + Some(values) => handle_args(values, options), + None => handle_stdin(options), + }); + + match result { + Err(e) => { + std::io::stdout().flush().expect("error flushing stdout"); + show_error!("{}", e); + 1 + } + _ => 0, + } +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .after_help(LONG_HELP) .setting(AppSettings::AllowNegativeNumbers) .arg( @@ -224,20 +242,4 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .possible_values(&["up", "down", "from-zero", "towards-zero", "nearest"]), ) .arg(Arg::with_name(options::NUMBER).hidden(true).multiple(true)) - .get_matches_from(args); - - let result = - parse_options(&matches).and_then(|options| match matches.values_of(options::NUMBER) { - Some(values) => handle_args(values, options), - None => handle_stdin(options), - }); - - match result { - Err(e) => { - std::io::stdout().flush().expect("error flushing stdout"); - show_error!("{}", e); - 1 - } - _ => 0, - } } diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index bf6c39011..ec5bb595a 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -214,7 +214,45 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let clap_opts = clap::App::new(executable!()) + let clap_opts = uu_app(); + + let clap_matches = clap_opts + .clone() // Clone to reuse clap_opts to print help + .get_matches_from(args.clone()); + + let od_options = match OdOptions::new(clap_matches, args) { + Err(s) => { + crash!(1, "{}", s); + } + Ok(o) => o, + }; + + let mut input_offset = + InputOffset::new(od_options.radix, od_options.skip_bytes, od_options.label); + + let mut input = open_input_peek_reader( + &od_options.input_strings, + od_options.skip_bytes, + od_options.read_bytes, + ); + let mut input_decoder = InputDecoder::new( + &mut input, + od_options.line_bytes, + PEEK_BUFFER_SIZE, + od_options.byte_order, + ); + + let output_info = OutputInfo::new( + od_options.line_bytes, + &od_options.formats[..], + od_options.output_duplicates, + ); + + odfunc(&mut input_offset, &mut input_decoder, &output_info) +} + +pub fn uu_app() -> clap::App<'static, 'static> { + clap::App::new(executable!()) .version(crate_version!()) .about(ABOUT) .usage(USAGE) @@ -434,41 +472,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { AppSettings::DontDelimitTrailingValues, AppSettings::DisableVersion, AppSettings::DeriveDisplayOrder, - ]); - - let clap_matches = clap_opts - .clone() // Clone to reuse clap_opts to print help - .get_matches_from(args.clone()); - - let od_options = match OdOptions::new(clap_matches, args) { - Err(s) => { - crash!(1, "{}", s); - } - Ok(o) => o, - }; - - let mut input_offset = - InputOffset::new(od_options.radix, od_options.skip_bytes, od_options.label); - - let mut input = open_input_peek_reader( - &od_options.input_strings, - od_options.skip_bytes, - od_options.read_bytes, - ); - let mut input_decoder = InputDecoder::new( - &mut input, - od_options.line_bytes, - PEEK_BUFFER_SIZE, - od_options.byte_order, - ); - - let output_info = OutputInfo::new( - od_options.line_bytes, - &od_options.formats[..], - od_options.output_duplicates, - ); - - odfunc(&mut input_offset, &mut input_decoder, &output_info) + ]) } /// Loops through the input line by line, calling print_bytes to take care of the output. diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index f2fa3c81c..7f7969687 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -37,7 +37,22 @@ fn read_line( } pub fn uumain(args: impl uucore::Args) -> i32 { - let matches = App::new(executable!()) + let matches = uu_app().get_matches_from(args); + + let serial = matches.is_present(options::SERIAL); + let delimiters = matches.value_of(options::DELIMITER).unwrap().to_owned(); + let files = matches + .values_of(options::FILE) + .unwrap() + .map(|s| s.to_owned()) + .collect(); + paste(files, serial, delimiters); + + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) .arg( @@ -61,18 +76,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .multiple(true) .default_value("-"), ) - .get_matches_from(args); - - let serial = matches.is_present(options::SERIAL); - let delimiters = matches.value_of(options::DELIMITER).unwrap().to_owned(); - let files = matches - .values_of(options::FILE) - .unwrap() - .map(|s| s.to_owned()) - .collect(); - paste(files, serial, delimiters); - - 0 } fn paste(filenames: Vec, serial: bool, delimiters: String) { diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 358881509..335266456 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -49,27 +49,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::POSIX) - .short("p") - .help("check for most POSIX systems"), - ) - .arg( - Arg::with_name(options::POSIX_SPECIAL) - .short("P") - .help(r#"check for empty names and leading "-""#), - ) - .arg( - Arg::with_name(options::PORTABILITY) - .long(options::PORTABILITY) - .help("check for all POSIX systems (equivalent to -p -P)"), - ) - .arg(Arg::with_name(options::PATH).hidden(true).multiple(true)) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); // set working mode let is_posix = matches.values_of(options::POSIX).is_some(); @@ -115,6 +95,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::POSIX) + .short("p") + .help("check for most POSIX systems"), + ) + .arg( + Arg::with_name(options::POSIX_SPECIAL) + .short("P") + .help(r#"check for empty names and leading "-""#), + ) + .arg( + Arg::with_name(options::PORTABILITY) + .long(options::PORTABILITY) + .help("check for all POSIX systems (equivalent to -p -P)"), + ) + .arg(Arg::with_name(options::PATH).hidden(true).multiple(true)) +} + // check a path, given as a slice of it's components and an operating mode fn check_path(mode: &Mode, path: &[String]) -> bool { match *mode { diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 33dcff274..16bcfd3c9 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -60,62 +60,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&after_help[..]) - .arg( - Arg::with_name(options::LONG_FORMAT) - .short("l") - .requires(options::USER) - .help("produce long format output for the specified USERs"), - ) - .arg( - Arg::with_name(options::OMIT_HOME_DIR) - .short("b") - .help("omit the user's home directory and shell in long format"), - ) - .arg( - Arg::with_name(options::OMIT_PROJECT_FILE) - .short("h") - .help("omit the user's project file in long format"), - ) - .arg( - Arg::with_name(options::OMIT_PLAN_FILE) - .short("p") - .help("omit the user's plan file in long format"), - ) - .arg( - Arg::with_name(options::SHORT_FORMAT) - .short("s") - .help("do short format output, this is the default"), - ) - .arg( - Arg::with_name(options::OMIT_HEADINGS) - .short("f") - .help("omit the line of column headings in short format"), - ) - .arg( - Arg::with_name(options::OMIT_NAME) - .short("w") - .help("omit the user's full name in short format"), - ) - .arg( - Arg::with_name(options::OMIT_NAME_HOST) - .short("i") - .help("omit the user's full name and remote host in short format"), - ) - .arg( - Arg::with_name(options::OMIT_NAME_HOST_TIME) - .short("q") - .help("omit the user's full name, remote host and idle time in short format"), - ) - .arg( - Arg::with_name(options::USER) - .takes_value(true) - .multiple(true), - ) .get_matches_from(args); let users: Vec = matches @@ -182,6 +129,63 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::LONG_FORMAT) + .short("l") + .requires(options::USER) + .help("produce long format output for the specified USERs"), + ) + .arg( + Arg::with_name(options::OMIT_HOME_DIR) + .short("b") + .help("omit the user's home directory and shell in long format"), + ) + .arg( + Arg::with_name(options::OMIT_PROJECT_FILE) + .short("h") + .help("omit the user's project file in long format"), + ) + .arg( + Arg::with_name(options::OMIT_PLAN_FILE) + .short("p") + .help("omit the user's plan file in long format"), + ) + .arg( + Arg::with_name(options::SHORT_FORMAT) + .short("s") + .help("do short format output, this is the default"), + ) + .arg( + Arg::with_name(options::OMIT_HEADINGS) + .short("f") + .help("omit the line of column headings in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME) + .short("w") + .help("omit the user's full name in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME_HOST) + .short("i") + .help("omit the user's full name and remote host in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME_HOST_TIME) + .short("q") + .help("omit the user's full name, remote host and idle time in short format"), + ) + .arg( + Arg::with_name(options::USER) + .takes_value(true) + .multiple(true), + ) +} + struct Pinky { include_idle: bool, include_heading: bool, diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index 6d9ec2304..de519161a 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/pr.rs" [dependencies] +clap = "2.33.3" uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["utmpx", "entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } getopts = "0.2.21" diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index 239a0970f..d6b9e8ca3 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -23,6 +23,7 @@ use std::fs::{metadata, File}; use std::io::{stdin, stdout, BufRead, BufReader, Lines, Read, Stdout, Write}; #[cfg(unix)] use std::os::unix::fs::FileTypeExt; +use uucore::executable; type IOError = std::io::Error; @@ -167,6 +168,11 @@ quick_error! { } } +pub fn uu_app() -> clap::App<'static, 'static> { + // TODO: migrate to clap to get more shell completions + clap::App::new(executable!()) +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(uucore::InvalidEncodingHandling::Ignore) diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 5c2594835..6e0ca7157 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -26,23 +26,7 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(OPT_NULL) - .short("0") - .long(OPT_NULL) - .help("end each output line with 0 byte rather than newline"), - ) - .arg( - Arg::with_name(ARG_VARIABLES) - .multiple(true) - .takes_value(true) - .min_values(1), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let variables: Vec = matches .values_of(ARG_VARIABLES) @@ -69,3 +53,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_NULL) + .short("0") + .long(OPT_NULL) + .help("end each output line with 0 byte rather than newline"), + ) + .arg( + Arg::with_name(ARG_VARIABLES) + .multiple(true) + .takes_value(true) + .min_values(1), + ) +} diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index bc77d31be..13d54fcca 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -18,6 +18,7 @@ edition = "2018" path = "src/printf.rs" [dependencies] +clap = "2.33.3" itertools = "0.8.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 88d18838d..efa9aea57 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -2,14 +2,18 @@ // spell-checker:ignore (change!) each's // spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr +#[macro_use] +extern crate uucore; + +use clap::{crate_version, App, Arg}; use uucore::InvalidEncodingHandling; mod cli; mod memo; mod tokenize; -static NAME: &str = "printf"; -static VERSION: &str = env!("CARGO_PKG_VERSION"); +const VERSION: &str = "version"; +const HELP: &str = "help"; static LONGHELP_LEAD: &str = "printf USAGE: printf FORMATSTRING [ARGUMENT]... @@ -290,10 +294,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if formatstr == "--help" { print!("{} {}", LONGHELP_LEAD, LONGHELP_BODY); } else if formatstr == "--version" { - println!("{} {}", NAME, VERSION); + println!("{} {}", executable!(), crate_version!()); } else { let printf_args = &args[2..]; memo::Memo::run_all(formatstr, printf_args); } 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .arg(Arg::with_name(VERSION).long(VERSION)) + .arg(Arg::with_name(HELP).long(HELP)) +} diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 31da8f05d..01b14bc4d 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -638,7 +638,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .accept_any(); // let mut opts = Options::new(); - let matches = App::new(executable!()) + let matches = uu_app().get_matches_from(args); + + let input_files: Vec = match &matches.values_of(options::FILE) { + Some(v) => v.clone().map(|v| v.to_owned()).collect(), + None => vec!["-".to_string()], + }; + + let config = get_config(&matches); + let word_filter = WordFilter::new(&matches, &config); + let file_map = read_input(&input_files, &config); + let word_set = create_word_set(&config, &word_filter, &file_map); + let output_file = if !config.gnu_ext && matches.args.len() == 2 { + matches.value_of(options::FILE).unwrap_or("-").to_string() + } else { + "-".to_owned() + }; + write_traditional_output(&config, &file_map, &word_set, &output_file); + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .name(NAME) .version(crate_version!()) .usage(BRIEF) @@ -762,22 +783,4 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .value_name("NUMBER") .takes_value(true), ) - .get_matches_from(args); - - let input_files: Vec = match &matches.values_of(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), - None => vec!["-".to_string()], - }; - - let config = get_config(&matches); - let word_filter = WordFilter::new(&matches, &config); - let file_map = read_input(&input_files, &config); - let word_set = create_word_set(&config, &word_filter, &file_map); - let output_file = if !config.gnu_ext && matches.args.len() == 2 { - matches.value_of(options::FILE).unwrap_or("-").to_string() - } else { - "-".to_owned() - }; - write_traditional_output(&config, &file_map, &word_set, &output_file); - 0 } diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 9b4e5c600..764a63a88 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -39,23 +39,7 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(OPT_LOGICAL) - .short("L") - .long(OPT_LOGICAL) - .help("use PWD from environment, even if it contains symlinks"), - ) - .arg( - Arg::with_name(OPT_PHYSICAL) - .short("P") - .long(OPT_PHYSICAL) - .help("avoid all symlinks"), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); match env::current_dir() { Ok(logical_path) => { @@ -73,3 +57,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_LOGICAL) + .short("L") + .long(OPT_LOGICAL) + .help("use PWD from environment, even if it contains symlinks"), + ) + .arg( + Arg::with_name(OPT_PHYSICAL) + .short("P") + .long(OPT_PHYSICAL) + .help("avoid all symlinks"), + ) +} diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 02e286315..826fa0254 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -35,69 +35,7 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(OPT_CANONICALIZE) - .short("f") - .long(OPT_CANONICALIZE) - .help( - "canonicalize by following every symlink in every component of the \ - given name recursively; all but the last component must exist", - ), - ) - .arg( - Arg::with_name(OPT_CANONICALIZE_EXISTING) - .short("e") - .long("canonicalize-existing") - .help( - "canonicalize by following every symlink in every component of the \ - given name recursively, all components must exist", - ), - ) - .arg( - Arg::with_name(OPT_CANONICALIZE_MISSING) - .short("m") - .long(OPT_CANONICALIZE_MISSING) - .help( - "canonicalize by following every symlink in every component of the \ - given name recursively, without requirements on components existence", - ), - ) - .arg( - Arg::with_name(OPT_NO_NEWLINE) - .short("n") - .long(OPT_NO_NEWLINE) - .help("do not output the trailing delimiter"), - ) - .arg( - Arg::with_name(OPT_QUIET) - .short("q") - .long(OPT_QUIET) - .help("suppress most error messages"), - ) - .arg( - Arg::with_name(OPT_SILENT) - .short("s") - .long(OPT_SILENT) - .help("suppress most error messages"), - ) - .arg( - Arg::with_name(OPT_VERBOSE) - .short("v") - .long(OPT_VERBOSE) - .help("report error message"), - ) - .arg( - Arg::with_name(OPT_ZERO) - .short("z") - .long(OPT_ZERO) - .help("separate output with NUL rather than newline"), - ) - .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let mut no_newline = matches.is_present(OPT_NO_NEWLINE); let use_zero = matches.is_present(OPT_ZERO); @@ -159,6 +97,70 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_CANONICALIZE) + .short("f") + .long(OPT_CANONICALIZE) + .help( + "canonicalize by following every symlink in every component of the \ + given name recursively; all but the last component must exist", + ), + ) + .arg( + Arg::with_name(OPT_CANONICALIZE_EXISTING) + .short("e") + .long("canonicalize-existing") + .help( + "canonicalize by following every symlink in every component of the \ + given name recursively, all components must exist", + ), + ) + .arg( + Arg::with_name(OPT_CANONICALIZE_MISSING) + .short("m") + .long(OPT_CANONICALIZE_MISSING) + .help( + "canonicalize by following every symlink in every component of the \ + given name recursively, without requirements on components existence", + ), + ) + .arg( + Arg::with_name(OPT_NO_NEWLINE) + .short("n") + .long(OPT_NO_NEWLINE) + .help("do not output the trailing delimiter"), + ) + .arg( + Arg::with_name(OPT_QUIET) + .short("q") + .long(OPT_QUIET) + .help("suppress most error messages"), + ) + .arg( + Arg::with_name(OPT_SILENT) + .short("s") + .long(OPT_SILENT) + .help("suppress most error messages"), + ) + .arg( + Arg::with_name(OPT_VERBOSE) + .short("v") + .long(OPT_VERBOSE) + .help("report error message"), + ) + .arg( + Arg::with_name(OPT_ZERO) + .short("z") + .long(OPT_ZERO) + .help("separate output with NUL rather than newline"), + ) + .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) +} + fn show(path: &Path, no_newline: bool, use_zero: bool) { let path = path.to_str().unwrap(); if use_zero { diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 1a96b7f80..fe2ad4ccc 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -29,10 +29,35 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + /* the list of files */ + + let paths: Vec = matches + .values_of(ARG_FILES) + .unwrap() + .map(PathBuf::from) + .collect(); + + let strip = matches.is_present(OPT_STRIP); + let zero = matches.is_present(OPT_ZERO); + let quiet = matches.is_present(OPT_QUIET); + let mut retcode = 0; + for path in &paths { + if let Err(e) = resolve_path(path, strip, zero) { + if !quiet { + show_error!("{}: {}", e, path.display()); + } + retcode = 1 + }; + } + retcode +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .arg( Arg::with_name(OPT_QUIET) .short("q") @@ -58,29 +83,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .required(true) .min_values(1), ) - .get_matches_from(args); - - /* the list of files */ - - let paths: Vec = matches - .values_of(ARG_FILES) - .unwrap() - .map(PathBuf::from) - .collect(); - - let strip = matches.is_present(OPT_STRIP); - let zero = matches.is_present(OPT_ZERO); - let quiet = matches.is_present(OPT_QUIET); - let mut retcode = 0; - for path in &paths { - if let Err(e) = resolve_path(path, strip, zero) { - if !quiet { - show_error!("{}: {}", e, path.display()); - } - retcode = 1 - }; - } - retcode } /// Resolve a path to an absolute form and print it. diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index a997e1c5f..cb0fba7cc 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -35,26 +35,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .accept_any(); let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::DIR) - .short("d") - .takes_value(true) - .help("If any of FROM and TO is not subpath of DIR, output absolute path instead of relative"), - ) - .arg( - Arg::with_name(options::TO) - .required(true) - .takes_value(true), - ) - .arg( - Arg::with_name(options::FROM) - .takes_value(true), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let to = Path::new(matches.value_of(options::TO).unwrap()).to_path_buf(); // required let from = match matches.value_of(options::FROM) { @@ -99,3 +80,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 { println!("{}", result.display()); 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::DIR) + .short("d") + .takes_value(true) + .help("If any of FROM and TO is not subpath of DIR, output absolute path instead of relative"), + ) + .arg( + Arg::with_name(options::TO) + .required(true) + .takes_value(true), + ) + .arg( + Arg::with_name(options::FROM) + .takes_value(true), + ) +} diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 40a24cea7..259d1ab39 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -77,11 +77,72 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let long_usage = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&long_usage[..]) + .get_matches_from(args); + + let files: Vec = matches + .values_of(ARG_FILES) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + let force = matches.is_present(OPT_FORCE); + + if files.is_empty() && !force { + // Still check by hand and not use clap + // Because "rm -f" is a thing + show_error!("missing an argument"); + show_error!("for help, try '{0} --help'", executable!()); + return 1; + } else { + let options = Options { + force, + interactive: { + if matches.is_present(OPT_PROMPT) { + InteractiveMode::Always + } else if matches.is_present(OPT_PROMPT_MORE) { + InteractiveMode::Once + } else if matches.is_present(OPT_INTERACTIVE) { + match matches.value_of(OPT_INTERACTIVE).unwrap() { + "none" => InteractiveMode::None, + "once" => InteractiveMode::Once, + "always" => InteractiveMode::Always, + val => crash!(1, "Invalid argument to interactive ({})", val), + } + } else { + InteractiveMode::None + } + }, + one_fs: matches.is_present(OPT_ONE_FILE_SYSTEM), + preserve_root: !matches.is_present(OPT_NO_PRESERVE_ROOT), + recursive: matches.is_present(OPT_RECURSIVE) || matches.is_present(OPT_RECURSIVE_R), + dir: matches.is_present(OPT_DIR), + verbose: matches.is_present(OPT_VERBOSE), + }; + if options.interactive == InteractiveMode::Once && (options.recursive || files.len() > 3) { + let msg = if options.recursive { + "Remove all arguments recursively? " + } else { + "Remove all arguments? " + }; + if !prompt(msg) { + return 0; + } + } + + if remove(files, options) { + return 1; + } + } + + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) .arg( Arg::with_name(OPT_FORCE) @@ -151,63 +212,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .min_values(1) ) - .get_matches_from(args); - - let files: Vec = matches - .values_of(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - let force = matches.is_present(OPT_FORCE); - - if files.is_empty() && !force { - // Still check by hand and not use clap - // Because "rm -f" is a thing - show_error!("missing an argument"); - show_error!("for help, try '{0} --help'", executable!()); - return 1; - } else { - let options = Options { - force, - interactive: { - if matches.is_present(OPT_PROMPT) { - InteractiveMode::Always - } else if matches.is_present(OPT_PROMPT_MORE) { - InteractiveMode::Once - } else if matches.is_present(OPT_INTERACTIVE) { - match matches.value_of(OPT_INTERACTIVE).unwrap() { - "none" => InteractiveMode::None, - "once" => InteractiveMode::Once, - "always" => InteractiveMode::Always, - val => crash!(1, "Invalid argument to interactive ({})", val), - } - } else { - InteractiveMode::None - } - }, - one_fs: matches.is_present(OPT_ONE_FILE_SYSTEM), - preserve_root: !matches.is_present(OPT_NO_PRESERVE_ROOT), - recursive: matches.is_present(OPT_RECURSIVE) || matches.is_present(OPT_RECURSIVE_R), - dir: matches.is_present(OPT_DIR), - verbose: matches.is_present(OPT_VERBOSE), - }; - if options.interactive == InteractiveMode::Once && (options.recursive || files.len() > 3) { - let msg = if options.recursive { - "Remove all arguments recursively? " - } else { - "Remove all arguments? " - }; - if !prompt(msg) { - return 0; - } - } - - if remove(files, options) { - return 1; - } - } - - 0 } // TODO: implement one-file-system (this may get partially implemented in walkdir) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index fc22cca09..8dbaf79a8 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -33,10 +33,29 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + let dirs: Vec = matches + .values_of(ARG_DIRS) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + let ignore = matches.is_present(OPT_IGNORE_FAIL_NON_EMPTY); + let parents = matches.is_present(OPT_PARENTS); + let verbose = matches.is_present(OPT_VERBOSE); + + match remove(dirs, ignore, parents, verbose) { + Ok(()) => ( /* pass */ ), + Err(e) => return e, + } + + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .arg( Arg::with_name(OPT_IGNORE_FAIL_NON_EMPTY) .long(OPT_IGNORE_FAIL_NON_EMPTY) @@ -64,23 +83,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .min_values(1) .required(true), ) - .get_matches_from(args); - - let dirs: Vec = matches - .values_of(ARG_DIRS) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - let ignore = matches.is_present(OPT_IGNORE_FAIL_NON_EMPTY); - let parents = matches.is_present(OPT_PARENTS); - let verbose = matches.is_present(OPT_VERBOSE); - - match remove(dirs, ignore, parents, verbose) { - Ok(()) => ( /* pass */ ), - Err(e) => return e, - } - - 0 } fn remove(dirs: Vec, ignore: bool, parents: bool, verbose: bool) -> Result<(), i32> { diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 954d15f2f..50a93d3af 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -87,42 +87,7 @@ impl FromStr for Number { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .setting(AppSettings::AllowLeadingHyphen) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(OPT_SEPARATOR) - .short("s") - .long("separator") - .help("Separator character (defaults to \\n)") - .takes_value(true) - .number_of_values(1), - ) - .arg( - Arg::with_name(OPT_TERMINATOR) - .short("t") - .long("terminator") - .help("Terminator character (defaults to \\n)") - .takes_value(true) - .number_of_values(1), - ) - .arg( - Arg::with_name(OPT_WIDTHS) - .short("w") - .long("widths") - .help("Equalize widths of all numbers by padding with zeros"), - ) - .arg( - Arg::with_name(ARG_NUMBERS) - .multiple(true) - .takes_value(true) - .allow_hyphen_values(true) - .max_values(3) - .required(true), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let numbers = matches.values_of(ARG_NUMBERS).unwrap().collect::>(); @@ -197,6 +162,43 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .setting(AppSettings::AllowLeadingHyphen) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_SEPARATOR) + .short("s") + .long("separator") + .help("Separator character (defaults to \\n)") + .takes_value(true) + .number_of_values(1), + ) + .arg( + Arg::with_name(OPT_TERMINATOR) + .short("t") + .long("terminator") + .help("Terminator character (defaults to \\n)") + .takes_value(true) + .number_of_values(1), + ) + .arg( + Arg::with_name(OPT_WIDTHS) + .short("w") + .long("widths") + .help("Equalize widths of all numbers by padding with zeros"), + ) + .arg( + Arg::with_name(ARG_NUMBERS) + .multiple(true) + .takes_value(true) + .allow_hyphen_values(true) + .max_values(3) + .required(true), + ) +} + fn done_printing(next: &T, increment: &T, last: &T) -> bool { if increment >= &T::zero() { next > last diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 177143811..90336ea95 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -272,62 +272,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let app = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .usage(&usage[..]) - .arg( - Arg::with_name(options::FORCE) - .long(options::FORCE) - .short("f") - .help("change permissions to allow writing if necessary"), - ) - .arg( - Arg::with_name(options::ITERATIONS) - .long(options::ITERATIONS) - .short("n") - .help("overwrite N times instead of the default (3)") - .value_name("NUMBER") - .default_value("3"), - ) - .arg( - Arg::with_name(options::SIZE) - .long(options::SIZE) - .short("s") - .takes_value(true) - .value_name("N") - .help("shred this many bytes (suffixes like K, M, G accepted)"), - ) - .arg( - Arg::with_name(options::REMOVE) - .short("u") - .long(options::REMOVE) - .help("truncate and remove file after overwriting; See below"), - ) - .arg( - Arg::with_name(options::VERBOSE) - .long(options::VERBOSE) - .short("v") - .help("show progress"), - ) - .arg( - Arg::with_name(options::EXACT) - .long(options::EXACT) - .short("x") - .help( - "do not round file sizes up to the next full block;\n\ - this is the default for non-regular files", - ), - ) - .arg( - Arg::with_name(options::ZERO) - .long(options::ZERO) - .short("z") - .help("add a final overwrite with zeros to hide shredding"), - ) - // Positional arguments - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)); + let app = uu_app().usage(&usage[..]); let matches = app.get_matches_from(args); @@ -384,6 +329,64 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .after_help(AFTER_HELP) + .arg( + Arg::with_name(options::FORCE) + .long(options::FORCE) + .short("f") + .help("change permissions to allow writing if necessary"), + ) + .arg( + Arg::with_name(options::ITERATIONS) + .long(options::ITERATIONS) + .short("n") + .help("overwrite N times instead of the default (3)") + .value_name("NUMBER") + .default_value("3"), + ) + .arg( + Arg::with_name(options::SIZE) + .long(options::SIZE) + .short("s") + .takes_value(true) + .value_name("N") + .help("shred this many bytes (suffixes like K, M, G accepted)"), + ) + .arg( + Arg::with_name(options::REMOVE) + .short("u") + .long(options::REMOVE) + .help("truncate and remove file after overwriting; See below"), + ) + .arg( + Arg::with_name(options::VERBOSE) + .long(options::VERBOSE) + .short("v") + .help("show progress"), + ) + .arg( + Arg::with_name(options::EXACT) + .long(options::EXACT) + .short("x") + .help( + "do not round file sizes up to the next full block;\n\ + this is the default for non-regular files", + ), + ) + .arg( + Arg::with_name(options::ZERO) + .long(options::ZERO) + .short("z") + .help("add a final overwrite with zeros to hide shredding"), + ) + // Positional arguments + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) +} + // TODO: Add support for all postfixes here up to and including EiB // http://www.gnu.org/software/coreutils/manual/coreutils.html#Block-size fn get_size(size_str_opt: Option) -> Option { diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 2d1f558de..4690d1c6e 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -56,7 +56,66 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) + let matches = uu_app().get_matches_from(args); + + let mode = if let Some(args) = matches.values_of(options::ECHO) { + Mode::Echo(args.map(String::from).collect()) + } else if let Some(range) = matches.value_of(options::INPUT_RANGE) { + match parse_range(range) { + Ok(m) => Mode::InputRange(m), + Err(msg) => { + crash!(1, "{}", msg); + } + } + } else { + Mode::Default(matches.value_of(options::FILE).unwrap_or("-").to_string()) + }; + + let options = Options { + head_count: match matches.value_of(options::HEAD_COUNT) { + Some(count) => match count.parse::() { + Ok(val) => val, + Err(_) => { + show_error!("invalid line count: '{}'", count); + return 1; + } + }, + None => std::usize::MAX, + }, + output: matches.value_of(options::OUTPUT).map(String::from), + random_source: matches.value_of(options::RANDOM_SOURCE).map(String::from), + repeat: matches.is_present(options::REPEAT), + sep: if matches.is_present(options::ZERO_TERMINATED) { + 0x00_u8 + } else { + 0x0a_u8 + }, + }; + + match mode { + Mode::Echo(args) => { + let mut evec = args.iter().map(String::as_bytes).collect::>(); + find_seps(&mut evec, options.sep); + shuf_bytes(&mut evec, options); + } + Mode::InputRange((b, e)) => { + let rvec = (b..e).map(|x| format!("{}", x)).collect::>(); + let mut rvec = rvec.iter().map(String::as_bytes).collect::>(); + shuf_bytes(&mut rvec, options); + } + Mode::Default(filename) => { + let fdata = read_input_file(&filename); + let mut fdata = vec![&fdata[..]]; + find_seps(&mut fdata, options.sep); + shuf_bytes(&mut fdata, options); + } + } + + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .name(NAME) .version(crate_version!()) .template(TEMPLATE) @@ -118,62 +177,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("line delimiter is NUL, not newline"), ) .arg(Arg::with_name(options::FILE).takes_value(true)) - .get_matches_from(args); - - let mode = if let Some(args) = matches.values_of(options::ECHO) { - Mode::Echo(args.map(String::from).collect()) - } else if let Some(range) = matches.value_of(options::INPUT_RANGE) { - match parse_range(range) { - Ok(m) => Mode::InputRange(m), - Err(msg) => { - crash!(1, "{}", msg); - } - } - } else { - Mode::Default(matches.value_of(options::FILE).unwrap_or("-").to_string()) - }; - - let options = Options { - head_count: match matches.value_of(options::HEAD_COUNT) { - Some(count) => match count.parse::() { - Ok(val) => val, - Err(_) => { - show_error!("invalid line count: '{}'", count); - return 1; - } - }, - None => std::usize::MAX, - }, - output: matches.value_of(options::OUTPUT).map(String::from), - random_source: matches.value_of(options::RANDOM_SOURCE).map(String::from), - repeat: matches.is_present(options::REPEAT), - sep: if matches.is_present(options::ZERO_TERMINATED) { - 0x00_u8 - } else { - 0x0a_u8 - }, - }; - - match mode { - Mode::Echo(args) => { - let mut evec = args.iter().map(String::as_bytes).collect::>(); - find_seps(&mut evec, options.sep); - shuf_bytes(&mut evec, options); - } - Mode::InputRange((b, e)) => { - let rvec = (b..e).map(|x| format!("{}", x)).collect::>(); - let mut rvec = rvec.iter().map(String::as_bytes).collect::>(); - shuf_bytes(&mut rvec, options); - } - Mode::Default(filename) => { - let fdata = read_input_file(&filename); - let mut fdata = vec![&fdata[..]]; - find_seps(&mut fdata, options.sep); - shuf_bytes(&mut fdata, options); - } - } - - 0 } fn read_input_file(filename: &str) -> Vec { diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index c78c1cfc9..ada3336df 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -35,10 +35,20 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + if let Some(values) = matches.values_of(options::NUMBER) { + let numbers = values.collect(); + sleep(numbers); + } + + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .after_help(LONG_HELP) .arg( Arg::with_name(options::NUMBER) @@ -49,14 +59,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .multiple(true) .required(true), ) - .get_matches_from(args); - - if let Some(values) = matches.values_of(options::NUMBER) { - let numbers = values.collect(); - sleep(numbers); - } - - 0 } fn sleep(args: Vec<&str>) { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index d0e574627..fb0241945 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -944,10 +944,170 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let mut settings: GlobalSettings = Default::default(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + settings.debug = matches.is_present(options::DEBUG); + + // check whether user specified a zero terminated list of files for input, otherwise read files from args + let mut files: Vec = if matches.is_present(options::FILES0_FROM) { + let files0_from: Vec = matches + .values_of(options::FILES0_FROM) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + let mut files = Vec::new(); + for path in &files0_from { + let reader = open(path.as_str()); + let buf_reader = BufReader::new(reader); + for line in buf_reader.split(b'\0').flatten() { + files.push( + std::str::from_utf8(&line) + .expect("Could not parse string from zero terminated input.") + .to_string(), + ); + } + } + files + } else { + matches + .values_of(options::FILES) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default() + }; + + settings.mode = if matches.is_present(options::modes::HUMAN_NUMERIC) + || matches.value_of(options::modes::SORT) == Some("human-numeric") + { + SortMode::HumanNumeric + } else if matches.is_present(options::modes::MONTH) + || matches.value_of(options::modes::SORT) == Some("month") + { + SortMode::Month + } else if matches.is_present(options::modes::GENERAL_NUMERIC) + || matches.value_of(options::modes::SORT) == Some("general-numeric") + { + SortMode::GeneralNumeric + } else if matches.is_present(options::modes::NUMERIC) + || matches.value_of(options::modes::SORT) == Some("numeric") + { + SortMode::Numeric + } else if matches.is_present(options::modes::VERSION) + || matches.value_of(options::modes::SORT) == Some("version") + { + SortMode::Version + } else if matches.is_present(options::modes::RANDOM) + || matches.value_of(options::modes::SORT) == Some("random") + { + settings.salt = get_rand_string(); + SortMode::Random + } else { + SortMode::Default + }; + + settings.dictionary_order = matches.is_present(options::DICTIONARY_ORDER); + settings.ignore_non_printing = matches.is_present(options::IGNORE_NONPRINTING); + if matches.is_present(options::PARALLEL) { + // "0" is default - threads = num of cores + settings.threads = matches + .value_of(options::PARALLEL) + .map(String::from) + .unwrap_or_else(|| "0".to_string()); + env::set_var("RAYON_NUM_THREADS", &settings.threads); + } + + settings.buffer_size = matches + .value_of(options::BUF_SIZE) + .map_or(DEFAULT_BUF_SIZE, |s| { + GlobalSettings::parse_byte_count(s) + .unwrap_or_else(|e| crash!(2, "{}", format_error_message(e, s, options::BUF_SIZE))) + }); + + settings.tmp_dir = matches + .value_of(options::TMP_DIR) + .map(PathBuf::from) + .unwrap_or_else(env::temp_dir); + + settings.compress_prog = matches.value_of(options::COMPRESS_PROG).map(String::from); + + if let Some(n_merge) = matches.value_of(options::BATCH_SIZE) { + settings.merge_batch_size = n_merge + .parse() + .unwrap_or_else(|_| crash!(2, "invalid --batch-size argument '{}'", n_merge)); + } + + settings.zero_terminated = matches.is_present(options::ZERO_TERMINATED); + settings.merge = matches.is_present(options::MERGE); + + settings.check = matches.is_present(options::check::CHECK); + if matches.is_present(options::check::CHECK_SILENT) + || matches!( + matches.value_of(options::check::CHECK), + Some(options::check::SILENT) | Some(options::check::QUIET) + ) + { + settings.check_silent = true; + settings.check = true; + }; + + settings.ignore_case = matches.is_present(options::IGNORE_CASE); + + settings.ignore_leading_blanks = matches.is_present(options::IGNORE_LEADING_BLANKS); + + settings.output_file = matches.value_of(options::OUTPUT).map(String::from); + settings.reverse = matches.is_present(options::REVERSE); + settings.stable = matches.is_present(options::STABLE); + settings.unique = matches.is_present(options::UNIQUE); + + if files.is_empty() { + /* if no file, default to stdin */ + files.push("-".to_owned()); + } else if settings.check && files.len() != 1 { + crash!(1, "extra operand `{}' not allowed with -c", files[1]) + } + + if let Some(arg) = matches.args.get(options::SEPARATOR) { + let separator = arg.vals[0].to_string_lossy(); + let separator = separator; + if separator.len() != 1 { + crash!(1, "separator must be exactly one character long"); + } + settings.separator = Some(separator.chars().next().unwrap()) + } + + if let Some(values) = matches.values_of(options::KEY) { + for value in values { + settings + .selectors + .push(FieldSelector::parse(value, &settings)); + } + } + + if !matches.is_present(options::KEY) { + // add a default selector matching the whole line + let key_settings = KeySettings::from(&settings); + settings.selectors.push( + FieldSelector::new( + KeyPosition { + field: 1, + char: 1, + ignore_blanks: key_settings.ignore_blanks, + }, + None, + key_settings, + ) + .unwrap(), + ); + } + + settings.init_precomputed(); + + exec(&files, &settings) +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .arg( Arg::with_name(options::modes::SORT) .long(options::modes::SORT) @@ -1169,164 +1329,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("underline the parts of the line that are actually used for sorting"), ) .arg(Arg::with_name(options::FILES).multiple(true).takes_value(true)) - .get_matches_from(args); - - settings.debug = matches.is_present(options::DEBUG); - - // check whether user specified a zero terminated list of files for input, otherwise read files from args - let mut files: Vec = if matches.is_present(options::FILES0_FROM) { - let files0_from: Vec = matches - .values_of(options::FILES0_FROM) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - let mut files = Vec::new(); - for path in &files0_from { - let reader = open(path.as_str()); - let buf_reader = BufReader::new(reader); - for line in buf_reader.split(b'\0').flatten() { - files.push( - std::str::from_utf8(&line) - .expect("Could not parse string from zero terminated input.") - .to_string(), - ); - } - } - files - } else { - matches - .values_of(options::FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default() - }; - - settings.mode = if matches.is_present(options::modes::HUMAN_NUMERIC) - || matches.value_of(options::modes::SORT) == Some("human-numeric") - { - SortMode::HumanNumeric - } else if matches.is_present(options::modes::MONTH) - || matches.value_of(options::modes::SORT) == Some("month") - { - SortMode::Month - } else if matches.is_present(options::modes::GENERAL_NUMERIC) - || matches.value_of(options::modes::SORT) == Some("general-numeric") - { - SortMode::GeneralNumeric - } else if matches.is_present(options::modes::NUMERIC) - || matches.value_of(options::modes::SORT) == Some("numeric") - { - SortMode::Numeric - } else if matches.is_present(options::modes::VERSION) - || matches.value_of(options::modes::SORT) == Some("version") - { - SortMode::Version - } else if matches.is_present(options::modes::RANDOM) - || matches.value_of(options::modes::SORT) == Some("random") - { - settings.salt = get_rand_string(); - SortMode::Random - } else { - SortMode::Default - }; - - settings.dictionary_order = matches.is_present(options::DICTIONARY_ORDER); - settings.ignore_non_printing = matches.is_present(options::IGNORE_NONPRINTING); - if matches.is_present(options::PARALLEL) { - // "0" is default - threads = num of cores - settings.threads = matches - .value_of(options::PARALLEL) - .map(String::from) - .unwrap_or_else(|| "0".to_string()); - env::set_var("RAYON_NUM_THREADS", &settings.threads); - } - - settings.buffer_size = matches - .value_of(options::BUF_SIZE) - .map_or(DEFAULT_BUF_SIZE, |s| { - GlobalSettings::parse_byte_count(s) - .unwrap_or_else(|e| crash!(2, "{}", format_error_message(e, s, options::BUF_SIZE))) - }); - - settings.tmp_dir = matches - .value_of(options::TMP_DIR) - .map(PathBuf::from) - .unwrap_or_else(env::temp_dir); - - settings.compress_prog = matches.value_of(options::COMPRESS_PROG).map(String::from); - - if let Some(n_merge) = matches.value_of(options::BATCH_SIZE) { - settings.merge_batch_size = n_merge - .parse() - .unwrap_or_else(|_| crash!(2, "invalid --batch-size argument '{}'", n_merge)); - } - - settings.zero_terminated = matches.is_present(options::ZERO_TERMINATED); - settings.merge = matches.is_present(options::MERGE); - - settings.check = matches.is_present(options::check::CHECK); - if matches.is_present(options::check::CHECK_SILENT) - || matches!( - matches.value_of(options::check::CHECK), - Some(options::check::SILENT) | Some(options::check::QUIET) - ) - { - settings.check_silent = true; - settings.check = true; - }; - - settings.ignore_case = matches.is_present(options::IGNORE_CASE); - - settings.ignore_leading_blanks = matches.is_present(options::IGNORE_LEADING_BLANKS); - - settings.output_file = matches.value_of(options::OUTPUT).map(String::from); - settings.reverse = matches.is_present(options::REVERSE); - settings.stable = matches.is_present(options::STABLE); - settings.unique = matches.is_present(options::UNIQUE); - - if files.is_empty() { - /* if no file, default to stdin */ - files.push("-".to_owned()); - } else if settings.check && files.len() != 1 { - crash!(1, "extra operand `{}' not allowed with -c", files[1]) - } - - if let Some(arg) = matches.args.get(options::SEPARATOR) { - let separator = arg.vals[0].to_string_lossy(); - let separator = separator; - if separator.len() != 1 { - crash!(1, "separator must be exactly one character long"); - } - settings.separator = Some(separator.chars().next().unwrap()) - } - - if let Some(values) = matches.values_of(options::KEY) { - for value in values { - settings - .selectors - .push(FieldSelector::parse(value, &settings)); - } - } - - if !matches.is_present(options::KEY) { - // add a default selector matching the whole line - let key_settings = KeySettings::from(&settings); - settings.selectors.push( - FieldSelector::new( - KeyPosition { - field: 1, - char: 1, - ignore_blanks: key_settings.ignore_blanks, - }, - None, - key_settings, - ) - .unwrap(), - ); - } - - settings.init_precomputed(); - - exec(&files, &settings) } fn exec(files: &[String], settings: &GlobalSettings) -> i32 { diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index ad5c083aa..ccc98ee5e 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -30,7 +30,7 @@ static OPT_ADDITIONAL_SUFFIX: &str = "additional-suffix"; static OPT_FILTER: &str = "filter"; static OPT_NUMERIC_SUFFIXES: &str = "numeric-suffixes"; static OPT_SUFFIX_LENGTH: &str = "suffix-length"; -static OPT_DEFAULT_SUFFIX_LENGTH: usize = 2; +static OPT_DEFAULT_SUFFIX_LENGTH: &str = "2"; static OPT_VERBOSE: &str = "verbose"; static ARG_INPUT: &str = "input"; @@ -54,85 +54,10 @@ size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let long_usage = get_long_usage(); - let default_suffix_length_str = OPT_DEFAULT_SUFFIX_LENGTH.to_string(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about("Create output files containing consecutive or interleaved sections of input") + let matches = uu_app() .usage(&usage[..]) .after_help(&long_usage[..]) - // strategy (mutually exclusive) - .arg( - Arg::with_name(OPT_BYTES) - .short("b") - .long(OPT_BYTES) - .takes_value(true) - .default_value("2") - .help("use suffixes of length N (default 2)"), - ) - .arg( - Arg::with_name(OPT_LINE_BYTES) - .short("C") - .long(OPT_LINE_BYTES) - .takes_value(true) - .default_value("2") - .help("put at most SIZE bytes of lines per output file"), - ) - .arg( - Arg::with_name(OPT_LINES) - .short("l") - .long(OPT_LINES) - .takes_value(true) - .default_value("1000") - .help("write to shell COMMAND file name is $FILE (Currently not implemented for Windows)"), - ) - // rest of the arguments - .arg( - Arg::with_name(OPT_ADDITIONAL_SUFFIX) - .long(OPT_ADDITIONAL_SUFFIX) - .takes_value(true) - .default_value("") - .help("additional suffix to append to output file names"), - ) - .arg( - Arg::with_name(OPT_FILTER) - .long(OPT_FILTER) - .takes_value(true) - .help("write to shell COMMAND file name is $FILE (Currently not implemented for Windows)"), - ) - .arg( - Arg::with_name(OPT_NUMERIC_SUFFIXES) - .short("d") - .long(OPT_NUMERIC_SUFFIXES) - .takes_value(true) - .default_value("0") - .help("use numeric suffixes instead of alphabetic"), - ) - .arg( - Arg::with_name(OPT_SUFFIX_LENGTH) - .short("a") - .long(OPT_SUFFIX_LENGTH) - .takes_value(true) - .default_value(default_suffix_length_str.as_str()) - .help("use suffixes of length N (default 2)"), - ) - .arg( - Arg::with_name(OPT_VERBOSE) - .long(OPT_VERBOSE) - .help("print a diagnostic just before each output file is opened"), - ) - .arg( - Arg::with_name(ARG_INPUT) - .takes_value(true) - .default_value("-") - .index(1) - ) - .arg( - Arg::with_name(ARG_PREFIX) - .takes_value(true) - .default_value("x") - .index(2) - ) .get_matches_from(args); let mut settings = Settings { @@ -201,6 +126,84 @@ pub fn uumain(args: impl uucore::Args) -> i32 { split(&settings) } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about("Create output files containing consecutive or interleaved sections of input") + // strategy (mutually exclusive) + .arg( + Arg::with_name(OPT_BYTES) + .short("b") + .long(OPT_BYTES) + .takes_value(true) + .default_value("2") + .help("use suffixes of length N (default 2)"), + ) + .arg( + Arg::with_name(OPT_LINE_BYTES) + .short("C") + .long(OPT_LINE_BYTES) + .takes_value(true) + .default_value("2") + .help("put at most SIZE bytes of lines per output file"), + ) + .arg( + Arg::with_name(OPT_LINES) + .short("l") + .long(OPT_LINES) + .takes_value(true) + .default_value("1000") + .help("write to shell COMMAND file name is $FILE (Currently not implemented for Windows)"), + ) + // rest of the arguments + .arg( + Arg::with_name(OPT_ADDITIONAL_SUFFIX) + .long(OPT_ADDITIONAL_SUFFIX) + .takes_value(true) + .default_value("") + .help("additional suffix to append to output file names"), + ) + .arg( + Arg::with_name(OPT_FILTER) + .long(OPT_FILTER) + .takes_value(true) + .help("write to shell COMMAND file name is $FILE (Currently not implemented for Windows)"), + ) + .arg( + Arg::with_name(OPT_NUMERIC_SUFFIXES) + .short("d") + .long(OPT_NUMERIC_SUFFIXES) + .takes_value(true) + .default_value("0") + .help("use numeric suffixes instead of alphabetic"), + ) + .arg( + Arg::with_name(OPT_SUFFIX_LENGTH) + .short("a") + .long(OPT_SUFFIX_LENGTH) + .takes_value(true) + .default_value(OPT_DEFAULT_SUFFIX_LENGTH) + .help("use suffixes of length N (default 2)"), + ) + .arg( + Arg::with_name(OPT_VERBOSE) + .long(OPT_VERBOSE) + .help("print a diagnostic just before each output file is opened"), + ) + .arg( + Arg::with_name(ARG_INPUT) + .takes_value(true) + .default_value("-") + .index(1) + ) + .arg( + Arg::with_name(ARG_PREFIX) + .takes_value(true) + .default_value("x") + .index(2) + ) +} + #[allow(dead_code)] struct Settings { prefix: String, diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 7bf3db4c2..70c06bdf6 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -947,11 +947,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let long_usage = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&long_usage[..]) + .get_matches_from(args); + + match Stater::new(matches) { + Ok(stater) => stater.exec(), + Err(e) => { + show_error!("{}", e); + 1 + } + } +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) .arg( Arg::with_name(options::DEREFERENCE) .short("L") @@ -996,13 +1009,4 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .min_values(1), ) - .get_matches_from(args); - - match Stater::new(matches) { - Ok(stater) => stater.exec(), - Err(e) => { - show_error!("{}", e); - 1 - } - } } diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index fc0c83ec8..7460a2cb2 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -154,10 +154,40 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .accept_any(); let usage = get_usage(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + let options = ProgramOptions::try_from(&matches) + .unwrap_or_else(|e| crash!(125, "{}\nTry 'stdbuf --help' for more information.", e.0)); + + let mut command_values = matches.values_of::<&str>(options::COMMAND).unwrap(); + let mut command = Command::new(command_values.next().unwrap()); + let command_params: Vec<&str> = command_values.collect(); + + let mut tmp_dir = tempdir().unwrap(); + let (preload_env, libstdbuf) = return_if_err!(1, get_preload_env(&mut tmp_dir)); + command.env(preload_env, libstdbuf); + set_command_env(&mut command, "_STDBUF_I", options.stdin); + set_command_env(&mut command, "_STDBUF_O", options.stdout); + set_command_env(&mut command, "_STDBUF_E", options.stderr); + command.args(command_params); + + let mut process = match command.spawn() { + Ok(p) => p, + Err(e) => crash!(1, "failed to execute process: {}", e), + }; + match process.wait() { + Ok(status) => match status.code() { + Some(i) => i, + None => crash!(1, "process killed by signal {}", status.signal().unwrap()), + }, + Err(e) => crash!(1, "{}", e), + } +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .after_help(LONG_HELP) .setting(AppSettings::TrailingVarArg) .arg( @@ -191,32 +221,4 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .hidden(true) .required(true), ) - .get_matches_from(args); - - let options = ProgramOptions::try_from(&matches) - .unwrap_or_else(|e| crash!(125, "{}\nTry 'stdbuf --help' for more information.", e.0)); - - let mut command_values = matches.values_of::<&str>(options::COMMAND).unwrap(); - let mut command = Command::new(command_values.next().unwrap()); - let command_params: Vec<&str> = command_values.collect(); - - let mut tmp_dir = tempdir().unwrap(); - let (preload_env, libstdbuf) = return_if_err!(1, get_preload_env(&mut tmp_dir)); - command.env(preload_env, libstdbuf); - set_command_env(&mut command, "_STDBUF_I", options.stdin); - set_command_env(&mut command, "_STDBUF_O", options.stdout); - set_command_env(&mut command, "_STDBUF_E", options.stderr); - command.args(command_params); - - let mut process = match command.spawn() { - Ok(p) => p, - Err(e) => crash!(1, "failed to execute process: {}", e), - }; - match process.wait() { - Ok(status) => match status.code() { - Some(i) => i, - None => crash!(1, "process killed by signal {}", status.signal().unwrap()), - }, - Err(e) => crash!(1, "{}", e), - } } diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 4d42d7a97..0ce612859 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -98,24 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(USAGE) - .about(SUMMARY) - .arg(Arg::with_name(options::FILE).multiple(true).hidden(true)) - .arg( - Arg::with_name(options::BSD_COMPATIBLE) - .short(options::BSD_COMPATIBLE) - .help("use the BSD sum algorithm, use 1K blocks (default)"), - ) - .arg( - Arg::with_name(options::SYSTEM_V_COMPATIBLE) - .short("s") - .long(options::SYSTEM_V_COMPATIBLE) - .help("use System V sum algorithm, use 512 bytes blocks"), - ) - .get_matches_from(args); + let matches = uu_app().get_matches_from(args); let files: Vec = match matches.values_of(options::FILE) { Some(v) => v.clone().map(|v| v.to_owned()).collect(), @@ -155,3 +138,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 { exit_code } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .name(NAME) + .version(crate_version!()) + .usage(USAGE) + .about(SUMMARY) + .arg(Arg::with_name(options::FILE).multiple(true).hidden(true)) + .arg( + Arg::with_name(options::BSD_COMPATIBLE) + .short(options::BSD_COMPATIBLE) + .help("use the BSD sum algorithm, use 1K blocks (default)"), + ) + .arg( + Arg::with_name(options::SYSTEM_V_COMPATIBLE) + .short("s") + .long(options::SYSTEM_V_COMPATIBLE) + .help("use System V sum algorithm, use 512 bytes blocks"), + ) +} diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 53d1a5701..4fcdf49f9 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -166,26 +166,7 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::FILE_SYSTEM) - .short("f") - .long(options::FILE_SYSTEM) - .conflicts_with(options::DATA) - .help("sync the file systems that contain the files (Linux and Windows only)"), - ) - .arg( - Arg::with_name(options::DATA) - .short("d") - .long(options::DATA) - .conflicts_with(options::FILE_SYSTEM) - .help("sync only file data, no unneeded metadata (Linux only)"), - ) - .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let files: Vec = matches .values_of(ARG_FILES) @@ -211,6 +192,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::FILE_SYSTEM) + .short("f") + .long(options::FILE_SYSTEM) + .conflicts_with(options::DATA) + .help("sync the file systems that contain the files (Linux and Windows only)"), + ) + .arg( + Arg::with_name(options::DATA) + .short("d") + .long(options::DATA) + .conflicts_with(options::FILE_SYSTEM) + .help("sync only file data, no unneeded metadata (Linux only)"), + ) + .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) +} + fn sync() -> isize { unsafe { platform::do_sync() } } diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index be1852ec5..ae1fd9bc5 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -31,7 +31,31 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) + let matches = uu_app().get_matches_from(args); + + let before = matches.is_present(options::BEFORE); + let regex = matches.is_present(options::REGEX); + let separator = match matches.value_of(options::SEPARATOR) { + Some(m) => { + if m.is_empty() { + crash!(1, "separator cannot be empty") + } else { + m.to_owned() + } + } + None => "\n".to_owned(), + }; + + let files: Vec = match matches.values_of(options::FILE) { + Some(v) => v.map(|v| v.to_owned()).collect(), + None => vec!["-".to_owned()], + }; + + tac(files, before, regex, &separator[..]) +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .name(NAME) .version(crate_version!()) .usage(USAGE) @@ -58,27 +82,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true), ) .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .get_matches_from(args); - - let before = matches.is_present(options::BEFORE); - let regex = matches.is_present(options::REGEX); - let separator = match matches.value_of(options::SEPARATOR) { - Some(m) => { - if m.is_empty() { - crash!(1, "separator cannot be empty") - } else { - m.to_owned() - } - } - None => "\n".to_owned(), - }; - - let files: Vec = match matches.values_of(options::FILE) { - Some(v) => v.map(|v| v.to_owned()).collect(), - None => vec!["-".to_owned()], - }; - - tac(files, before, regex, &separator[..]) } fn tac(filenames: Vec, before: bool, _: bool, separator: &str) -> i32 { diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 8950886a2..4970cdcc2 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -72,74 +72,7 @@ impl Default for Settings { pub fn uumain(args: impl uucore::Args) -> i32 { let mut settings: Settings = Default::default(); - let app = App::new(executable!()) - .version(crate_version!()) - .about("output the last part of files") - // TODO: add usage - .arg( - Arg::with_name(options::BYTES) - .short("c") - .long(options::BYTES) - .takes_value(true) - .allow_hyphen_values(true) - .overrides_with_all(&[options::BYTES, options::LINES]) - .help("Number of bytes to print"), - ) - .arg( - Arg::with_name(options::FOLLOW) - .short("f") - .long(options::FOLLOW) - .help("Print the file as it grows"), - ) - .arg( - Arg::with_name(options::LINES) - .short("n") - .long(options::LINES) - .takes_value(true) - .allow_hyphen_values(true) - .overrides_with_all(&[options::BYTES, options::LINES]) - .help("Number of lines to print"), - ) - .arg( - Arg::with_name(options::PID) - .long(options::PID) - .takes_value(true) - .help("with -f, terminate after process ID, PID dies"), - ) - .arg( - Arg::with_name(options::verbosity::QUIET) - .short("q") - .long(options::verbosity::QUIET) - .visible_alias("silent") - .overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE]) - .help("never output headers giving file names"), - ) - .arg( - Arg::with_name(options::SLEEP_INT) - .short("s") - .takes_value(true) - .long(options::SLEEP_INT) - .help("Number or seconds to sleep between polling the file when running with -f"), - ) - .arg( - Arg::with_name(options::verbosity::VERBOSE) - .short("v") - .long(options::verbosity::VERBOSE) - .overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE]) - .help("always output headers giving file names"), - ) - .arg( - Arg::with_name(options::ZERO_TERM) - .short("z") - .long(options::ZERO_TERM) - .help("Line delimiter is NUL, not newline"), - ) - .arg( - Arg::with_name(options::ARG_FILES) - .multiple(true) - .takes_value(true) - .min_values(1), - ); + let app = uu_app(); let matches = app.get_matches_from(args); @@ -244,6 +177,77 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about("output the last part of files") + // TODO: add usage + .arg( + Arg::with_name(options::BYTES) + .short("c") + .long(options::BYTES) + .takes_value(true) + .allow_hyphen_values(true) + .overrides_with_all(&[options::BYTES, options::LINES]) + .help("Number of bytes to print"), + ) + .arg( + Arg::with_name(options::FOLLOW) + .short("f") + .long(options::FOLLOW) + .help("Print the file as it grows"), + ) + .arg( + Arg::with_name(options::LINES) + .short("n") + .long(options::LINES) + .takes_value(true) + .allow_hyphen_values(true) + .overrides_with_all(&[options::BYTES, options::LINES]) + .help("Number of lines to print"), + ) + .arg( + Arg::with_name(options::PID) + .long(options::PID) + .takes_value(true) + .help("with -f, terminate after process ID, PID dies"), + ) + .arg( + Arg::with_name(options::verbosity::QUIET) + .short("q") + .long(options::verbosity::QUIET) + .visible_alias("silent") + .overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE]) + .help("never output headers giving file names"), + ) + .arg( + Arg::with_name(options::SLEEP_INT) + .short("s") + .takes_value(true) + .long(options::SLEEP_INT) + .help("Number or seconds to sleep between polling the file when running with -f"), + ) + .arg( + Arg::with_name(options::verbosity::VERBOSE) + .short("v") + .long(options::verbosity::VERBOSE) + .overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE]) + .help("always output headers giving file names"), + ) + .arg( + Arg::with_name(options::ZERO_TERM) + .short("z") + .long(options::ZERO_TERM) + .help("Line delimiter is NUL, not newline"), + ) + .arg( + Arg::with_name(options::ARG_FILES) + .multiple(true) + .takes_value(true) + .min_values(1), + ) +} + fn follow(readers: &mut [BufReader], filenames: &[String], settings: &Settings) { assert!(settings.follow); let mut last = readers.len() - 1; diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index f5f24d944..a207dee63 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -39,25 +39,7 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .after_help("If a FILE is -, it refers to a file named - .") - .arg( - Arg::with_name(options::APPEND) - .long(options::APPEND) - .short("a") - .help("append to the given FILEs, do not overwrite"), - ) - .arg( - Arg::with_name(options::IGNORE_INTERRUPTS) - .long(options::IGNORE_INTERRUPTS) - .short("i") - .help("ignore interrupt signals (ignored on non-Unix platforms)"), - ) - .arg(Arg::with_name(options::FILE).multiple(true)) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let options = Options { append: matches.is_present(options::APPEND), @@ -74,6 +56,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .after_help("If a FILE is -, it refers to a file named - .") + .arg( + Arg::with_name(options::APPEND) + .long(options::APPEND) + .short("a") + .help("append to the given FILEs, do not overwrite"), + ) + .arg( + Arg::with_name(options::IGNORE_INTERRUPTS) + .long(options::IGNORE_INTERRUPTS) + .short("i") + .help("ignore interrupt signals (ignored on non-Unix platforms)"), + ) + .arg(Arg::with_name(options::FILE).multiple(true)) +} + #[cfg(unix)] fn ignore_interrupts() -> Result<()> { let ret = unsafe { libc::signal(libc::SIGINT, libc::SIG_IGN) }; diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index e1f6e62e7..cd0282a45 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/test.rs" [dependencies] +clap = "2.33.3" libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 107ad2df4..dba840d3c 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -10,9 +10,17 @@ mod parser; +use clap::{App, AppSettings}; use parser::{parse, Symbol}; use std::ffi::{OsStr, OsString}; use std::path::Path; +use uucore::executable; + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .setting(AppSettings::DisableHelpFlags) + .setting(AppSettings::DisableVersion) +} pub fn uumain(mut args: impl uucore::Args) -> i32 { let program = args.next().unwrap_or_else(|| OsString::from("test")); diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index f21a0265f..464414c5e 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -102,9 +102,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let app = App::new("timeout") + let app = uu_app().usage(&usage[..]); + + let matches = app.get_matches_from(args); + + let config = Config::from(matches); + timeout( + &config.command, + config.duration, + config.signal, + config.kill_after, + config.foreground, + config.preserve_status, + config.verbose, + ) +} + +pub fn uu_app() -> App<'static, 'static> { + App::new("timeout") .version(crate_version!()) - .usage(&usage[..]) .about(ABOUT) .arg( Arg::with_name(options::FOREGROUND) @@ -144,20 +160,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .required(true) .multiple(true) ) - .setting(AppSettings::TrailingVarArg); - - let matches = app.get_matches_from(args); - - let config = Config::from(matches); - timeout( - &config.command, - config.duration, - config.signal, - config.kill_after, - config.foreground, - config.preserve_status, - config.verbose, - ) + .setting(AppSettings::TrailingVarArg) } /// Remove pre-existing SIGCHLD handlers that would make waiting for the child's exit code fail. diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 2e1c3c8e8..3e9ff5624 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -55,80 +55,7 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::ACCESS) - .short("a") - .help("change only the access time"), - ) - .arg( - Arg::with_name(options::sources::CURRENT) - .short("t") - .help("use [[CC]YY]MMDDhhmm[.ss] instead of the current time") - .value_name("STAMP") - .takes_value(true), - ) - .arg( - Arg::with_name(options::sources::DATE) - .short("d") - .long(options::sources::DATE) - .help("parse argument and use it instead of current time") - .value_name("STRING"), - ) - .arg( - Arg::with_name(options::MODIFICATION) - .short("m") - .help("change only the modification time"), - ) - .arg( - Arg::with_name(options::NO_CREATE) - .short("c") - .long(options::NO_CREATE) - .help("do not create any files"), - ) - .arg( - Arg::with_name(options::NO_DEREF) - .short("h") - .long(options::NO_DEREF) - .help( - "affect each symbolic link instead of any referenced file \ - (only for systems that can change the timestamps of a symlink)", - ), - ) - .arg( - Arg::with_name(options::sources::REFERENCE) - .short("r") - .long(options::sources::REFERENCE) - .help("use this file's times instead of the current time") - .value_name("FILE"), - ) - .arg( - Arg::with_name(options::TIME) - .long(options::TIME) - .help( - "change only the specified time: \"access\", \"atime\", or \ - \"use\" are equivalent to -a; \"modify\" or \"mtime\" are \ - equivalent to -m", - ) - .value_name("WORD") - .possible_values(&["access", "atime", "use"]) - .takes_value(true), - ) - .arg( - Arg::with_name(ARG_FILES) - .multiple(true) - .takes_value(true) - .min_values(1), - ) - .group(ArgGroup::with_name(options::SOURCES).args(&[ - options::sources::CURRENT, - options::sources::DATE, - options::sources::REFERENCE, - ])) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let files: Vec = matches .values_of(ARG_FILES) @@ -236,6 +163,81 @@ pub fn uumain(args: impl uucore::Args) -> i32 { error_code } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::ACCESS) + .short("a") + .help("change only the access time"), + ) + .arg( + Arg::with_name(options::sources::CURRENT) + .short("t") + .help("use [[CC]YY]MMDDhhmm[.ss] instead of the current time") + .value_name("STAMP") + .takes_value(true), + ) + .arg( + Arg::with_name(options::sources::DATE) + .short("d") + .long(options::sources::DATE) + .help("parse argument and use it instead of current time") + .value_name("STRING"), + ) + .arg( + Arg::with_name(options::MODIFICATION) + .short("m") + .help("change only the modification time"), + ) + .arg( + Arg::with_name(options::NO_CREATE) + .short("c") + .long(options::NO_CREATE) + .help("do not create any files"), + ) + .arg( + Arg::with_name(options::NO_DEREF) + .short("h") + .long(options::NO_DEREF) + .help( + "affect each symbolic link instead of any referenced file \ + (only for systems that can change the timestamps of a symlink)", + ), + ) + .arg( + Arg::with_name(options::sources::REFERENCE) + .short("r") + .long(options::sources::REFERENCE) + .help("use this file's times instead of the current time") + .value_name("FILE"), + ) + .arg( + Arg::with_name(options::TIME) + .long(options::TIME) + .help( + "change only the specified time: \"access\", \"atime\", or \ + \"use\" are equivalent to -a; \"modify\" or \"mtime\" are \ + equivalent to -m", + ) + .value_name("WORD") + .possible_values(&["access", "atime", "use"]) + .takes_value(true), + ) + .arg( + Arg::with_name(ARG_FILES) + .multiple(true) + .takes_value(true) + .min_values(1), + ) + .group(ArgGroup::with_name(options::SOURCES).args(&[ + options::sources::CURRENT, + options::sources::DATE, + options::sources::REFERENCE, + ])) +} + fn stat(path: &str, follow: bool) -> (FileTime, FileTime) { let metadata = if follow { fs::symlink_metadata(path) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 9916af7db..28ce70c22 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -249,46 +249,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&after_help[..]) - .arg( - Arg::with_name(options::COMPLEMENT) - // .visible_short_alias('C') // TODO: requires clap "3.0.0-beta.2" - .short("c") - .long(options::COMPLEMENT) - .help("use the complement of SET1"), - ) - .arg( - Arg::with_name("C") // work around for `Arg::visible_short_alias` - .short("C") - .help("same as -c"), - ) - .arg( - Arg::with_name(options::DELETE) - .short("d") - .long(options::DELETE) - .help("delete characters in SET1, do not translate"), - ) - .arg( - Arg::with_name(options::SQUEEZE) - .long(options::SQUEEZE) - .short("s") - .help( - "replace each sequence of a repeated character that is - listed in the last specified SET, with a single occurrence - of that character", - ), - ) - .arg( - Arg::with_name(options::TRUNCATE) - .long(options::TRUNCATE) - .short("t") - .help("first truncate SET1 to length of SET2"), - ) - .arg(Arg::with_name(options::SETS).multiple(true)) .get_matches_from(args); let delete_flag = matches.is_present(options::DELETE); @@ -358,3 +321,44 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::COMPLEMENT) + // .visible_short_alias('C') // TODO: requires clap "3.0.0-beta.2" + .short("c") + .long(options::COMPLEMENT) + .help("use the complement of SET1"), + ) + .arg( + Arg::with_name("C") // work around for `Arg::visible_short_alias` + .short("C") + .help("same as -c"), + ) + .arg( + Arg::with_name(options::DELETE) + .short("d") + .long(options::DELETE) + .help("delete characters in SET1, do not translate"), + ) + .arg( + Arg::with_name(options::SQUEEZE) + .long(options::SQUEEZE) + .short("s") + .help( + "replace each sequence of a repeated character that is + listed in the last specified SET, with a single occurrence + of that character", + ), + ) + .arg( + Arg::with_name(options::TRUNCATE) + .long(options::TRUNCATE) + .short("t") + .help("first truncate SET1 to length of SET2"), + ) + .arg(Arg::with_name(options::SETS).multiple(true)) +} diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index 9f13318fd..f121d56de 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/true.rs" [dependencies] +clap = "2.33.3" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index 7cb23f621..521ca2ea5 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -5,6 +5,15 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +use clap::{App, AppSettings}; +use uucore::executable; + pub fn uumain(_: impl uucore::Args) -> i32 { 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .setting(AppSettings::DisableHelpFlags) + .setting(AppSettings::DisableVersion) +} diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 8ef246833..bb7aa61d4 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -93,45 +93,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let long_usage = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&long_usage[..]) - .arg( - Arg::with_name(options::IO_BLOCKS) - .short("o") - .long(options::IO_BLOCKS) - .help("treat SIZE as the number of I/O blocks of the file rather than bytes (NOT IMPLEMENTED)") - ) - .arg( - Arg::with_name(options::NO_CREATE) - .short("c") - .long(options::NO_CREATE) - .help("do not create files that do not exist") - ) - .arg( - Arg::with_name(options::REFERENCE) - .short("r") - .long(options::REFERENCE) - .required_unless(options::SIZE) - .help("base the size of each file on the size of RFILE") - .value_name("RFILE") - ) - .arg( - Arg::with_name(options::SIZE) - .short("s") - .long(options::SIZE) - .required_unless(options::REFERENCE) - .help("set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified") - .value_name("SIZE") - ) - .arg(Arg::with_name(options::ARG_FILES) - .value_name("FILE") - .multiple(true) - .takes_value(true) - .required(true) - .min_values(1)) .get_matches_from(args); let files: Vec = matches @@ -168,6 +132,46 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::IO_BLOCKS) + .short("o") + .long(options::IO_BLOCKS) + .help("treat SIZE as the number of I/O blocks of the file rather than bytes (NOT IMPLEMENTED)") + ) + .arg( + Arg::with_name(options::NO_CREATE) + .short("c") + .long(options::NO_CREATE) + .help("do not create files that do not exist") + ) + .arg( + Arg::with_name(options::REFERENCE) + .short("r") + .long(options::REFERENCE) + .required_unless(options::SIZE) + .help("base the size of each file on the size of RFILE") + .value_name("RFILE") + ) + .arg( + Arg::with_name(options::SIZE) + .short("s") + .long(options::SIZE) + .required_unless(options::REFERENCE) + .help("set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified") + .value_name("SIZE") + ) + .arg(Arg::with_name(options::ARG_FILES) + .value_name("FILE") + .multiple(true) + .takes_value(true) + .required(true) + .min_values(1)) +} + /// Truncate the named file to the specified size. /// /// If `create` is true, then the file will be created if it does not diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 8bd6dabef..0a323f837 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -30,16 +30,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .usage(USAGE) - .about(SUMMARY) - .arg( - Arg::with_name(options::FILE) - .default_value("-") - .hidden(true), - ) - .get_matches_from(args); + let matches = uu_app().get_matches_from(args); let input = matches .value_of(options::FILE) @@ -98,6 +89,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .usage(USAGE) + .about(SUMMARY) + .arg( + Arg::with_name(options::FILE) + .default_value("-") + .hidden(true), + ) +} + // We use String as a representation of node here // but using integer may improve performance. struct Graph { diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index cc5052dea..7412cdf45 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -33,19 +33,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::SILENT) - .long(options::SILENT) - .visible_alias("quiet") - .short("s") - .help("print nothing, only return an exit status") - .required(false), - ) - .get_matches_from_safe(args); + let matches = uu_app().usage(&usage[..]).get_matches_from_safe(args); let matches = match matches { Ok(m) => m, @@ -88,3 +76,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { libc::EXIT_FAILURE } } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::SILENT) + .long(options::SILENT) + .visible_alias("quiet") + .short("s") + .help("print nothing, only return an exit status") + .required(false), + ) +} diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index aa591ee18..dda859722 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -47,49 +47,7 @@ const HOST_OS: &str = "Redox"; pub fn uumain(args: impl uucore::Args) -> i32 { let usage = format!("{} [OPTION]...", executable!()); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg(Arg::with_name(options::ALL) - .short("a") - .long(options::ALL) - .help("Behave as though all of the options -mnrsv were specified.")) - .arg(Arg::with_name(options::KERNELNAME) - .short("s") - .long(options::KERNELNAME) - .alias("sysname") // Obsolescent option in GNU uname - .help("print the kernel name.")) - .arg(Arg::with_name(options::NODENAME) - .short("n") - .long(options::NODENAME) - .help("print the nodename (the nodename may be a name that the system is known by to a communications network).")) - .arg(Arg::with_name(options::KERNELRELEASE) - .short("r") - .long(options::KERNELRELEASE) - .alias("release") // Obsolescent option in GNU uname - .help("print the operating system release.")) - .arg(Arg::with_name(options::KERNELVERSION) - .short("v") - .long(options::KERNELVERSION) - .help("print the operating system version.")) - .arg(Arg::with_name(options::HWPLATFORM) - .short("i") - .long(options::HWPLATFORM) - .help("print the hardware platform (non-portable)")) - .arg(Arg::with_name(options::MACHINE) - .short("m") - .long(options::MACHINE) - .help("print the machine hardware name.")) - .arg(Arg::with_name(options::PROCESSOR) - .short("p") - .long(options::PROCESSOR) - .help("print the processor type (non-portable)")) - .arg(Arg::with_name(options::OS) - .short("o") - .long(options::OS) - .help("print the operating system name.")) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let uname = return_if_err!(1, PlatformInfo::new()); let mut output = String::new(); @@ -155,3 +113,47 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg(Arg::with_name(options::ALL) + .short("a") + .long(options::ALL) + .help("Behave as though all of the options -mnrsv were specified.")) + .arg(Arg::with_name(options::KERNELNAME) + .short("s") + .long(options::KERNELNAME) + .alias("sysname") // Obsolescent option in GNU uname + .help("print the kernel name.")) + .arg(Arg::with_name(options::NODENAME) + .short("n") + .long(options::NODENAME) + .help("print the nodename (the nodename may be a name that the system is known by to a communications network).")) + .arg(Arg::with_name(options::KERNELRELEASE) + .short("r") + .long(options::KERNELRELEASE) + .alias("release") // Obsolescent option in GNU uname + .help("print the operating system release.")) + .arg(Arg::with_name(options::KERNELVERSION) + .short("v") + .long(options::KERNELVERSION) + .help("print the operating system version.")) + .arg(Arg::with_name(options::HWPLATFORM) + .short("i") + .long(options::HWPLATFORM) + .help("print the hardware platform (non-portable)")) + .arg(Arg::with_name(options::MACHINE) + .short("m") + .long(options::MACHINE) + .help("print the machine hardware name.")) + .arg(Arg::with_name(options::PROCESSOR) + .short("p") + .long(options::PROCESSOR) + .help("print the processor type (non-portable)")) + .arg(Arg::with_name(options::OS) + .short("o") + .long(options::OS) + .help("print the operating system name.")) +} diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 92b3c7520..50e3f186d 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -94,7 +94,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) + let matches = uu_app().get_matches_from(args); + + unexpand(Options::new(matches)); + + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .name(NAME) .version(crate_version!()) .usage(USAGE) @@ -126,11 +134,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::NO_UTF8) .takes_value(false) .help("interpret input file as 8-bit ASCII rather than UTF-8")) - .get_matches_from(args); - - unexpand(Options::new(matches)); - - 0 } fn open(path: String) -> BufReader> { diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index aee024dd4..20639c850 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -238,11 +238,52 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let long_usage = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&long_usage[..]) + .get_matches_from(args); + + let files: Vec = matches + .values_of(ARG_FILES) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + let (in_file_name, out_file_name) = match files.len() { + 0 => ("-".to_owned(), "-".to_owned()), + 1 => (files[0].clone(), "-".to_owned()), + 2 => (files[0].clone(), files[1].clone()), + _ => { + // Cannot happen as clap will fail earlier + crash!(1, "Extra operand: {}", files[2]); + } + }; + + let uniq = Uniq { + repeats_only: matches.is_present(options::REPEATED) + || matches.is_present(options::ALL_REPEATED), + uniques_only: matches.is_present(options::UNIQUE), + all_repeated: matches.is_present(options::ALL_REPEATED) + || matches.is_present(options::GROUP), + delimiters: get_delimiter(&matches), + show_counts: matches.is_present(options::COUNT), + skip_fields: opt_parsed(options::SKIP_FIELDS, &matches), + slice_start: opt_parsed(options::SKIP_CHARS, &matches), + slice_stop: opt_parsed(options::CHECK_CHARS, &matches), + ignore_case: matches.is_present(options::IGNORE_CASE), + zero_terminated: matches.is_present(options::ZERO_TERMINATED), + }; + uniq.print_uniq( + &mut open_input_file(in_file_name), + &mut open_output_file(out_file_name), + ); + + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) .arg( Arg::with_name(options::ALL_REPEATED) .short("D") @@ -329,43 +370,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .max_values(2), ) - .get_matches_from(args); - - let files: Vec = matches - .values_of(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - let (in_file_name, out_file_name) = match files.len() { - 0 => ("-".to_owned(), "-".to_owned()), - 1 => (files[0].clone(), "-".to_owned()), - 2 => (files[0].clone(), files[1].clone()), - _ => { - // Cannot happen as clap will fail earlier - crash!(1, "Extra operand: {}", files[2]); - } - }; - - let uniq = Uniq { - repeats_only: matches.is_present(options::REPEATED) - || matches.is_present(options::ALL_REPEATED), - uniques_only: matches.is_present(options::UNIQUE), - all_repeated: matches.is_present(options::ALL_REPEATED) - || matches.is_present(options::GROUP), - delimiters: get_delimiter(&matches), - show_counts: matches.is_present(options::COUNT), - skip_fields: opt_parsed(options::SKIP_FIELDS, &matches), - slice_start: opt_parsed(options::SKIP_CHARS, &matches), - slice_stop: opt_parsed(options::CHECK_CHARS, &matches), - ignore_case: matches.is_present(options::IGNORE_CASE), - zero_terminated: matches.is_present(options::ZERO_TERMINATED), - }; - uniq.print_uniq( - &mut open_input_file(in_file_name), - &mut open_output_file(out_file_name), - ); - - 0 } fn get_delimiter(matches: &ArgMatches) -> Delimiters { diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 343f2653f..49f17cb12 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -33,12 +33,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg(Arg::with_name(OPT_PATH).hidden(true).multiple(true)) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let paths: Vec = matches .values_of(OPT_PATH) @@ -98,3 +93,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg(Arg::with_name(OPT_PATH).hidden(true).multiple(true)) +} diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 3683a4de0..35270093c 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -38,17 +38,7 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::SINCE) - .short("s") - .long(options::SINCE) - .help("system up since"), - ) - .get_matches_from(args); + let matches = uu_app().usage(&usage[..]).get_matches_from(args); let (boot_time, user_count) = process_utmpx(); let uptime = get_uptime(boot_time); @@ -73,6 +63,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::SINCE) + .short("s") + .long(options::SINCE) + .help("system up since"), + ) +} + #[cfg(unix)] fn print_loadavg() { use uucore::libc::c_double; diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 5b1f1c037..ef878497c 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -34,12 +34,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&after_help[..]) - .arg(Arg::with_name(ARG_FILES).takes_value(true).max_values(1)) .get_matches_from(args); let files: Vec = matches @@ -66,3 +63,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) + .arg(Arg::with_name(ARG_FILES).takes_value(true).max_values(1)) +} diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index d1e1f75ca..0bcc66664 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -134,10 +134,39 @@ impl Input { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) + let matches = uu_app().usage(&usage[..]).get_matches_from(args); + + let mut inputs: Vec = matches + .values_of(ARG_FILES) + .map(|v| { + v.map(|i| { + if i == "-" { + Input::Stdin(StdinKind::Explicit) + } else { + Input::Path(ToString::to_string(i)) + } + }) + .collect() + }) + .unwrap_or_default(); + + if inputs.is_empty() { + inputs.push(Input::Stdin(StdinKind::Implicit)); + } + + let settings = Settings::new(&matches); + + if wc(inputs, &settings).is_ok() { + 0 + } else { + 1 + } +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) .version(crate_version!()) .about(ABOUT) - .usage(&usage[..]) .arg( Arg::with_name(options::BYTES) .short("c") @@ -169,33 +198,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("print the word counts"), ) .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) - .get_matches_from(args); - - let mut inputs: Vec = matches - .values_of(ARG_FILES) - .map(|v| { - v.map(|i| { - if i == "-" { - Input::Stdin(StdinKind::Explicit) - } else { - Input::Path(ToString::to_string(i)) - } - }) - .collect() - }) - .unwrap_or_default(); - - if inputs.is_empty() { - inputs.push(Input::Stdin(StdinKind::Implicit)); - } - - let settings = Settings::new(&matches); - - if wc(inputs, &settings).is_ok() { - 0 - } else { - 1 - } } fn word_count_from_reader( diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 047452240..6a9c88710 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -64,11 +64,105 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = uu_app() .usage(&usage[..]) .after_help(&after_help[..]) + .get_matches_from(args); + + let files: Vec = matches + .values_of(options::FILE) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + // If true, attempt to canonicalize hostnames via a DNS lookup. + let do_lookup = matches.is_present(options::LOOKUP); + + // If true, display only a list of usernames and count of + // the users logged on. + // Ignored for 'who am i'. + let short_list = matches.is_present(options::COUNT); + + let all = matches.is_present(options::ALL); + + // If true, display a line at the top describing each field. + let include_heading = matches.is_present(options::HEADING); + + // If true, display a '+' for each user if mesg y, a '-' if mesg n, + // or a '?' if their tty cannot be statted. + let include_mesg = all || matches.is_present(options::MESG) || matches.is_present("w"); + + // If true, display the last boot time. + let need_boottime = all || matches.is_present(options::BOOT); + + // If true, display dead processes. + let need_deadprocs = all || matches.is_present(options::DEAD); + + // If true, display processes waiting for user login. + let need_login = all || matches.is_present(options::LOGIN); + + // If true, display processes started by init. + let need_initspawn = all || matches.is_present(options::PROCESS); + + // If true, display the last clock change. + let need_clockchange = all || matches.is_present(options::TIME); + + // If true, display the current runlevel. + let need_runlevel = all || matches.is_present(options::RUNLEVEL); + + let use_defaults = !(all + || need_boottime + || need_deadprocs + || need_login + || need_initspawn + || need_runlevel + || need_clockchange + || matches.is_present(options::USERS)); + + // If true, display user processes. + let need_users = all || matches.is_present(options::USERS) || use_defaults; + + // If true, display the hours:minutes since each user has touched + // the keyboard, or "." if within the last minute, or "old" if + // not within the last day. + let include_idle = need_deadprocs || need_login || need_runlevel || need_users; + + // If true, display process termination & exit status. + let include_exit = need_deadprocs; + + // If true, display only name, line, and time fields. + let short_output = !include_exit && use_defaults; + + // If true, display info only for the controlling tty. + let my_line_only = matches.is_present(options::ONLY_HOSTNAME_USER) || files.len() == 2; + + let mut who = Who { + do_lookup, + short_list, + short_output, + include_idle, + include_heading, + include_mesg, + include_exit, + need_boottime, + need_deadprocs, + need_login, + need_initspawn, + need_clockchange, + need_runlevel, + need_users, + my_line_only, + args: files, + }; + + who.exec(); + + 0 +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(executable!()) + .version(crate_version!()) + .about(ABOUT) .arg( Arg::with_name(options::ALL) .long(options::ALL) @@ -164,96 +258,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .min_values(1) .max_values(2), ) - .get_matches_from(args); - - let files: Vec = matches - .values_of(options::FILE) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - // If true, attempt to canonicalize hostnames via a DNS lookup. - let do_lookup = matches.is_present(options::LOOKUP); - - // If true, display only a list of usernames and count of - // the users logged on. - // Ignored for 'who am i'. - let short_list = matches.is_present(options::COUNT); - - let all = matches.is_present(options::ALL); - - // If true, display a line at the top describing each field. - let include_heading = matches.is_present(options::HEADING); - - // If true, display a '+' for each user if mesg y, a '-' if mesg n, - // or a '?' if their tty cannot be statted. - let include_mesg = all || matches.is_present(options::MESG) || matches.is_present("w"); - - // If true, display the last boot time. - let need_boottime = all || matches.is_present(options::BOOT); - - // If true, display dead processes. - let need_deadprocs = all || matches.is_present(options::DEAD); - - // If true, display processes waiting for user login. - let need_login = all || matches.is_present(options::LOGIN); - - // If true, display processes started by init. - let need_initspawn = all || matches.is_present(options::PROCESS); - - // If true, display the last clock change. - let need_clockchange = all || matches.is_present(options::TIME); - - // If true, display the current runlevel. - let need_runlevel = all || matches.is_present(options::RUNLEVEL); - - let use_defaults = !(all - || need_boottime - || need_deadprocs - || need_login - || need_initspawn - || need_runlevel - || need_clockchange - || matches.is_present(options::USERS)); - - // If true, display user processes. - let need_users = all || matches.is_present(options::USERS) || use_defaults; - - // If true, display the hours:minutes since each user has touched - // the keyboard, or "." if within the last minute, or "old" if - // not within the last day. - let include_idle = need_deadprocs || need_login || need_runlevel || need_users; - - // If true, display process termination & exit status. - let include_exit = need_deadprocs; - - // If true, display only name, line, and time fields. - let short_output = !include_exit && use_defaults; - - // If true, display info only for the controlling tty. - let my_line_only = matches.is_present(options::ONLY_HOSTNAME_USER) || files.len() == 2; - - let mut who = Who { - do_lookup, - short_list, - short_output, - include_idle, - include_heading, - include_mesg, - include_exit, - need_boottime, - need_deadprocs, - need_login, - need_initspawn, - need_clockchange, - need_runlevel, - need_users, - my_line_only, - args: files, - }; - - who.exec(); - - 0 } struct Who { diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index 383fb40b5..bd2eea1e3 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -1,3 +1,5 @@ +use clap::App; + // * This file is part of the uutils coreutils package. // * // * (c) Jordi Boggiano @@ -15,7 +17,7 @@ extern crate uucore; mod platform; pub fn uumain(args: impl uucore::Args) -> i32 { - let app = app_from_crate!(); + let app = uu_app(); if let Err(err) = app.get_matches_from_safe(args) { if err.kind == clap::ErrorKind::HelpDisplayed @@ -34,6 +36,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } +pub fn uu_app() -> App<'static, 'static> { + app_from_crate!() +} + pub fn exec() { unsafe { match platform::get_username() { diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index 1fc2d92bc..2c0d43000 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -12,7 +12,7 @@ extern crate clap; #[macro_use] extern crate uucore; -use clap::Arg; +use clap::{App, Arg}; use std::borrow::Cow; use std::io::{self, Write}; use uucore::zero_copy::ZeroCopyWriter; @@ -22,7 +22,7 @@ use uucore::zero_copy::ZeroCopyWriter; const BUF_SIZE: usize = 16 * 1024; pub fn uumain(args: impl uucore::Args) -> i32 { - let app = app_from_crate!().arg(Arg::with_name("STRING").index(1).multiple(true)); + let app = uu_app(); let matches = match app.get_matches_from_safe(args) { Ok(m) => m, @@ -56,6 +56,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +pub fn uu_app() -> App<'static, 'static> { + app_from_crate!().arg(Arg::with_name("STRING").index(1).multiple(true)) +} + #[cfg(not(feature = "latency"))] fn prepare_buffer<'a>(input: &'a str, buffer: &'a mut [u8; BUF_SIZE]) -> &'a [u8] { if input.len() < BUF_SIZE / 2 { From a9e79c72c7717b5de86f32fa53399fbf0c5ee77f Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 15 Jun 2021 15:35:49 +0200 Subject: [PATCH 290/320] uutils: enable shell completions This adds a hidden `completion` subcommand to coreutils. When invoked with `coreutils completion ` a completion file will be printed to stdout. When running `make install` those files will be created for all utilities and copied to the appropriate locations. `make install` will install completions for zsh, fish and bash; however, clap also supports generating completions for powershell and elvish. With this patch all utilities are required to have a publich uu_app function that returns a clap::App in addition to the uumain function. --- Cargo.lock | 1 + Cargo.toml | 1 + GNUmakefile | 5 +++++ build.rs | 40 +++++++++++++++++----------------- src/bin/coreutils.rs | 52 +++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 76 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f96f7f8b..51424332d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,6 +218,7 @@ version = "0.0.6" dependencies = [ "atty", "chrono", + "clap", "conv", "filetime", "glob 0.3.0", diff --git a/Cargo.toml b/Cargo.toml index 0fec2af78..2783ff1b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -225,6 +225,7 @@ test = [ "uu_test" ] [workspace] [dependencies] +clap = "2.33.3" lazy_static = { version="1.3" } textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review uucore = { version=">=0.0.8", package="uucore", path="src/uucore" } diff --git a/GNUmakefile b/GNUmakefile index e5ad01340..ea9c7254a 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -314,6 +314,11 @@ else endif $(foreach man, $(filter $(INSTALLEES), $(basename $(notdir $(wildcard $(DOCSDIR)/_build/man/*)))), \ cat $(DOCSDIR)/_build/man/$(man).1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)$(man).1.gz &&) : + $(foreach prog, $(INSTALLEES), \ + $(BUILDDIR)/coreutils completion $(prog) zsh > $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(PROG_PREFIX)$(prog); \ + $(BUILDDIR)/coreutils completion $(prog) bash > $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(PROG_PREFIX)$(prog); \ + $(BUILDDIR)/coreutils completion $(prog) fish > $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/$(PROG_PREFIX)$(prog).fish; \ + ) uninstall: ifeq (${MULTICALL}, y) diff --git a/build.rs b/build.rs index 2ed8e1345..e9fe129eb 100644 --- a/build.rs +++ b/build.rs @@ -43,7 +43,7 @@ pub fn main() { let mut tf = File::create(Path::new(&out_dir).join("test_modules.rs")).unwrap(); mf.write_all( - "type UtilityMap = HashMap<&'static str, fn(T) -> i32>;\n\ + "type UtilityMap = HashMap<&'static str, (fn(T) -> i32, fn() -> App<'static, 'static>)>;\n\ \n\ fn util_map() -> UtilityMap {\n\ \tlet mut map = UtilityMap::new();\n\ @@ -60,8 +60,8 @@ pub fn main() { mf.write_all( format!( "\ - \tmap.insert(\"test\", {krate}::uumain);\n\ - \t\tmap.insert(\"[\", {krate}::uumain);\n\ + \tmap.insert(\"test\", ({krate}::uumain, {krate}::uu_app));\n\ + \t\tmap.insert(\"[\", ({krate}::uumain, {krate}::uu_app));\n\ ", krate = krate ) @@ -80,7 +80,7 @@ pub fn main() { k if k.starts_with(override_prefix) => { mf.write_all( format!( - "\tmap.insert(\"{k}\", {krate}::uumain);\n", + "\tmap.insert(\"{k}\", ({krate}::uumain, {krate}::uu_app));\n", k = krate[override_prefix.len()..].to_string(), krate = krate ) @@ -100,7 +100,7 @@ pub fn main() { "false" | "true" => { mf.write_all( format!( - "\tmap.insert(\"{krate}\", r#{krate}::uumain);\n", + "\tmap.insert(\"{krate}\", (r#{krate}::uumain, r#{krate}::uu_app));\n", krate = krate ) .as_bytes(), @@ -120,20 +120,20 @@ pub fn main() { mf.write_all( format!( "\ - \tmap.insert(\"{krate}\", {krate}::uumain);\n\ - \t\tmap.insert(\"md5sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha1sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha224sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha256sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha384sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha512sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha3sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha3-224sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha3-256sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha3-384sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha3-512sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"shake128sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"shake256sum\", {krate}::uumain);\n\ + \tmap.insert(\"{krate}\", ({krate}::uumain, {krate}::uu_app_custom));\n\ + \t\tmap.insert(\"md5sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"sha1sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"sha224sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"sha256sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"sha384sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"sha512sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"sha3sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"sha3-224sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"sha3-256sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"sha3-384sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"sha3-512sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"shake128sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ + \t\tmap.insert(\"shake256sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ ", krate = krate ) @@ -153,7 +153,7 @@ pub fn main() { _ => { mf.write_all( format!( - "\tmap.insert(\"{krate}\", {krate}::uumain);\n", + "\tmap.insert(\"{krate}\", ({krate}::uumain, {krate}::uu_app));\n", krate = krate ) .as_bytes(), diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 2e703b682..270f5153a 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -5,6 +5,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use clap::App; +use clap::Shell; use std::cmp; use std::collections::hash_map::HashMap; use std::ffi::OsString; @@ -52,7 +54,7 @@ fn main() { let binary_as_util = name(&binary); // binary name equals util name? - if let Some(&uumain) = utils.get(binary_as_util) { + if let Some(&(uumain, _)) = utils.get(binary_as_util) { process::exit(uumain((vec![binary.into()].into_iter()).chain(args))); } @@ -74,8 +76,12 @@ fn main() { if let Some(util_os) = util_name { let util = util_os.as_os_str().to_string_lossy(); + if util == "completion" { + gen_completions(args, utils); + } + match utils.get(&util[..]) { - Some(&uumain) => { + Some(&(uumain, _)) => { process::exit(uumain((vec![util_os].into_iter()).chain(args))); } None => { @@ -85,7 +91,7 @@ fn main() { let util = util_os.as_os_str().to_string_lossy(); match utils.get(&util[..]) { - Some(&uumain) => { + Some(&(uumain, _)) => { let code = uumain( (vec![util_os, OsString::from("--help")].into_iter()) .chain(args), @@ -113,3 +119,43 @@ fn main() { process::exit(0); } } + +/// 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, + 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 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) + }; + let shell: Shell = shell.parse().unwrap(); + 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); +} + +fn gen_coreutils_app(util_map: UtilityMap) -> App<'static, 'static> { + let mut app = App::new("coreutils"); + for (_, (_, sub_app)) in util_map { + app = app.subcommand(sub_app()); + } + app +} From a8d62b9b2351e4dc216942c998cedec19f6b6fe9 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 18 Jun 2021 11:57:19 +0200 Subject: [PATCH 291/320] fmt: fix indentation for help --- src/uu/fmt/src/fmt.rs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 9eceaa56c..8c2c8d9d9 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -219,10 +219,10 @@ pub fn uu_app() -> App<'static, 'static> { .short("c") .long(OPT_CROWN_MARGIN) .help( - "First and second line of paragraph - may have different indentations, in which - case the first line's indentation is preserved, - and each subsequent line's indentation matches the second line.", + "First and second line of paragraph \ + may have different indentations, in which \ + case the first line's indentation is preserved, \ + and each subsequent line's indentation matches the second line.", ), ) .arg( @@ -230,7 +230,7 @@ pub fn uu_app() -> App<'static, 'static> { .short("t") .long("tagged-paragraph") .help( - "Like -c, except that the first and second line of a paragraph *must* + "Like -c, except that the first and second line of a paragraph *must* \ have different indentation or they are treated as separate paragraphs.", ), ) @@ -239,7 +239,7 @@ pub fn uu_app() -> App<'static, 'static> { .short("m") .long("preserve-headers") .help( - "Attempt to detect and preserve mail headers in the input. + "Attempt to detect and preserve mail headers in the input. \ Be careful when combining this flag with -p.", ), ) @@ -254,10 +254,10 @@ pub fn uu_app() -> App<'static, 'static> { .short("u") .long("uniform-spacing") .help( - "Insert exactly one - space between words, and two between sentences. - Sentence breaks in the input are detected as [?!.] - followed by two spaces or a newline; other punctuation + "Insert exactly one \ + space between words, and two between sentences. \ + Sentence breaks in the input are detected as [?!.] \ + followed by two spaces or a newline; other punctuation \ is not interpreted as a sentence break.", ), ) @@ -266,9 +266,9 @@ pub fn uu_app() -> App<'static, 'static> { .short("p") .long("prefix") .help( - "Reformat only lines - beginning with PREFIX, reattaching PREFIX to reformatted lines. - Unless -x is specified, leading whitespace will be ignored + "Reformat only lines \ + beginning with PREFIX, reattaching PREFIX to reformatted lines. \ + Unless -x is specified, leading whitespace will be ignored \ when matching PREFIX.", ) .value_name("PREFIX"), @@ -278,8 +278,8 @@ pub fn uu_app() -> App<'static, 'static> { .short("P") .long("skip-prefix") .help( - "Do not reformat lines - beginning with PSKIP. Unless -X is specified, leading whitespace + "Do not reformat lines \ + beginning with PSKIP. Unless -X is specified, leading whitespace \ will be ignored when matching PSKIP", ) .value_name("PSKIP"), @@ -289,7 +289,7 @@ pub fn uu_app() -> App<'static, 'static> { .short("x") .long("exact-prefix") .help( - "PREFIX must match at the + "PREFIX must match at the \ beginning of the line with no preceding whitespace.", ), ) @@ -298,7 +298,7 @@ pub fn uu_app() -> App<'static, 'static> { .short("X") .long("exact-skip-prefix") .help( - "PSKIP must match at the + "PSKIP must match at the \ beginning of the line with no preceding whitespace.", ), ) @@ -317,7 +317,7 @@ pub fn uu_app() -> App<'static, 'static> { .value_name("GOAL"), ) .arg(Arg::with_name(OPT_QUICK).short("q").long("quick").help( - "Break lines more quickly at the + "Break lines more quickly at the \ expense of a potentially more ragged appearance.", )) .arg( @@ -325,8 +325,8 @@ pub fn uu_app() -> App<'static, 'static> { .short("T") .long("tab-width") .help( - "Treat tabs as TABWIDTH spaces for - determining line length, default 8. Note that this is used only for + "Treat tabs as TABWIDTH spaces for \ + determining line length, default 8. Note that this is used only for \ calculating line lengths; tabs are preserved in the output.", ) .value_name("TABWIDTH"), From 0fec449de334a352b5fef26b3bdc4275b7d79a70 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 18 Jun 2021 11:58:23 +0200 Subject: [PATCH 292/320] mkfifo: make rustfmt work --- src/uu/mkfifo/src/mkfifo.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index ad12e230d..ea0906567 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -86,8 +86,16 @@ pub fn uu_app() -> App<'static, 'static> { .arg( Arg::with_name(options::SE_LINUX_SECURITY_CONTEXT) .short(options::SE_LINUX_SECURITY_CONTEXT) - .help("set the SELinux security context to default type") + .help("set the SELinux security context to default type"), + ) + .arg( + Arg::with_name(options::CONTEXT) + .long(options::CONTEXT) + .value_name("CTX") + .help( + "like -Z, or if CTX is specified then set the SELinux \ + or SMACK security context to CTX", + ), ) - .arg(Arg::with_name(options::CONTEXT).long(options::CONTEXT).value_name("CTX").help("like -Z, or if CTX is specified then set the SELinux\nor SMACK security context to CTX")) .arg(Arg::with_name(options::FIFO).hidden(true).multiple(true)) } From 2e027bf45d717a47430008d963bcae55d2e5c567 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 18 Jun 2021 12:00:08 +0200 Subject: [PATCH 293/320] true, false: enable --help and --version --- src/uu/false/src/false.rs | 7 +++---- src/uu/true/src/true.rs | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index aaeb6b751..17c681129 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -5,15 +5,14 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -use clap::{App, AppSettings}; +use clap::App; use uucore::executable; -pub fn uumain(_: impl uucore::Args) -> i32 { +pub fn uumain(args: impl uucore::Args) -> i32 { + uu_app().get_matches_from(args); 1 } pub fn uu_app() -> App<'static, 'static> { App::new(executable!()) - .setting(AppSettings::DisableHelpFlags) - .setting(AppSettings::DisableVersion) } diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index 521ca2ea5..ea53b0075 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -5,15 +5,14 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -use clap::{App, AppSettings}; +use clap::App; use uucore::executable; -pub fn uumain(_: impl uucore::Args) -> i32 { +pub fn uumain(args: impl uucore::Args) -> i32 { + uu_app().get_matches_from(args); 0 } pub fn uu_app() -> App<'static, 'static> { App::new(executable!()) - .setting(AppSettings::DisableHelpFlags) - .setting(AppSettings::DisableVersion) } From 73cfcc27e7a03bbdec0e6539f72f16a9b4daec80 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 18 Jun 2021 12:12:06 +0200 Subject: [PATCH 294/320] cp: insert some spaces into the help text --- src/uu/cp/src/cp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 12dfeab3f..4deaefa98 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -375,7 +375,7 @@ pub fn uu_app() -> App<'static, 'static> { .arg(Arg::with_name(options::UPDATE) .short("u") .long(options::UPDATE) - .help("copy only when the SOURCE file is newer than the destination file\ + .help("copy only when the SOURCE file is newer than the destination file \ or when the destination file is missing")) .arg(Arg::with_name(options::REFLINK) .long(options::REFLINK) @@ -398,7 +398,7 @@ pub fn uu_app() -> App<'static, 'static> { .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE]) // -d sets this option // --archive sets this option - .help("Preserve the specified attributes (default: mode(unix only),ownership,timestamps),\ + .help("Preserve the specified attributes (default: mode (unix only), ownership, timestamps), \ if possible additional attributes: context, links, xattr, all")) .arg(Arg::with_name(options::PRESERVE_DEFAULT_ATTRIBUTES) .short("-p") From a87538b77d46f5d19172c8b1f9aeca21f4d3a2f0 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 19 Jun 2021 12:31:23 +0200 Subject: [PATCH 295/320] uutils: uninstall shell completions --- GNUmakefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index ea9c7254a..89a4dca80 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -326,6 +326,9 @@ ifeq (${MULTICALL}, y) endif rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz) rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS)) + rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(PROG_PREFIX),$(PROGS)) + rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(PROG_PREFIX),$(PROGS)) + rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/$(PROG_PREFIX),$(addsuffix .fish,$(PROGS))) rm -f $(addprefix $(INSTALLDIR_MAN)/$(PROG_PREFIX),$(addsuffix .1.gz,$(PROGS))) .PHONY: all build build-coreutils build-pkgs build-docs test distclean clean busytest install uninstall From 9b8150d283d2122ac162e4b84ba9b58649359415 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 19 Jun 2021 12:37:01 +0200 Subject: [PATCH 296/320] uutils: document completions in the readme --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index fd8709b64..083320ac0 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,9 @@ $ cargo install --path . This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`). +This does not install files necessary for shell completion. For shell completion to work, +use `GNU Make` or see `Manually install shell completions`. + ### GNU Make To install all available utilities: @@ -179,6 +182,10 @@ Set install parent directory (default value is /usr/local): $ make PREFIX=/my/path install ``` +Installing with `make` installs shell completions for all installed utilities +for `bash`, `fish` and `zsh`. Completions for `elvish` and `powershell` can also +be generated; See `Manually install shell completions`. + ### NixOS The [standard package set](https://nixos.org/nixpkgs/manual/) of [NixOS](https://nixos.org/) @@ -188,6 +195,23 @@ provides this package out of the box since 18.03: $ nix-env -iA nixos.uutils-coreutils ``` +### Manually install shell completions + +The `coreutils` binary can generate completions for the `bash`, `elvish`, `fish`, `powershell` +and `zsh` shells. It prints the result to stdout. + +The syntax is: +```bash +cargo run completion +``` + +So, to install completions for `ls` on `bash` to `/usr/local/share/bash-completion/completions/ls`, +run: + +```bash +cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls +``` + ## Un-installation Instructions Un-installation differs depending on how you have installed uutils. If you used From 211af9a3ead2919272dac7bc733d172db3dc82e2 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 23 Jun 2021 16:09:38 +0200 Subject: [PATCH 297/320] backup_control: Add todo for gnu compliant behavior The logic behind the file-backup implementation currently doesn't comply 100% with what the GNU manual [1] describes. Adds a TODO so it isn't forgotten. [1]: https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html --- src/uucore/src/lib/mods/backup_control.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index 83268d351..bbe9cd227 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) { From 3155cd510ff0fba9c0de05204276cac08dab6268 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 23 Jun 2021 16:11:46 +0200 Subject: [PATCH 298/320] install: Fix argument parsing for '--backup' The '--backup' option would previously accept arguments separated from the option either by a space or an equals sign. The GNU implementation strictly requires an "equals" for argument separation. As the argument to '--backup' is optional, the equals sign mustn't be ommited as otherwise there is no way to tell a file argument apart from an argument that's meant for the '--backup' option. This ensures that if '--backup' is present it either has no further associated arguments (i.e. fallback to the default), or the arguments are separated by an equals sign. --- src/uu/install/src/install.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 3992ac25e..44c1ca980 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -106,6 +106,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(OPT_BACKUP) .long(OPT_BACKUP) .help("(unimplemented) make a backup of each existing destination file") + .takes_value(true) + .require_equals(true) + .min_values(0) .value_name("CONTROL") ) .arg( From a85adf3c3fff1a74e656e5f5f9bd530451e4d189 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 23 Jun 2021 16:18:41 +0200 Subject: [PATCH 299/320] install: Internally rename short '-b' option Rename from OPT_BACKUP_2 to a more descriptive name "OPT_BACKUP_NO_ARGS". --- src/uu/install/src/install.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 44c1ca980..0f0be1959 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -68,7 +68,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"; @@ -113,7 +113,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) .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") ) @@ -270,6 +270,7 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> { Err("--backup") } else if matches.is_present(OPT_BACKUP_2) { Err("-b") + } else if matches.is_present(OPT_BACKUP_NO_ARG) { } else if matches.is_present(OPT_SUFFIX) { Err("--suffix, -S") } else if matches.is_present(OPT_NO_TARGET_DIRECTORY) { From 49a9f359bb3e335c745ca6baccf0c6645a8a5146 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 23 Jun 2021 16:20:10 +0200 Subject: [PATCH 300/320] install: Make use of 'backup_controls' for '--backup' opts Use the methods and types offered by the 'backup_controls' module to implement the logic for backing up files instead of overwriting. --- src/uu/install/src/install.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 0f0be1959..2269109ad 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -17,6 +17,7 @@ use file_diff::diff; use filetime::{set_file_times, FileTime}; use uucore::entries::{grp2gid, usr2uid}; use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity}; +use uucore::backup_control::{self, BackupMode}; use libc::{getegid, geteuid}; use std::fs; @@ -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, @@ -311,18 +313,17 @@ 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), From 54379857053f8412b45781fe258b82eed7b45fcc Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 23 Jun 2021 16:32:05 +0200 Subject: [PATCH 301/320] install: Implement '--backup' and '-b' Adds the ability to perform file backups before installing newer files on top of existing ones. Adds a status message about backups to stdout if running in verbose mode. --- src/uu/install/src/install.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 2269109ad..451545e62 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -520,6 +520,29 @@ 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 @@ -627,7 +650,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(()) From df41fed6402bd71eb6a2557bace8750513556f58 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 23 Jun 2021 16:33:52 +0200 Subject: [PATCH 302/320] install: Mark '-S', '-b' and '--backup' as implemented Removes the "unimplemented" notice from the respective help texts. Stop printing errors if the options are supplied via CLI. --- src/uu/install/src/install.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 451545e62..d25fc47bc 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -107,7 +107,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .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) @@ -117,7 +117,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // TODO implement flag 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) @@ -190,7 +190,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 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) @@ -268,14 +268,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { /// /// 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_BACKUP_NO_ARG) { - } 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") From 69e8838b27a02d6d714cac2d0e1d059a89409d14 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 23 Jun 2021 16:35:33 +0200 Subject: [PATCH 303/320] tests: install: Add tests for '--backup' Tests the '--backup' option for the install utility. Most of the code is adapted from the respective tests for 'mv'. --- tests/by-util/test_install.rs | 385 ++++++++++++++++++++++++++++++++++ 1 file changed, 385 insertions(+) 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))); +} From 233a7789634aa0c5f4fb2fc69e01411ed23e42c7 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 27 Jun 2021 12:52:46 +0200 Subject: [PATCH 304/320] sort/ls: implement version cmp matching GNU spec This reimplements version_cmp, which is used in sort and ls to sort according to versions. However, it is not bug-for-bug identical with GNU's implementation. I reported a bug with GNU here: https://lists.gnu.org/archive/html/bug-coreutils/2021-06/msg00045.html This implementation does not contain the bugs regarding the handling of file extensions and null bytes. --- Cargo.lock | 16 - src/uu/ls/src/ls.rs | 7 +- src/uu/ls/src/version_cmp.rs | 306 --------------- src/uu/sort/Cargo.toml | 1 - src/uu/sort/src/sort.rs | 31 +- src/uucore/src/lib/lib.rs | 1 + src/uucore/src/lib/mods.rs | 1 + src/uucore/src/lib/mods/version_cmp.rs | 361 ++++++++++++++++++ tests/by-util/test_sort.rs | 6 +- .../sort/version-empty-lines.expected | 4 + .../sort/version-empty-lines.expected.debug | 45 +++ tests/fixtures/sort/version-empty-lines.txt | 4 + 12 files changed, 423 insertions(+), 360 deletions(-) delete mode 100644 src/uu/ls/src/version_cmp.rs create mode 100644 src/uucore/src/lib/mods/version_cmp.rs create mode 100644 tests/fixtures/sort/version-empty-lines.expected.debug diff --git a/Cargo.lock b/Cargo.lock index 51424332d..e2dd7d5f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1434,21 +1434,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "sha1" version = "0.6.0" @@ -2488,7 +2473,6 @@ dependencies = [ "ouroboros", "rand 0.7.3", "rayon", - "semver", "tempfile", "unicode-width", "uucore", diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 6ca3f4bbe..b866253a9 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -14,7 +14,6 @@ extern crate uucore; extern crate lazy_static; mod quoting_style; -mod version_cmp; use clap::{crate_version, App, Arg}; use globset::{self, Glob, GlobSet, GlobSetBuilder}; @@ -44,6 +43,7 @@ use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use unicode_width::UnicodeWidthStr; #[cfg(unix)] 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 @@ -1256,7 +1256,8 @@ fn sort_entries(entries: &mut Vec, config: &Config) { } // The default sort in GNU ls is case insensitive Sort::Name => entries.sort_by(|a, b| a.display_name.cmp(&b.display_name)), - Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(&a.p_buf, &b.p_buf)), + Sort::Version => entries + .sort_by(|a, b| version_cmp(&a.p_buf.to_string_lossy(), &b.p_buf.to_string_lossy())), Sort::Extension => entries.sort_by(|a, b| { a.p_buf .extension() @@ -1467,8 +1468,6 @@ fn display_grid( } } -use uucore::fs::display_permissions; - fn display_item_long( item: &PathData, max_links: usize, diff --git a/src/uu/ls/src/version_cmp.rs b/src/uu/ls/src/version_cmp.rs deleted file mode 100644 index e3f7e29e3..000000000 --- a/src/uu/ls/src/version_cmp.rs +++ /dev/null @@ -1,306 +0,0 @@ -use std::cmp::Ordering; -use std::path::Path; - -/// Compare paths in a way that matches the GNU version sort, meaning that -/// numbers get sorted in a natural way. -pub(crate) fn version_cmp(a: &Path, b: &Path) -> Ordering { - let a_string = a.to_string_lossy(); - let b_string = b.to_string_lossy(); - let mut a = a_string.chars().peekable(); - let mut b = b_string.chars().peekable(); - - // The order determined from the number of leading zeroes. - // This is used if the filenames are equivalent up to leading zeroes. - let mut leading_zeroes = Ordering::Equal; - - loop { - match (a.next(), b.next()) { - // If the characters are both numerical. We collect the rest of the number - // and parse them to u64's and compare them. - (Some(a_char @ '0'..='9'), Some(b_char @ '0'..='9')) => { - let mut a_leading_zeroes = 0; - if a_char == '0' { - a_leading_zeroes = 1; - while let Some('0') = a.peek() { - a_leading_zeroes += 1; - a.next(); - } - } - - let mut b_leading_zeroes = 0; - if b_char == '0' { - b_leading_zeroes = 1; - while let Some('0') = b.peek() { - b_leading_zeroes += 1; - b.next(); - } - } - // The first different number of leading zeros determines the order - // so if it's already been determined by a previous number, we leave - // it as that ordering. - // It's b.cmp(&a), because the *largest* number of leading zeros - // should go first - if leading_zeroes == Ordering::Equal { - leading_zeroes = b_leading_zeroes.cmp(&a_leading_zeroes); - } - - let mut a_str = String::new(); - let mut b_str = String::new(); - if a_char != '0' { - a_str.push(a_char); - } - if b_char != '0' { - b_str.push(b_char); - } - - // Unwrapping here is fine because we only call next if peek returns - // Some(_), so next should also return Some(_). - while let Some('0'..='9') = a.peek() { - a_str.push(a.next().unwrap()); - } - - while let Some('0'..='9') = b.peek() { - b_str.push(b.next().unwrap()); - } - - // Since the leading zeroes are stripped, the length can be - // used to compare the numbers. - match a_str.len().cmp(&b_str.len()) { - Ordering::Equal => {} - x => return x, - } - - // At this point, leading zeroes are stripped and the lengths - // are equal, meaning that the strings can be compared using - // the standard compare function. - match a_str.cmp(&b_str) { - Ordering::Equal => {} - x => return x, - } - } - // If there are two characters we just compare the characters - (Some(a_char), Some(b_char)) => match a_char.cmp(&b_char) { - Ordering::Equal => {} - x => return x, - }, - // Otherwise, we compare the options (because None < Some(_)) - (a_opt, b_opt) => match a_opt.cmp(&b_opt) { - // If they are completely equal except for leading zeroes, we use the leading zeroes. - Ordering::Equal => return leading_zeroes, - x => return x, - }, - } - } -} - -#[cfg(test)] -mod tests { - use crate::version_cmp::version_cmp; - use std::cmp::Ordering; - use std::path::PathBuf; - #[test] - fn test_version_cmp() { - // Identical strings - assert_eq!( - version_cmp(&PathBuf::from("hello"), &PathBuf::from("hello")), - Ordering::Equal - ); - - assert_eq!( - version_cmp(&PathBuf::from("file12"), &PathBuf::from("file12")), - Ordering::Equal - ); - - assert_eq!( - version_cmp( - &PathBuf::from("file12-suffix"), - &PathBuf::from("file12-suffix") - ), - Ordering::Equal - ); - - assert_eq!( - version_cmp( - &PathBuf::from("file12-suffix24"), - &PathBuf::from("file12-suffix24") - ), - Ordering::Equal - ); - - // Shortened names - assert_eq!( - version_cmp(&PathBuf::from("world"), &PathBuf::from("wo")), - Ordering::Greater, - ); - - assert_eq!( - version_cmp(&PathBuf::from("hello10wo"), &PathBuf::from("hello10world")), - Ordering::Less, - ); - - // Simple names - assert_eq!( - version_cmp(&PathBuf::from("world"), &PathBuf::from("hello")), - Ordering::Greater, - ); - - assert_eq!( - version_cmp(&PathBuf::from("hello"), &PathBuf::from("world")), - Ordering::Less - ); - - assert_eq!( - version_cmp(&PathBuf::from("apple"), &PathBuf::from("ant")), - Ordering::Greater - ); - - assert_eq!( - version_cmp(&PathBuf::from("ant"), &PathBuf::from("apple")), - Ordering::Less - ); - - // Uppercase letters - assert_eq!( - version_cmp(&PathBuf::from("Beef"), &PathBuf::from("apple")), - Ordering::Less, - "Uppercase letters are sorted before all lowercase letters" - ); - - assert_eq!( - version_cmp(&PathBuf::from("Apple"), &PathBuf::from("apple")), - Ordering::Less - ); - - assert_eq!( - version_cmp(&PathBuf::from("apple"), &PathBuf::from("aPple")), - Ordering::Greater - ); - - // Numbers - assert_eq!( - version_cmp(&PathBuf::from("100"), &PathBuf::from("20")), - Ordering::Greater, - "Greater numbers are greater even if they start with a smaller digit", - ); - - assert_eq!( - version_cmp(&PathBuf::from("20"), &PathBuf::from("20")), - Ordering::Equal, - "Equal numbers are equal" - ); - - assert_eq!( - version_cmp(&PathBuf::from("15"), &PathBuf::from("200")), - Ordering::Less, - "Small numbers are smaller" - ); - - // Comparing numbers with other characters - assert_eq!( - version_cmp(&PathBuf::from("1000"), &PathBuf::from("apple")), - Ordering::Less, - "Numbers are sorted before other characters" - ); - - assert_eq!( - // spell-checker:disable-next-line - version_cmp(&PathBuf::from("file1000"), &PathBuf::from("fileapple")), - Ordering::Less, - "Numbers in the middle of the name are sorted before other characters" - ); - - // Leading zeroes - assert_eq!( - version_cmp(&PathBuf::from("012"), &PathBuf::from("12")), - Ordering::Less, - "A single leading zero can make a difference" - ); - - assert_eq!( - version_cmp(&PathBuf::from("000800"), &PathBuf::from("0000800")), - Ordering::Greater, - "Leading number of zeroes is used even if both non-zero number of zeros" - ); - - // Numbers and other characters combined - assert_eq!( - version_cmp(&PathBuf::from("ab10"), &PathBuf::from("aa11")), - Ordering::Greater - ); - - assert_eq!( - version_cmp(&PathBuf::from("aa10"), &PathBuf::from("aa11")), - Ordering::Less, - "Numbers after other characters are handled correctly." - ); - - assert_eq!( - version_cmp(&PathBuf::from("aa2"), &PathBuf::from("aa100")), - Ordering::Less, - "Numbers after alphabetical characters are handled correctly." - ); - - assert_eq!( - version_cmp(&PathBuf::from("aa10bb"), &PathBuf::from("aa11aa")), - Ordering::Less, - "Number is used even if alphabetical characters after it differ." - ); - - assert_eq!( - version_cmp(&PathBuf::from("aa10aa0010"), &PathBuf::from("aa11aa1")), - Ordering::Less, - "Second number is ignored if the first number differs." - ); - - assert_eq!( - version_cmp(&PathBuf::from("aa10aa0010"), &PathBuf::from("aa10aa1")), - Ordering::Greater, - "Second number is used if the rest is equal." - ); - - assert_eq!( - version_cmp(&PathBuf::from("aa10aa0010"), &PathBuf::from("aa00010aa1")), - Ordering::Greater, - "Second number is used if the rest is equal up to leading zeroes of the first number." - ); - - assert_eq!( - version_cmp(&PathBuf::from("aa10aa0022"), &PathBuf::from("aa010aa022")), - Ordering::Greater, - "The leading zeroes of the first number has priority." - ); - - assert_eq!( - version_cmp(&PathBuf::from("aa10aa0022"), &PathBuf::from("aa10aa022")), - Ordering::Less, - "The leading zeroes of other numbers than the first are used." - ); - - assert_eq!( - version_cmp(&PathBuf::from("file-1.4"), &PathBuf::from("file-1.13")), - Ordering::Less, - "Periods are handled as normal text, not as a decimal point." - ); - - // Greater than u64::Max - // u64 == 18446744073709551615 so this should be plenty: - // 20000000000000000000000 - assert_eq!( - version_cmp( - &PathBuf::from("aa2000000000000000000000bb"), - &PathBuf::from("aa002000000000000000000001bb") - ), - Ordering::Less, - "Numbers larger than u64::MAX are handled correctly without crashing" - ); - - assert_eq!( - version_cmp( - &PathBuf::from("aa2000000000000000000000bb"), - &PathBuf::from("aa002000000000000000000000bb") - ), - Ordering::Greater, - "Leading zeroes for numbers larger than u64::MAX are handled correctly without crashing" - ); - } -} diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index f06610248..d454179b0 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -24,7 +24,6 @@ memchr = "2.4.0" ouroboros = "0.9.3" rand = "0.7" rayon = "1.5" -semver = "0.9.0" tempfile = "3" unicode-width = "0.1.8" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index fb0241945..821def70d 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -32,7 +32,6 @@ use numeric_str_cmp::{human_numeric_str_cmp, numeric_str_cmp, NumInfo, NumInfoPa use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rayon::prelude::*; -use semver::Version; use std::cmp::Ordering; use std::env; use std::ffi::OsStr; @@ -44,6 +43,7 @@ use std::path::Path; use std::path::PathBuf; use unicode_width::UnicodeWidthStr; use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::version_cmp::version_cmp; use uucore::InvalidEncodingHandling; const NAME: &str = "sort"; @@ -1410,7 +1410,7 @@ fn compare_by<'a>( general_numeric_compare(a_float, b_float) } SortMode::Month => month_compare(a_str, b_str), - SortMode::Version => version_compare(a_str, b_str), + SortMode::Version => version_cmp(a_str, b_str), SortMode::Default => custom_str_cmp( a_str, b_str, @@ -1615,31 +1615,6 @@ fn month_compare(a: &str, b: &str) -> Ordering { } } -fn version_parse(a: &str) -> Version { - let result = Version::parse(a); - - match result { - Ok(vers_a) => vers_a, - // Non-version lines parse to 0.0.0 - Err(_e) => Version::parse("0.0.0").unwrap(), - } -} - -fn version_compare(a: &str, b: &str) -> Ordering { - #![allow(clippy::comparison_chain)] - let ver_a = version_parse(a); - let ver_b = version_parse(b); - - // Version::cmp is not implemented; implement comparison directly - if ver_a > ver_b { - Ordering::Greater - } else if ver_a < ver_b { - Ordering::Less - } else { - Ordering::Equal - } -} - fn print_sorted<'a, T: Iterator>>(iter: T, settings: &GlobalSettings) { let mut writer = settings.out_writer(); for line in iter { @@ -1712,7 +1687,7 @@ mod tests { let a = "1.2.3-alpha2"; let b = "1.4.0"; - assert_eq!(Ordering::Less, version_compare(a, b)); + assert_eq!(Ordering::Less, version_cmp(a, b)); } #[test] diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index bf2e5b1bb..ac505e42f 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -30,6 +30,7 @@ pub use crate::mods::coreopts; pub use crate::mods::os; pub use crate::mods::panic; pub use crate::mods::ranges; +pub use crate::mods::version_cmp; // * string parsing modules pub use crate::parser::parse_size; diff --git a/src/uucore/src/lib/mods.rs b/src/uucore/src/lib/mods.rs index 2689361a0..f81d05ed9 100644 --- a/src/uucore/src/lib/mods.rs +++ b/src/uucore/src/lib/mods.rs @@ -5,3 +5,4 @@ pub mod coreopts; pub mod os; pub mod panic; pub mod ranges; +pub mod version_cmp; diff --git a/src/uucore/src/lib/mods/version_cmp.rs b/src/uucore/src/lib/mods/version_cmp.rs new file mode 100644 index 000000000..99b8c8b40 --- /dev/null +++ b/src/uucore/src/lib/mods/version_cmp.rs @@ -0,0 +1,361 @@ +use std::cmp::Ordering; + +/// Compares the non-digit parts of a version. +/// Special cases: ~ are before everything else, even ends ("a~" < "a") +/// Letters are before non-letters +fn version_non_digit_cmp(a: &str, b: &str) -> Ordering { + let mut a_chars = a.chars(); + let mut b_chars = b.chars(); + loop { + match (a_chars.next(), b_chars.next()) { + (Some(c1), Some(c2)) if c1 == c2 => {} + (None, None) => return Ordering::Equal, + (_, Some('~')) => return Ordering::Greater, + (Some('~'), _) => return Ordering::Less, + (None, Some(_)) => return Ordering::Less, + (Some(_), None) => return Ordering::Greater, + (Some(c1), Some(c2)) if c1.is_ascii_alphabetic() && !c2.is_ascii_alphabetic() => { + return Ordering::Less + } + (Some(c1), Some(c2)) if !c1.is_ascii_alphabetic() && c2.is_ascii_alphabetic() => { + return Ordering::Greater + } + (Some(c1), Some(c2)) => return c1.cmp(&c2), + } + } +} + +/// Remove file endings matching the regex (\.[A-Za-z~][A-Za-z0-9~]*)*$ +fn remove_file_ending(a: &str) -> &str { + let mut ending_start = None; + let mut prev_was_dot = false; + for (idx, char) in a.char_indices() { + if char == '.' { + if ending_start.is_none() || prev_was_dot { + ending_start = Some(idx); + } + prev_was_dot = true; + } else if prev_was_dot { + prev_was_dot = false; + if !char.is_ascii_alphabetic() && char != '~' { + ending_start = None; + } + } else if !char.is_ascii_alphanumeric() && char != '~' { + ending_start = None; + } + } + if prev_was_dot { + ending_start = None; + } + if let Some(ending_start) = ending_start { + &a[..ending_start] + } else { + a + } +} + +pub fn version_cmp(mut a: &str, mut b: &str) -> Ordering { + let str_cmp = a.cmp(b); + if str_cmp == Ordering::Equal { + return str_cmp; + } + + // Special cases: + // 1. Empty strings + match (a.is_empty(), b.is_empty()) { + (true, false) => return Ordering::Less, + (false, true) => return Ordering::Greater, + (true, true) => unreachable!(), + (false, false) => {} + } + // 2. Dots + match (a == ".", b == ".") { + (true, false) => return Ordering::Less, + (false, true) => return Ordering::Greater, + (true, true) => unreachable!(), + (false, false) => {} + } + // 3. Two Dots + match (a == "..", b == "..") { + (true, false) => return Ordering::Less, + (false, true) => return Ordering::Greater, + (true, true) => unreachable!(), + (false, false) => {} + } + // 4. Strings starting with a dot + match (a.starts_with('.'), b.starts_with('.')) { + (true, false) => return Ordering::Less, + (false, true) => return Ordering::Greater, + (true, true) => { + // Strip the leading dot for later comparisons + a = &a[1..]; + b = &b[1..]; + } + _ => {} + } + + // Try to strip file extensions + let (mut a, mut b) = match (remove_file_ending(a), remove_file_ending(b)) { + (a_stripped, b_stripped) if a_stripped == b_stripped => { + // If both would be the same after stripping file extensions, don't strip them. + (a, b) + } + stripped => stripped, + }; + + // 1. Compare leading non-numerical part + // 2. Compare leading numerical part + // 3. Repeat + loop { + let a_numerical_start = a.find(|c: char| c.is_ascii_digit()).unwrap_or(a.len()); + let b_numerical_start = b.find(|c: char| c.is_ascii_digit()).unwrap_or(b.len()); + + let a_str = &a[..a_numerical_start]; + let b_str = &b[..b_numerical_start]; + + match version_non_digit_cmp(a_str, b_str) { + Ordering::Equal => {} + ord => return ord, + } + + a = &a[a_numerical_start..]; + b = &b[a_numerical_start..]; + + let a_numerical_end = a.find(|c: char| !c.is_ascii_digit()).unwrap_or(a.len()); + let b_numerical_end = b.find(|c: char| !c.is_ascii_digit()).unwrap_or(b.len()); + + let a_str = a[..a_numerical_end].trim_start_matches('0'); + let b_str = b[..b_numerical_end].trim_start_matches('0'); + + match a_str.len().cmp(&b_str.len()) { + Ordering::Equal => {} + ord => return ord, + } + + match a_str.cmp(b_str) { + Ordering::Equal => {} + ord => return ord, + } + + a = &a[a_numerical_end..]; + b = &b[b_numerical_end..]; + + if a.is_empty() && b.is_empty() { + // Default to the lexical comparison. + return str_cmp; + } + } +} + +#[cfg(test)] +mod tests { + use crate::version_cmp::version_cmp; + use std::cmp::Ordering; + #[test] + fn test_version_cmp() { + // Identical strings + assert_eq!(version_cmp("hello", "hello"), Ordering::Equal); + + assert_eq!(version_cmp("file12", "file12"), Ordering::Equal); + + assert_eq!( + version_cmp("file12-suffix", "file12-suffix"), + Ordering::Equal + ); + + assert_eq!( + version_cmp("file12-suffix24", "file12-suffix24"), + Ordering::Equal + ); + + // Shortened names + assert_eq!(version_cmp("world", "wo"), Ordering::Greater,); + + assert_eq!(version_cmp("hello10wo", "hello10world"), Ordering::Less,); + + // Simple names + assert_eq!(version_cmp("world", "hello"), Ordering::Greater,); + + assert_eq!(version_cmp("hello", "world"), Ordering::Less); + + assert_eq!(version_cmp("apple", "ant"), Ordering::Greater); + + assert_eq!(version_cmp("ant", "apple"), Ordering::Less); + + // Uppercase letters + assert_eq!( + version_cmp("Beef", "apple"), + Ordering::Less, + "Uppercase letters are sorted before all lowercase letters" + ); + + assert_eq!(version_cmp("Apple", "apple"), Ordering::Less); + + assert_eq!(version_cmp("apple", "aPple"), Ordering::Greater); + + // Numbers + assert_eq!( + version_cmp("100", "20"), + Ordering::Greater, + "Greater numbers are greater even if they start with a smaller digit", + ); + + assert_eq!( + version_cmp("20", "20"), + Ordering::Equal, + "Equal numbers are equal" + ); + + assert_eq!( + version_cmp("15", "200"), + Ordering::Less, + "Small numbers are smaller" + ); + + // Comparing numbers with other characters + assert_eq!( + version_cmp("1000", "apple"), + Ordering::Less, + "Numbers are sorted before other characters" + ); + + assert_eq!( + // spell-checker:disable-next-line + version_cmp("file1000", "fileapple"), + Ordering::Less, + "Numbers in the middle of the name are sorted before other characters" + ); + + // Leading zeroes + assert_eq!( + version_cmp("012", "12"), + Ordering::Less, + "A single leading zero can make a difference" + ); + + assert_eq!( + version_cmp("000800", "0000800"), + Ordering::Greater, + "Leading number of zeroes is used even if both non-zero number of zeros" + ); + + // Numbers and other characters combined + assert_eq!(version_cmp("ab10", "aa11"), Ordering::Greater); + + assert_eq!( + version_cmp("aa10", "aa11"), + Ordering::Less, + "Numbers after other characters are handled correctly." + ); + + assert_eq!( + version_cmp("aa2", "aa100"), + Ordering::Less, + "Numbers after alphabetical characters are handled correctly." + ); + + assert_eq!( + version_cmp("aa10bb", "aa11aa"), + Ordering::Less, + "Number is used even if alphabetical characters after it differ." + ); + + assert_eq!( + version_cmp("aa10aa0010", "aa11aa1"), + Ordering::Less, + "Second number is ignored if the first number differs." + ); + + assert_eq!( + version_cmp("aa10aa0010", "aa10aa1"), + Ordering::Greater, + "Second number is used if the rest is equal." + ); + + assert_eq!( + version_cmp("aa10aa0010", "aa00010aa1"), + Ordering::Greater, + "Second number is used if the rest is equal up to leading zeroes of the first number." + ); + + assert_eq!( + version_cmp("aa10aa0022", "aa010aa022"), + Ordering::Greater, + "The leading zeroes of the first number has priority." + ); + + assert_eq!( + version_cmp("aa10aa0022", "aa10aa022"), + Ordering::Less, + "The leading zeroes of other numbers than the first are used." + ); + + assert_eq!( + version_cmp("file-1.4", "file-1.13"), + Ordering::Less, + "Periods are handled as normal text, not as a decimal point." + ); + + // Greater than u64::Max + // u64 == 18446744073709551615 so this should be plenty: + // 20000000000000000000000 + assert_eq!( + version_cmp("aa2000000000000000000000bb", "aa002000000000000000000001bb"), + Ordering::Less, + "Numbers larger than u64::MAX are handled correctly without crashing" + ); + + assert_eq!( + version_cmp("aa2000000000000000000000bb", "aa002000000000000000000000bb"), + Ordering::Greater, + "Leading zeroes for numbers larger than u64::MAX are \ + handled correctly without crashing" + ); + + assert_eq!( + version_cmp(" a", "a"), + Ordering::Greater, + "Whitespace is after letters because letters are before non-letters" + ); + + assert_eq!( + version_cmp("a~", "ab"), + Ordering::Less, + "A tilde is before other letters" + ); + + assert_eq!( + version_cmp("a~", "a"), + Ordering::Less, + "A tilde is before the line end" + ); + assert_eq!( + version_cmp("~", ""), + Ordering::Greater, + "A tilde is after the empty string" + ); + assert_eq!( + version_cmp(".f", ".1"), + Ordering::Greater, + "if both start with a dot it is ignored for the comparison" + ); + + // The following tests are incompatible with GNU as of 2021/06. + // I think that's because of a bug in GNU, reported as https://lists.gnu.org/archive/html/bug-coreutils/2021-06/msg00045.html + assert_eq!( + version_cmp("a..a", "a.+"), + Ordering::Less, + ".a is stripped before the comparison" + ); + assert_eq!( + version_cmp("a.", "a+"), + Ordering::Greater, + ". is not stripped before the comparison" + ); + assert_eq!( + version_cmp("a\0a", "a"), + Ordering::Greater, + "NULL bytes are handled comparison" + ); + } +} diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 3e841f630..2c99c1536 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -127,11 +127,7 @@ fn test_months_whitespace() { #[test] fn test_version_empty_lines() { - new_ucmd!() - .arg("-V") - .arg("version-empty-lines.txt") - .succeeds() - .stdout_is("\n\n\n\n\n\n\n1.2.3-alpha\n1.2.3-alpha2\n\t\t\t1.12.4\n11.2.3\n"); + test_helper("version-empty-lines", &["-V", "--version-sort"]); } #[test] diff --git a/tests/fixtures/sort/version-empty-lines.expected b/tests/fixtures/sort/version-empty-lines.expected index c496c0ff5..69a648966 100644 --- a/tests/fixtures/sort/version-empty-lines.expected +++ b/tests/fixtures/sort/version-empty-lines.expected @@ -8,4 +8,8 @@ 1.2.3-alpha 1.2.3-alpha2 11.2.3 +bar2 +bar2.0.0 +foo0.1 +foo1.0 1.12.4 diff --git a/tests/fixtures/sort/version-empty-lines.expected.debug b/tests/fixtures/sort/version-empty-lines.expected.debug new file mode 100644 index 000000000..d3f2aaceb --- /dev/null +++ b/tests/fixtures/sort/version-empty-lines.expected.debug @@ -0,0 +1,45 @@ + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key +1.2.3-alpha +___________ +___________ +1.2.3-alpha2 +____________ +____________ +11.2.3 +______ +______ +bar2 +____ +____ +bar2.0.0 +________ +________ +foo0.1 +______ +______ +foo1.0 +______ +______ +>>>1.12.4 +_________ +_________ diff --git a/tests/fixtures/sort/version-empty-lines.txt b/tests/fixtures/sort/version-empty-lines.txt index 9b6b89788..fef474259 100644 --- a/tests/fixtures/sort/version-empty-lines.txt +++ b/tests/fixtures/sort/version-empty-lines.txt @@ -9,3 +9,7 @@ 1.12.4 +foo1.0 +foo0.1 +bar2.0.0 +bar2 \ No newline at end of file From 0dda72eb60b86ae89b53f2393e2d11242389cfc3 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 27 Jun 2021 15:48:16 +0200 Subject: [PATCH 305/320] coreutils: better errors for invalid args for completions Use clap to extract command line arguments. This generates much better error messages. --- src/bin/coreutils.rs | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) 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); From 2ebca384c6a782c30bb5615253f12580cc1dfcca Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 27 Jun 2021 16:15:49 +0200 Subject: [PATCH 306/320] all utils: enable wrap_help This makes clap wrap the help text according to the terminal width, which improves readability for terminal widths < 120 chars, because clap defaults to a width of 120 chars without this feature. --- Cargo.lock | 1 + Cargo.toml | 2 +- src/uu/arch/Cargo.toml | 2 +- src/uu/base32/Cargo.toml | 2 +- src/uu/base64/Cargo.toml | 2 +- src/uu/basename/Cargo.toml | 2 +- src/uu/cat/Cargo.toml | 2 +- src/uu/chgrp/Cargo.toml | 2 +- src/uu/chmod/Cargo.toml | 2 +- src/uu/chown/Cargo.toml | 2 +- src/uu/cksum/Cargo.toml | 2 +- src/uu/comm/Cargo.toml | 2 +- src/uu/cp/Cargo.toml | 2 +- src/uu/csplit/Cargo.toml | 2 +- src/uu/cut/Cargo.toml | 2 +- src/uu/date/Cargo.toml | 2 +- src/uu/df/Cargo.toml | 2 +- src/uu/dircolors/Cargo.toml | 2 +- src/uu/dirname/Cargo.toml | 2 +- src/uu/du/Cargo.toml | 2 +- src/uu/echo/Cargo.toml | 2 +- src/uu/env/Cargo.toml | 2 +- src/uu/expand/Cargo.toml | 2 +- src/uu/expr/Cargo.toml | 2 +- src/uu/factor/Cargo.toml | 2 +- src/uu/false/Cargo.toml | 2 +- src/uu/fmt/Cargo.toml | 2 +- src/uu/fold/Cargo.toml | 2 +- src/uu/groups/Cargo.toml | 2 +- src/uu/hashsum/Cargo.toml | 2 +- src/uu/head/Cargo.toml | 2 +- src/uu/hostid/Cargo.toml | 2 +- src/uu/hostname/Cargo.toml | 2 +- src/uu/id/Cargo.toml | 2 +- src/uu/install/Cargo.toml | 2 +- src/uu/join/Cargo.toml | 2 +- src/uu/kill/Cargo.toml | 2 +- src/uu/link/Cargo.toml | 2 +- src/uu/ln/Cargo.toml | 2 +- src/uu/logname/Cargo.toml | 2 +- src/uu/ls/Cargo.toml | 2 +- src/uu/mkdir/Cargo.toml | 2 +- src/uu/mkfifo/Cargo.toml | 2 +- src/uu/mknod/Cargo.toml | 2 +- src/uu/mktemp/Cargo.toml | 2 +- src/uu/more/Cargo.toml | 2 +- src/uu/mv/Cargo.toml | 2 +- src/uu/nice/Cargo.toml | 2 +- src/uu/nl/Cargo.toml | 2 +- src/uu/nohup/Cargo.toml | 2 +- src/uu/nproc/Cargo.toml | 2 +- src/uu/numfmt/Cargo.toml | 2 +- src/uu/od/Cargo.toml | 2 +- src/uu/paste/Cargo.toml | 2 +- src/uu/pathchk/Cargo.toml | 2 +- src/uu/pinky/Cargo.toml | 2 +- src/uu/pr/Cargo.toml | 2 +- src/uu/printenv/Cargo.toml | 2 +- src/uu/printf/Cargo.toml | 2 +- src/uu/ptx/Cargo.toml | 2 +- src/uu/pwd/Cargo.toml | 2 +- src/uu/readlink/Cargo.toml | 2 +- src/uu/realpath/Cargo.toml | 2 +- src/uu/relpath/Cargo.toml | 2 +- src/uu/rm/Cargo.toml | 2 +- src/uu/rmdir/Cargo.toml | 2 +- src/uu/seq/Cargo.toml | 2 +- src/uu/shred/Cargo.toml | 2 +- src/uu/shuf/Cargo.toml | 2 +- src/uu/sleep/Cargo.toml | 2 +- src/uu/sort/Cargo.toml | 2 +- src/uu/split/Cargo.toml | 2 +- src/uu/stat/Cargo.toml | 2 +- src/uu/stdbuf/Cargo.toml | 2 +- src/uu/sum/Cargo.toml | 2 +- src/uu/sync/Cargo.toml | 2 +- src/uu/tac/Cargo.toml | 2 +- src/uu/tail/Cargo.toml | 2 +- src/uu/tee/Cargo.toml | 2 +- src/uu/test/Cargo.toml | 2 +- src/uu/timeout/Cargo.toml | 2 +- src/uu/touch/Cargo.toml | 2 +- src/uu/tr/Cargo.toml | 2 +- src/uu/true/Cargo.toml | 2 +- src/uu/truncate/Cargo.toml | 2 +- src/uu/tty/Cargo.toml | 2 +- src/uu/uname/Cargo.toml | 2 +- src/uu/unexpand/Cargo.toml | 2 +- src/uu/uniq/Cargo.toml | 2 +- src/uu/unlink/Cargo.toml | 2 +- src/uu/uptime/Cargo.toml | 2 +- src/uu/users/Cargo.toml | 2 +- src/uu/wc/Cargo.toml | 2 +- src/uu/who/Cargo.toml | 2 +- src/uu/whoami/Cargo.toml | 2 +- src/uu/yes/Cargo.toml | 2 +- 96 files changed, 96 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51424332d..ba289cc5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,6 +177,7 @@ dependencies = [ "atty", "bitflags", "strsim", + "term_size", "textwrap", "unicode-width", "vec_map", diff --git a/Cargo.toml b/Cargo.toml index 2783ff1b4..02d0abb88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -225,7 +225,7 @@ test = [ "uu_test" ] [workspace] [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } lazy_static = { version="1.3" } textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review uucore = { version=">=0.0.8", package="uucore", path="src/uucore" } diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index b3fe1f8cb..855b577d6 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -16,7 +16,7 @@ path = "src/arch.rs" [dependencies] platform-info = "0.1" -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index 1b448af0a..a024c49db 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/base32.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index d4ee69f03..202c6511b 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/base64.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"} diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index 0072619b7..9912dfd87 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/basename.rs" [dependencies] -clap = "2.33.2" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index 9218e84fe..f20cddcf9 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/cat.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } thiserror = "1.0" atty = "0.2" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 0e43f7c02..619bdaaad 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/chgrp.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } walkdir = "2.2" diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index ac7030b62..c523829f3 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/chmod.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs", "mode"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index 74533af04..f19ed39a8 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/chown.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } glob = "0.3.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index 0332efbf8..792c6c0c7 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/cksum.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index f02217790..b1f8948e7 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/comm.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 9d582adae..76990863d 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -19,7 +19,7 @@ edition = "2018" path = "src/cp.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } filetime = "0.2" libc = "0.2.85" quick-error = "1.2.3" diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index 7687991b0..48655316f 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/csplit.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } thiserror = "1.0" regex = "1.0.0" glob = "0.2.11" diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index 47b8223c5..9a83ff554 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/cut.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } memchr = "2" diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index db6c077bd..3751e071e 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -16,7 +16,7 @@ path = "src/date.rs" [dependencies] chrono = "0.4.4" -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index 0e65fdb32..4700d419a 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/df.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } number_prefix = "0.4" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc", "fsext"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index 7d47fa5c4..a97c78c78 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/dircolors.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } glob = "0.3.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/dirname/Cargo.toml b/src/uu/dirname/Cargo.toml index 0975f33bb..2375d66c9 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/dirname.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index dcd1f720e..60f37db06 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/du.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } chrono = "0.4" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/echo/Cargo.toml b/src/uu/echo/Cargo.toml index 15f189030..5ba44d4a8 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/echo.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index ef0017e02..7cbd812c2 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/env.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" rust-ini = "0.13.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index 4931cf53c..2119897b4 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/expand.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } unicode-width = "0.1.5" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index 0906856d1..4211a2d25 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/expr.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" num-bigint = "0.4.0" num-traits = "0.2.14" diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index eb977760f..c9cfe78ab 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -21,7 +21,7 @@ rand = { version = "0.7", features = ["small_rng"] } smallvec = { version = "0.6.14, < 1.0" } uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" } uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" } -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } [dev-dependencies] paste = "0.1.18" diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index 644051d59..93913b7e2 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/false.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/fmt/Cargo.toml b/src/uu/fmt/Cargo.toml index 24ee13b35..fdb1f8ca4 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/fmt.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" unicode-width = "0.1.5" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } diff --git a/src/uu/fold/Cargo.toml b/src/uu/fold/Cargo.toml index c5578384e..50ed34388 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/fold.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index 4a5a537e5..14ee44d18 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -17,7 +17,7 @@ path = "src/groups.rs" [dependencies] uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "process"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } [[bin]] name = "groups" diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 11388ebf8..87a2b8aa1 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -16,7 +16,7 @@ path = "src/hashsum.rs" [dependencies] digest = "0.6.2" -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } hex = "0.2.0" libc = "0.2.42" md5 = "0.3.5" diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index 661052f58..a0f1f9d95 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/head.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index ab8b43f05..95e20db68 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/hostid.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/hostname/Cargo.toml b/src/uu/hostname/Cargo.toml index fb1d00682..e4d78441c 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/hostname.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" hostname = { version = "0.3", features = ["set"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["wide"] } diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index 308d6089d..97f478ef9 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/id.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "process"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 91463199a..5beef2b29 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -18,7 +18,7 @@ edition = "2018" path = "src/install.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } filetime = "0.2" file_diff = "1.0.0" libc = ">= 0.2" diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index 9371b7601..21284a6c3 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/join.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index e33411c70..c3a5368d9 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/kill.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["signals"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index 14a6ac7c9..0457ec479 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -18,7 +18,7 @@ path = "src/link.rs" libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } [[bin]] name = "link" diff --git a/src/uu/ln/Cargo.toml b/src/uu/ln/Cargo.toml index c19d8fb52..4386d7522 100644 --- a/src/uu/ln/Cargo.toml +++ b/src/uu/ln/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/ln.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index 4aa4d68f4..2a541073f 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -16,7 +16,7 @@ path = "src/logname.rs" [dependencies] libc = "0.2.42" -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index ab58a7300..ecd4f1b8d 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -17,7 +17,7 @@ path = "src/ls.rs" [dependencies] locale = "0.2.2" chrono = "0.4.19" -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } unicode-width = "0.1.8" number_prefix = "0.4" term_grid = "0.1.5" diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index a8d374bf9..ad7972f2d 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/mkdir.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs", "mode"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index d66003b10..5a78183ea 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/mkfifo.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 1320e3546..c7ba535fd 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -16,7 +16,7 @@ name = "uu_mknod" path = "src/mknod.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "^0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["mode"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/mktemp/Cargo.toml b/src/uu/mktemp/Cargo.toml index c669f0acc..93fb88857 100644 --- a/src/uu/mktemp/Cargo.toml +++ b/src/uu/mktemp/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/mktemp.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } rand = "0.5" tempfile = "3.1" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index af6781876..497f91f4e 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/more.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" } uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" } crossterm = ">=0.19" diff --git a/src/uu/mv/Cargo.toml b/src/uu/mv/Cargo.toml index 8f1e7b9ee..94d3de15e 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/mv.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } fs_extra = "1.1.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index 279e79ae3..eed524b8a 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/nice.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" nix = { version="<=0.13" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } diff --git a/src/uu/nl/Cargo.toml b/src/uu/nl/Cargo.toml index a51a2555e..4197bfd8e 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/nl.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } aho-corasick = "0.7.3" libc = "0.2.42" memchr = "2.2.0" diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index 839219a84..f7166a4b6 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/nohup.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" atty = "0.2" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } diff --git a/src/uu/nproc/Cargo.toml b/src/uu/nproc/Cargo.toml index be9d8f2e3..a4eec07eb 100644 --- a/src/uu/nproc/Cargo.toml +++ b/src/uu/nproc/Cargo.toml @@ -17,7 +17,7 @@ path = "src/nproc.rs" [dependencies] libc = "0.2.42" num_cpus = "1.10" -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index ac5266d68..7a81e36d6 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/numfmt.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index 6f9a75318..24da14b31 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -16,7 +16,7 @@ path = "src/od.rs" [dependencies] byteorder = "1.3.2" -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } half = "1.6" libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } diff --git a/src/uu/paste/Cargo.toml b/src/uu/paste/Cargo.toml index 4e9971368..cfc70a3bd 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/paste.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/pathchk/Cargo.toml b/src/uu/pathchk/Cargo.toml index 8c4e61d2b..c39eb6e16 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/pathchk.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index a3c36259a..2cdb28d92 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -17,7 +17,7 @@ path = "src/pinky.rs" [dependencies] uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx", "entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } [[bin]] name = "pinky" diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index de519161a..122bed694 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/pr.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["utmpx", "entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } getopts = "0.2.21" diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index be95b8157..faa24a33b 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/printenv.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index 13d54fcca..f980837e7 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -18,7 +18,7 @@ edition = "2018" path = "src/printf.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } itertools = "0.8.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index eb4413cbd..1ccdd9ad4 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/ptx.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } aho-corasick = "0.7.3" libc = "0.2.42" memchr = "2.2.0" diff --git a/src/uu/pwd/Cargo.toml b/src/uu/pwd/Cargo.toml index f4350d54c..3393a63b3 100644 --- a/src/uu/pwd/Cargo.toml +++ b/src/uu/pwd/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/pwd.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/readlink/Cargo.toml b/src/uu/readlink/Cargo.toml index 6e4be4dd8..65b5c149b 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/readlink.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/realpath/Cargo.toml b/src/uu/realpath/Cargo.toml index 327a875f8..dc21bdaca 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/realpath.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/relpath/Cargo.toml b/src/uu/relpath/Cargo.toml index 7a316c29c..1240d9b1b 100644 --- a/src/uu/relpath/Cargo.toml +++ b/src/uu/relpath/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/relpath.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index d84756fd3..20fd60745 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/rm.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } walkdir = "2.2" remove_dir_all = "0.5.1" winapi = { version="0.3", features=[] } diff --git a/src/uu/rmdir/Cargo.toml b/src/uu/rmdir/Cargo.toml index b6e04f71c..40c2efbb1 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/rmdir.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index 32f2bbac8..726c7242b 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/seq.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } num-bigint = "0.4.0" num-traits = "0.2.14" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index dda68b45b..dafff162b 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/shred.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } filetime = "0.2.1" libc = "0.2.42" rand = "0.5" diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index dbf559454..6c0353681 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/shuf.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } rand = "0.5" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/sleep/Cargo.toml b/src/uu/sleep/Cargo.toml index 618ea7e28..14c4c5300 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/sleep.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index f06610248..91580a7d9 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -16,7 +16,7 @@ path = "src/sort.rs" [dependencies] binary-heap-plus = "0.4.1" -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } compare = "0.1.0" fnv = "1.0.7" itertools = "0.10.0" diff --git a/src/uu/split/Cargo.toml b/src/uu/split/Cargo.toml index 056fbe034..e19695a39 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/split.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index 86b7da139..81af993a5 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/stat.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "libc", "fs", "fsext"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 884a98785..a3eb059eb 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/stdbuf.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } tempfile = "3.1" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/sum/Cargo.toml b/src/uu/sum/Cargo.toml index 64b6d3de9..e16c865a3 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/sum.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index fcff6002e..83efb815d 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/sync.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["wide"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 3a530d0ce..2d0623cd9 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/tac.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index 273c67bb3..a895819cd 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/tail.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index 7ac81adc4..a88d76508 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/tee.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" retain_mut = "0.1.2" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc"] } diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index cd0282a45..6f6dd340e 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/test.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index 70ce64630..63a16c086 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/timeout.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" nix = "0.20.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["process", "signals"] } diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index 0608a7b7c..e2f948a5a 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -16,7 +16,7 @@ path = "src/touch.rs" [dependencies] filetime = "0.2.1" -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } time = "0.1.40" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index a3d066bfb..7783db144 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -17,7 +17,7 @@ path = "src/tr.rs" [dependencies] bit-set = "0.5.0" fnv = "1.0.5" -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index f121d56de..06e7c35ff 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/true.rs" [dependencies] -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/truncate/Cargo.toml b/src/uu/truncate/Cargo.toml index e2c0afadc..50d3dc4f3 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/truncate.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index 49b7669df..90396ff40 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/tty.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" atty = "0.2" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } diff --git a/src/uu/uname/Cargo.toml b/src/uu/uname/Cargo.toml index 9707d8444..54a1591a2 100644 --- a/src/uu/uname/Cargo.toml +++ b/src/uu/uname/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/uname.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } platform-info = "0.1" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index e39dd87ca..5e47d8b58 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/unexpand.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } unicode-width = "0.1.5" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index 3fe89b450..be082fe88 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/uniq.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } strum = "0.20" strum_macros = "0.20" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index 08da2624e..ef0f291f8 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/unlink.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index 1136e6420..eec745ab1 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -16,7 +16,7 @@ path = "src/uptime.rs" [dependencies] chrono = "0.4" -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc", "utmpx"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/users/Cargo.toml b/src/uu/users/Cargo.toml index 84da13020..6cafd7c32 100644 --- a/src/uu/users/Cargo.toml +++ b/src/uu/users/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/users.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 8ae79dc08..ad4301e7a 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/wc.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } thiserror = "1.0" diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index 4d8eccb45..06388c7bf 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -17,7 +17,7 @@ path = "src/who.rs" [dependencies] uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } -clap = "2.33.3" +clap = { version = "2.33", features = ["wrap_help"] } [[bin]] name = "who" diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index 28670c8b5..a7fc19848 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/whoami.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "wide"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index 4a843ddd8..0338a4037 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/yes.rs" [dependencies] -clap = "2.33" +clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["zero-copy"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } From d3732e08c4bbcd518d18017e102cbe8b1e5373c1 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 27 Jun 2021 15:34:20 +0200 Subject: [PATCH 307/320] sort: disable -o with -C and -c --- src/uu/sort/src/sort.rs | 2 ++ tests/by-util/test_sort.rs | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index fb0241945..0bad9de6b 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1198,12 +1198,14 @@ pub fn uu_app() -> App<'static, 'static> { options::check::QUIET, options::check::DIAGNOSE_FIRST, ]) + .conflicts_with(options::OUTPUT) .help("check for sorted input; do not sort"), ) .arg( Arg::with_name(options::check::CHECK_SILENT) .short("C") .long(options::check::CHECK_SILENT) + .conflicts_with(options::OUTPUT) .help("exit successfully if the given file is already sorted, and exit with status 1 otherwise."), ) .arg( diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 3e841f630..ea98643f7 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -941,3 +941,17 @@ fn test_sigpipe_panic() { Ok(String::new()) ); } + +#[test] +fn test_conflict_check_out() { + let check_flags = ["-c=silent", "-c=quiet", "-c=diagnose-first", "-c", "-C"]; + for check_flag in &check_flags { + new_ucmd!() + .arg(check_flag) + .arg("-o=/dev/null") + .fails() + .stderr_contains( + "error: The argument '--output ' cannot be used with '--check", + ); + } +} From 1a43a94e3147b2187cd4f37f350d218c2607aacf Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Sun, 27 Jun 2021 09:16:16 +0200 Subject: [PATCH 308/320] install: Apply rustfmt --- src/uu/install/src/install.rs | 8 +++----- src/uucore/src/lib/mods/backup_control.rs | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index d25fc47bc..3b1384d8a 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -15,9 +15,9 @@ 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}; -use uucore::backup_control::{self, BackupMode}; use libc::{getegid, geteuid}; use std::fs; @@ -315,8 +315,7 @@ fn behavior(matches: &ArgMatches) -> Result { 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)), + 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), @@ -521,8 +520,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> { // 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); + 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) { diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index bbe9cd227..b8f389c83 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -38,17 +38,17 @@ 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 { From d8a33da5b3ae5bbf0e4d5c2bfb2d25a735b2cae5 Mon Sep 17 00:00:00 2001 From: Syukron Rifail M Date: Sat, 19 Jun 2021 23:02:39 +0700 Subject: [PATCH 309/320] du: add --inodes --- src/uu/du/src/du.rs | 49 ++++++++++++++++++++------- tests/by-util/test_du.rs | 71 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 12 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index e5f0e6efc..a55156ca3 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 { + // 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 + } +} + // 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/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 67036be44..240d929fa 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!() From 27e05078442d078f07492029f439099cb75760c6 Mon Sep 17 00:00:00 2001 From: Syukron Rifail M Date: Sat, 26 Jun 2021 07:34:04 +0700 Subject: [PATCH 310/320] du: more tests --- src/uu/du/src/du.rs | 2 +- tests/by-util/test_du.rs | 93 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index a55156ca3..05167853c 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -269,7 +269,7 @@ fn choose_size(matches: &ArgMatches, stat: &Stat) -> u64 { } else 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 + // 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 } diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 240d929fa..029f5e516 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -490,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"); +} From 66b1ac019da5459d0edc6c1c903ffb99c7492acc Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 28 Jun 2021 13:38:47 +0200 Subject: [PATCH 311/320] uucore/error: add standardized error handling (adds UResult & UError) --- src/uucore/src/lib/lib.rs | 1 + src/uucore/src/lib/mods.rs | 1 + src/uucore/src/lib/mods/error.rs | 479 +++++++++++++++++++++++++++++++ 3 files changed, 481 insertions(+) create mode 100644 src/uucore/src/lib/mods/error.rs diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index bf2e5b1bb..00477cd7e 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -27,6 +27,7 @@ mod parser; // string parsing modules // * cross-platform modules pub use crate::mods::backup_control; pub use crate::mods::coreopts; +pub use crate::mods::error; pub use crate::mods::os; pub use crate::mods::panic; pub use crate::mods::ranges; diff --git a/src/uucore/src/lib/mods.rs b/src/uucore/src/lib/mods.rs index 2689361a0..040da2f02 100644 --- a/src/uucore/src/lib/mods.rs +++ b/src/uucore/src/lib/mods.rs @@ -2,6 +2,7 @@ pub mod backup_control; pub mod coreopts; +pub mod error; pub mod os; pub mod panic; pub mod ranges; diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs new file mode 100644 index 000000000..f13c777b6 --- /dev/null +++ b/src/uucore/src/lib/mods/error.rs @@ -0,0 +1,479 @@ +//! All utils return exit with an exit code. Usually, the following scheme is used: +//! * `0`: succeeded +//! * `1`: minor problems +//! * `2`: major problems +//! +//! This module provides types to reconcile these exit codes with idiomatic Rust error +//! handling. This has a couple advantages over manually using [`std::process::exit`]: +//! 1. It enables the use of `?`, `map_err`, `unwrap_or`, etc. in `uumain`. +//! 1. It encourages the use of `UResult`/`Result` in functions in the utils. +//! 1. The error messages are largely standardized across utils. +//! 1. Standardized error messages can be created from external result types +//! (i.e. [`std::io::Result`] & `clap::ClapResult`). +//! 1. `set_exit_code` takes away the burden of manually tracking exit codes for non-fatal errors. +//! +//! # Usage +//! The signature of a typical util should be: +//! ```ignore +//! fn uumain(args: impl uucore::Args) -> UResult<()> { +//! ... +//! } +//! ``` +//! [`UResult`] is a simple wrapper around [`Result`] with a custom error type: [`UError`]. The +//! most important difference with types implementing [`std::error::Error`] is that [`UError`]s +//! can specify the exit code of the program when they are returned from `uumain`: +//! * When `Ok` is returned, the code set with [`set_exit_code`] is used as exit code. If +//! [`set_exit_code`] was not used, then `0` is used. +//! * When `Err` is returned, the code corresponding with the error is used as exit code and the +//! error message is displayed. +//! +//! Additionally, the errors can be displayed manually with the [`show`] and [`show_if_err`] macros: +//! ```ignore +//! let res = Err(USimpleError::new(1, "Error!!")); +//! show_if_err!(res); +//! // or +//! if let Err(e) = res { +//! show!(e); +//! } +//! ``` +//! +//! **Note**: The [`show`] and [`show_if_err`] macros set the exit code of the program using +//! [`set_exit_code`]. See the documentation on that function for more information. +//! +//! # Guidelines +//! * Use common errors where possible. +//! * Add variants to [`UCommonError`] if an error appears in multiple utils. +//! * Prefer proper custom error types over [`ExitCode`] and [`USimpleError`]. +//! * [`USimpleError`] may be used in small utils with simple error handling. +//! * Using [`ExitCode`] is not recommended but can be useful for converting utils to use +//! [`UResult`]. + +use std::{ + error::Error, + fmt::{Display, Formatter}, + sync::atomic::{AtomicI32, Ordering}, +}; + +static EXIT_CODE: AtomicI32 = AtomicI32::new(0); + +/// Get the last exit code set with [`set_exit_code`]. +/// The default value is `0`. +pub fn get_exit_code() -> i32 { + EXIT_CODE.load(Ordering::SeqCst) +} + +/// Set the exit code for the program if `uumain` returns `Ok(())`. +/// +/// This function is most useful for non-fatal errors, for example when applying an operation to +/// multiple files: +/// ```ignore +/// use uucore::error::{UResult, set_exit_code}; +/// +/// fn uumain(args: impl uucore::Args) -> UResult<()> { +/// ... +/// for file in files { +/// let res = some_operation_that_might_fail(file); +/// match res { +/// Ok() => {}, +/// Err(_) => set_exit_code(1), +/// } +/// } +/// Ok(()) // If any of the operations failed, 1 is returned. +/// } +/// ``` +pub fn set_exit_code(code: i32) { + EXIT_CODE.store(code, Ordering::SeqCst); +} + +/// Should be returned by all utils. +/// +/// Two additional methods are implemented on [`UResult`] on top of the normal [`Result`] methods: +/// `map_err_code` & `map_err_code_message`. +/// +/// These methods are used to convert [`UCommonError`]s into errors with a custom error code and +/// message. +pub type UResult = Result; + +trait UResultTrait { + fn map_err_code(self, mapper: fn(&UCommonError) -> Option) -> Self; + fn map_err_code_and_message(self, mapper: fn(&UCommonError) -> Option<(i32, String)>) -> Self; +} + +impl UResultTrait for UResult { + fn map_err_code(self, mapper: fn(&UCommonError) -> Option) -> Self { + if let Err(UError::Common(error)) = self { + if let Some(code) = mapper(&error) { + Err(UCommonErrorWithCode { code, error }.into()) + } else { + Err(error.into()) + } + } else { + self + } + } + + fn map_err_code_and_message(self, mapper: fn(&UCommonError) -> Option<(i32, String)>) -> Self { + if let Err(UError::Common(ref error)) = self { + if let Some((code, message)) = mapper(error) { + return Err(USimpleError { code, message }.into()); + } + } + self + } +} + +/// The error type of [`UResult`]. +/// +/// `UError::Common` errors are defined in [`uucore`](crate) while `UError::Custom` errors are +/// defined by the utils. +/// ``` +/// use uucore::error::USimpleError; +/// let err = USimpleError::new(1, "Error!!".into()); +/// assert_eq!(1, err.code()); +/// assert_eq!(String::from("Error!!"), format!("{}", err)); +/// ``` +pub enum UError { + Common(UCommonError), + Custom(Box), +} + +impl UError { + pub fn code(&self) -> i32 { + match self { + UError::Common(e) => e.code(), + UError::Custom(e) => e.code(), + } + } +} + +impl From for UError { + fn from(v: UCommonError) -> Self { + UError::Common(v) + } +} + +impl From for UError { + fn from(v: i32) -> Self { + UError::Custom(Box::new(ExitCode(v))) + } +} + +impl From for UError { + fn from(v: E) -> Self { + UError::Custom(Box::new(v) as Box) + } +} + +impl Display for UError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + UError::Common(e) => e.fmt(f), + UError::Custom(e) => e.fmt(f), + } + } +} + +/// Custom errors defined by the utils. +/// +/// All errors should implement [`std::error::Error`], [`std::fmt::Display`] and +/// [`std::fmt::Debug`] and have an additional `code` method that specifies the exit code of the +/// program if the error is returned from `uumain`. +/// +/// An example of a custom error from `ls`: +/// ``` +/// use uucore::error::{UCustomError}; +/// use std::{ +/// error::Error, +/// fmt::{Display, Debug}, +/// path::PathBuf +/// }; +/// +/// #[derive(Debug)] +/// enum LsError { +/// InvalidLineWidth(String), +/// NoMetadata(PathBuf), +/// } +/// +/// impl UCustomError for LsError { +/// fn code(&self) -> i32 { +/// match self { +/// LsError::InvalidLineWidth(_) => 2, +/// LsError::NoMetadata(_) => 1, +/// } +/// } +/// } +/// +/// impl Error for LsError {} +/// +/// impl Display for LsError { +/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +/// match self { +/// LsError::InvalidLineWidth(s) => write!(f, "invalid line width: '{}'", s), +/// LsError::NoMetadata(p) => write!(f, "could not open file: '{}'", p.display()), +/// } +/// } +/// } +/// ``` +/// A crate like [`quick_error`](https://crates.io/crates/quick-error) might also be used, but will +/// still require an `impl` for the `code` method. +pub trait UCustomError: Error { + fn code(&self) -> i32 { + 1 + } +} + +impl From> for i32 { + fn from(e: Box) -> i32 { + e.code() + } +} + +/// A [`UCommonError`] with an overridden exit code. +/// +/// This exit code is returned instead of the default exit code for the [`UCommonError`]. This is +/// typically created with the either the `UResult::map_err_code` or `UCommonError::with_code` +/// method. +#[derive(Debug)] +pub struct UCommonErrorWithCode { + code: i32, + error: UCommonError, +} + +impl Error for UCommonErrorWithCode {} + +impl Display for UCommonErrorWithCode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + self.error.fmt(f) + } +} + +impl UCustomError for UCommonErrorWithCode { + fn code(&self) -> i32 { + self.code + } +} + +/// A simple error type with an exit code and a message that implements [`UCustomError`]. +/// +/// It is typically created with the `UResult::map_err_code_and_message` method. Alternatively, it +/// can be constructed by manually: +/// ``` +/// use uucore::error::{UResult, USimpleError}; +/// let err = USimpleError { code: 1, message: "error!".into()}; +/// let res: UResult<()> = Err(err.into()); +/// // or using the `new` method: +/// let res: UResult<()> = Err(USimpleError::new(1, "error!".into())); +/// ``` +#[derive(Debug)] +pub struct USimpleError { + pub code: i32, + pub message: String, +} + +impl USimpleError { + #[allow(clippy::new_ret_no_self)] + pub fn new(code: i32, message: String) -> UError { + UError::Custom(Box::new(Self { code, message })) + } +} + +impl Error for USimpleError {} + +impl Display for USimpleError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + self.message.fmt(f) + } +} + +impl UCustomError for USimpleError { + fn code(&self) -> i32 { + self.code + } +} + +/// Wrapper type around [`std::io::Error`]. +/// +/// The messages displayed by [`UIoError`] should match the error messages displayed by GNU +/// coreutils. +/// +/// There are two ways to construct this type: with [`UIoError::new`] or by calling the +/// [`FromIo::map_err_context`] method on a [`std::io::Result`] or [`std::io::Error`]. +/// ``` +/// use uucore::error::{FromIo, UResult, UIoError, UCommonError}; +/// use std::fs::File; +/// use std::path::Path; +/// let path = Path::new("test.txt"); +/// +/// // Manual construction +/// let e: UIoError = UIoError::new( +/// std::io::ErrorKind::NotFound, +/// format!("cannot access '{}'", path.display()) +/// ); +/// let res: UResult<()> = Err(e.into()); +/// +/// // Converting from an `std::io::Error`. +/// let res: UResult = File::open(path).map_err_context(|| format!("cannot access '{}'", path.display())); +/// ``` +#[derive(Debug)] +pub struct UIoError { + context: String, + inner: std::io::Error, +} + +impl UIoError { + pub fn new(kind: std::io::ErrorKind, context: String) -> Self { + Self { + context, + inner: std::io::Error::new(kind, ""), + } + } + + pub fn code(&self) -> i32 { + 1 + } +} + +impl Error for UIoError {} + +impl Display for UIoError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + use std::io::ErrorKind::*; + write!( + f, + "{}: {}", + self.context, + match self.inner.kind() { + NotFound => "No such file or directory", + PermissionDenied => "Permission denied", + ConnectionRefused => "Connection refused", + ConnectionReset => "Connection reset", + ConnectionAborted => "Connection aborted", + NotConnected => "Not connected", + AddrInUse => "Address in use", + AddrNotAvailable => "Address not available", + BrokenPipe => "Broken pipe", + AlreadyExists => "Already exists", + WouldBlock => "Would block", + InvalidInput => "Invalid input", + InvalidData => "Invalid data", + TimedOut => "Timed out", + WriteZero => "Write zero", + Interrupted => "Interrupted", + Other => "Other", + UnexpectedEof => "Unexpected end of file", + _ => panic!("Unexpected io error: {}", self.inner), + }, + ) + } +} + +/// Enables the conversion from `std::io::Error` to `UError` and from `std::io::Result` to +/// `UResult`. +pub trait FromIo { + fn map_err_context(self, context: impl FnOnce() -> String) -> T; +} + +impl FromIo for std::io::Error { + fn map_err_context(self, context: impl FnOnce() -> String) -> UIoError { + UIoError { + context: (context)(), + inner: self, + } + } +} + +impl FromIo> for std::io::Result { + fn map_err_context(self, context: impl FnOnce() -> String) -> UResult { + self.map_err(|e| UError::Common(UCommonError::Io(e.map_err_context(context)))) + } +} + +impl FromIo for std::io::ErrorKind { + fn map_err_context(self, context: impl FnOnce() -> String) -> UIoError { + UIoError { + context: (context)(), + inner: std::io::Error::new(self, ""), + } + } +} + +impl From for UCommonError { + fn from(e: UIoError) -> UCommonError { + UCommonError::Io(e) + } +} + +impl From for UError { + fn from(e: UIoError) -> UError { + let common: UCommonError = e.into(); + common.into() + } +} + +/// Common errors for utilities. +/// +/// If identical errors appear across multiple utilities, they should be added here. +#[derive(Debug)] +pub enum UCommonError { + Io(UIoError), + // Clap(UClapError), +} + +impl UCommonError { + pub fn with_code(self, code: i32) -> UCommonErrorWithCode { + UCommonErrorWithCode { code, error: self } + } + + pub fn code(&self) -> i32 { + 1 + } +} + +impl From for i32 { + fn from(common: UCommonError) -> i32 { + match common { + UCommonError::Io(e) => e.code(), + } + } +} + +impl Error for UCommonError {} + +impl Display for UCommonError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + UCommonError::Io(e) => e.fmt(f), + } + } +} + +/// A special error type that does not print any message when returned from +/// `uumain`. Especially useful for porting utilities to using [`UResult`]. +/// +/// There are two ways to construct an [`ExitCode`]: +/// ``` +/// use uucore::error::{ExitCode, UResult}; +/// // Explicit +/// let res: UResult<()> = Err(ExitCode(1).into()); +/// +/// // Using into on `i32`: +/// let res: UResult<()> = Err(1.into()); +/// ``` +/// This type is especially useful for a trivial conversion from utils returning [`i32`] to +/// returning [`UResult`]. +#[derive(Debug)] +pub struct ExitCode(pub i32); + +impl Error for ExitCode {} + +impl Display for ExitCode { + fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + Ok(()) + } +} + +impl UCustomError for ExitCode { + fn code(&self) -> i32 { + self.0 + } +} From 43bfec7170d8df099f2ef8f300d77e0883319dd8 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 28 Jun 2021 13:39:34 +0200 Subject: [PATCH 312/320] uucore/error: add macros for standardized error handling --- src/uucore/src/lib/macros.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 07d47eed8..e4d83e746 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -21,6 +21,24 @@ macro_rules! executable( }) ); +#[macro_export] +macro_rules! show( + ($err:expr) => ({ + let e = $err; + uucore::error::set_exit_code(e.code()); + eprintln!("{}: {}", executable!(), e); + }) +); + +#[macro_export] +macro_rules! show_if_err( + ($res:expr) => ({ + if let Err(e) = $res { + show!(e); + } + }) +); + /// Show an error to stderr in a similar style to GNU coreutils. #[macro_export] macro_rules! show_error( From 60e4621c3be69d4f07bdeec466f1aaf6ec08d779 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 28 Jun 2021 13:44:21 +0200 Subject: [PATCH 313/320] uucore_procs: add temporary proc macro gen_uumain for standardized error handling --- src/uucore_procs/src/lib.rs | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/uucore_procs/src/lib.rs b/src/uucore_procs/src/lib.rs index e0d247c3f..93567a12d 100644 --- a/src/uucore_procs/src/lib.rs +++ b/src/uucore_procs/src/lib.rs @@ -1,6 +1,10 @@ // Copyright (C) ~ Roy Ivy III ; MIT license extern crate proc_macro; +use proc_macro::TokenStream; +use proc_macro2::{Ident, Span}; +use quote::quote; +use syn::{self, parse_macro_input, ItemFn}; //## rust proc-macro background info //* ref: @@ @@ -41,7 +45,7 @@ impl syn::parse::Parse for Tokens { } #[proc_macro] -pub fn main(stream: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn main(stream: TokenStream) -> TokenStream { let Tokens { expr } = syn::parse_macro_input!(stream as Tokens); proc_dbg!(&expr); @@ -78,5 +82,32 @@ pub fn main(stream: proc_macro::TokenStream) -> proc_macro::TokenStream { std::process::exit(code); } }; - proc_macro::TokenStream::from(result) + TokenStream::from(result) +} + +#[proc_macro_attribute] +pub fn gen_uumain(_args: TokenStream, stream: TokenStream) -> TokenStream { + let mut ast = parse_macro_input!(stream as ItemFn); + + // Change the name of the function to "uumain_result" to prevent name-conflicts + ast.sig.ident = Ident::new("uumain_result", Span::call_site()); + + let new = quote!( + pub fn uumain(args: impl uucore::Args) -> i32 { + #ast + let result = uumain_result(args); + match result { + Ok(()) => uucore::error::get_exit_code(), + Err(e) => { + let s = format!("{}", e); + if s != "" { + show_error!("{}", s); + } + e.code() + } + } + } + ); + + TokenStream::from(new) } From e4eac825fb4aed0b75903babf544438aed978cee Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 28 Jun 2021 13:44:39 +0200 Subject: [PATCH 314/320] ls: adapt to standardized error handling --- src/uu/ls/src/ls.rs | 81 ++++++++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 6ca3f4bbe..059981e28 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -26,10 +26,11 @@ use quoting_style::{escape_name, QuotingStyle}; use std::os::windows::fs::MetadataExt; use std::{ cmp::Reverse, + error::Error, + fmt::Display, fs::{self, DirEntry, FileType, Metadata}, io::{stdout, BufWriter, Stdout, Write}, path::{Path, PathBuf}, - process::exit, time::{SystemTime, UNIX_EPOCH}, }; #[cfg(unix)] @@ -38,8 +39,8 @@ use std::{ os::unix::fs::{FileTypeExt, MetadataExt}, time::Duration, }; - use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; +use uucore::error::{set_exit_code, FromIo, UCustomError, UResult}; use unicode_width::UnicodeWidthStr; #[cfg(unix)] @@ -125,6 +126,32 @@ pub mod options { pub static IGNORE: &str = "ignore"; } +#[derive(Debug)] +enum LsError { + InvalidLineWidth(String), + NoMetadata(PathBuf), +} + +impl UCustomError for LsError { + fn code(&self) -> i32 { + match self { + LsError::InvalidLineWidth(_) => 2, + LsError::NoMetadata(_) => 1, + } + } +} + +impl Error for LsError {} + +impl Display for LsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LsError::InvalidLineWidth(s) => write!(f, "invalid line width: '{}'", s), + LsError::NoMetadata(p) => write!(f, "could not open file: '{}'", p.display()), + } + } +} + #[derive(PartialEq, Eq)] enum Format { Columns, @@ -218,7 +245,7 @@ struct LongFormat { impl Config { #[allow(clippy::cognitive_complexity)] - fn from(options: clap::ArgMatches) -> Config { + fn from(options: clap::ArgMatches) -> UResult { let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) { ( match format_ { @@ -369,15 +396,13 @@ impl Config { } }; - let width = options - .value_of(options::WIDTH) - .map(|x| { - x.parse::().unwrap_or_else(|_e| { - show_error!("invalid line width: '{}'", x); - exit(2); - }) - }) - .or_else(|| termsize::get().map(|s| s.cols)); + let width = match options.value_of(options::WIDTH) { + Some(x) => match x.parse::() { + Ok(u) => Some(u), + Err(_) => return Err(LsError::InvalidLineWidth(x.into()).into()), + }, + None => termsize::get().map(|s| s.cols), + }; #[allow(clippy::needless_bool)] let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) { @@ -528,7 +553,7 @@ impl Config { Dereference::DirArgs }; - Config { + Ok(Config { format, files, sort, @@ -547,11 +572,12 @@ impl Config { quoting_style, indicator_style, time_style, - } + }) } } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -567,7 +593,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_else(|| vec![String::from(".")]); - list(locs, Config::from(matches)) + list(locs, Config::from(matches)?) } pub fn uu_app() -> App<'static, 'static> { @@ -1190,10 +1216,9 @@ impl PathData { } } -fn list(locs: Vec, config: Config) -> i32 { +fn list(locs: Vec, config: Config) -> UResult<()> { let mut files = Vec::::new(); let mut dirs = Vec::::new(); - let mut has_failed = false; let mut out = BufWriter::new(stdout()); @@ -1202,19 +1227,16 @@ fn list(locs: Vec, config: Config) -> i32 { let path_data = PathData::new(p, None, None, &config, true); if path_data.md().is_none() { - show_error!("'{}': {}", &loc, "No such file or directory"); - /* - We found an error, the return code of ls should not be 0 - And no need to continue the execution - */ - has_failed = true; + show!(std::io::ErrorKind::NotFound + .map_err_context(|| format!("cannot access '{}'", path_data.p_buf.display()))); + // We found an error, no need to continue the execution continue; } let show_dir_contents = match path_data.file_type() { Some(ft) => !config.directory && ft.is_dir(), None => { - has_failed = true; + set_exit_code(1); false } }; @@ -1235,11 +1257,8 @@ fn list(locs: Vec, config: Config) -> i32 { } enter_directory(&dir, &config, &mut out); } - if has_failed { - 1 - } else { - 0 - } + + Ok(()) } fn sort_entries(entries: &mut Vec, config: &Config) { @@ -1478,7 +1497,7 @@ fn display_item_long( ) { let md = match item.md() { None => { - show_error!("could not show file: {}", &item.p_buf.display()); + show!(LsError::NoMetadata(item.p_buf.clone())); return; } Some(md) => md, From 8c5052fcb79c1fc7ff9bacaafc9266df6ea58233 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 28 Jun 2021 13:45:04 +0200 Subject: [PATCH 315/320] mkdir: adapt to standardized error handling --- src/uu/mkdir/src/mkdir.rs | 126 +++++++++++++++----------------------- 1 file changed, 48 insertions(+), 78 deletions(-) diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 82d561213..a99867570 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -8,25 +8,26 @@ #[macro_use] extern crate uucore; +use clap::OsValues; use clap::{crate_version, App, Arg}; use std::fs; use std::path::Path; +use uucore::error::{FromIo, UResult, USimpleError}; static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist"; -static OPT_MODE: &str = "mode"; -static OPT_PARENTS: &str = "parents"; -static OPT_VERBOSE: &str = "verbose"; - -static ARG_DIRS: &str = "dirs"; +mod options { + pub const MODE: &str = "mode"; + pub const PARENTS: &str = "parents"; + pub const VERBOSE: &str = "verbose"; + pub const DIRS: &str = "dirs"; +} fn get_usage() -> String { format!("{0} [OPTION]... [USER]", executable!()) } -/** - * Handles option parsing - */ -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = get_usage(); // Linux-specific options, not implemented @@ -34,26 +35,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // " of each created directory to CTX"), let matches = uu_app().usage(&usage[..]).get_matches_from(args); - let dirs: Vec = matches - .values_of(ARG_DIRS) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - let verbose = matches.is_present(OPT_VERBOSE); - let recursive = matches.is_present(OPT_PARENTS); + let dirs = matches.values_of_os(options::DIRS).unwrap_or_default(); + let verbose = matches.is_present(options::VERBOSE); + let recursive = matches.is_present(options::PARENTS); // Translate a ~str in octal form to u16, default to 755 // Not tested on Windows - let mode_match = matches.value_of(OPT_MODE); - let mode: u16 = match mode_match { - Some(m) => { - let res: Option = u16::from_str_radix(m, 8).ok(); - match res { - Some(r) => r, - _ => crash!(1, "no mode given"), - } - } - _ => 0o755_u16, + let mode: u16 = match matches.value_of(options::MODE) { + Some(m) => u16::from_str_radix(m, 8) + .map_err(|_| USimpleError::new(1, format!("invalid mode '{}'", m)))?, + None => 0o755_u16, }; exec(dirs, recursive, mode, verbose) @@ -64,27 +55,27 @@ pub fn uu_app() -> App<'static, 'static> { .version(crate_version!()) .about(ABOUT) .arg( - Arg::with_name(OPT_MODE) + Arg::with_name(options::MODE) .short("m") - .long(OPT_MODE) + .long(options::MODE) .help("set file mode (not implemented on windows)") .default_value("755"), ) .arg( - Arg::with_name(OPT_PARENTS) + Arg::with_name(options::PARENTS) .short("p") - .long(OPT_PARENTS) + .long(options::PARENTS) .alias("parent") .help("make parent directories as needed"), ) .arg( - Arg::with_name(OPT_VERBOSE) + Arg::with_name(options::VERBOSE) .short("v") - .long(OPT_VERBOSE) + .long(options::VERBOSE) .help("print a message for each printed directory"), ) .arg( - Arg::with_name(ARG_DIRS) + Arg::with_name(options::DIRS) .multiple(true) .takes_value(true) .min_values(1), @@ -94,64 +85,43 @@ pub fn uu_app() -> App<'static, 'static> { /** * Create the list of new directories */ -fn exec(dirs: Vec, recursive: bool, mode: u16, verbose: bool) -> i32 { - let mut status = 0; - let empty = Path::new(""); - for dir in &dirs { +fn exec(dirs: OsValues, recursive: bool, mode: u16, verbose: bool) -> UResult<()> { + for dir in dirs { let path = Path::new(dir); - if !recursive { - if let Some(parent) = path.parent() { - if parent != empty && !parent.exists() { - show_error!( - "cannot create directory '{}': No such file or directory", - path.display() - ); - status = 1; - continue; - } - } - } - status |= mkdir(path, recursive, mode, verbose); + show_if_err!(mkdir(path, recursive, mode, verbose)); } - status + Ok(()) } -/** - * Wrapper to catch errors, return 1 if failed - */ -fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> i32 { +fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> UResult<()> { let create_dir = if recursive { fs::create_dir_all } else { fs::create_dir }; - if let Err(e) = create_dir(path) { - show_error!("{}: {}", path.display(), e.to_string()); - return 1; - } + + create_dir(path).map_err_context(|| format!("cannot create directory '{}'", path.display()))?; if verbose { println!("{}: created directory '{}'", executable!(), path.display()); } - #[cfg(any(unix, target_os = "redox"))] - fn chmod(path: &Path, mode: u16) -> i32 { - use std::fs::{set_permissions, Permissions}; - use std::os::unix::fs::PermissionsExt; - - let mode = Permissions::from_mode(u32::from(mode)); - - if let Err(err) = set_permissions(path, mode) { - show_error!("{}: {}", path.display(), err); - return 1; - } - 0 - } - #[cfg(windows)] - #[allow(unused_variables)] - fn chmod(path: &Path, mode: u16) -> i32 { - // chmod on Windows only sets the readonly flag, which isn't even honored on directories - 0 - } chmod(path, mode) } + +#[cfg(any(unix, target_os = "redox"))] +fn chmod(path: &Path, mode: u16) -> UResult<()> { + use std::fs::{set_permissions, Permissions}; + use std::os::unix::fs::PermissionsExt; + + let mode = Permissions::from_mode(u32::from(mode)); + + set_permissions(path, mode) + .map_err_context(|| format!("cannot set permissions '{}'", path.display())) +} + +#[cfg(windows)] +fn chmod(_path: &Path, _mode: u16) -> UResult<()> { + // chmod on Windows only sets the readonly flag, which isn't even honored on directories + Ok(()) +} From 73a7ead8570bb43a9ba34be2bb70c2868fc5c993 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 28 Jun 2021 13:45:15 +0200 Subject: [PATCH 316/320] mktemp: adapt to standardized error handling --- src/uu/mktemp/src/mktemp.rs | 185 +++++++++++++++++++---------------- tests/by-util/test_mktemp.rs | 3 +- 2 files changed, 105 insertions(+), 83 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index bbccf6628..8a4b472aa 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -12,8 +12,11 @@ extern crate uucore; use clap::{crate_version, App, Arg}; +use uucore::error::{FromIo, UCustomError, UResult}; use std::env; +use std::error::Error; +use std::fmt::Display; use std::iter; use std::path::{is_separator, PathBuf}; @@ -37,7 +40,40 @@ fn get_usage() -> String { format!("{0} [OPTION]... [TEMPLATE]", executable!()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[derive(Debug)] +enum MkTempError { + PersistError(PathBuf), + MustEndInX(String), + TooFewXs(String), + ContainsDirSeparator(String), + InvalidTemplate(String), +} + +impl UCustomError for MkTempError {} + +impl Error for MkTempError {} + +impl Display for MkTempError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use MkTempError::*; + match self { + PersistError(p) => write!(f, "could not persist file '{}'", p.display()), + MustEndInX(s) => write!(f, "with --suffix, template '{}' must end in X", s), + TooFewXs(s) => write!(f, "too few X's in template '{}'", s), + ContainsDirSeparator(s) => { + write!(f, "invalid suffix '{}', contains directory separator", s) + } + InvalidTemplate(s) => write!( + f, + "invalid template, '{}'; with --tmpdir, it may not be absolute", + s + ), + } + } +} + +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = get_usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -73,47 +109,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let dry_run = matches.is_present(OPT_DRY_RUN); let suppress_file_err = matches.is_present(OPT_QUIET); - let (prefix, rand, suffix) = match parse_template(template) { - Some((p, r, s)) => match matches.value_of(OPT_SUFFIX) { - Some(suf) => { - if s.is_empty() { - (p, r, suf) - } else { - crash!( - 1, - "Template should end with 'X' when you specify suffix option." - ) - } - } - None => (p, r, s), - }, - None => ("", 0, ""), - }; - - if rand < 3 { - crash!(1, "Too few 'X's in template") - } - - if suffix.chars().any(is_separator) { - crash!(1, "suffix cannot contain any path separators"); - } + let (prefix, rand, suffix) = parse_template(template, matches.value_of(OPT_SUFFIX))?; if matches.is_present(OPT_TMPDIR) && PathBuf::from(prefix).is_absolute() { - show_error!( - "invalid template, '{}'; with --tmpdir, it may not be absolute", - template - ); - return 1; - }; + return Err(MkTempError::InvalidTemplate(template.into()).into()); + } if matches.is_present(OPT_T) { tmpdir = env::temp_dir() - }; + } - if dry_run { + let res = if dry_run { dry_exec(tmpdir, prefix, rand, suffix) } else { - exec(tmpdir, prefix, rand, suffix, make_dir, suppress_file_err) + exec(tmpdir, prefix, rand, suffix, make_dir) + }; + + if suppress_file_err { + // Mapping all UErrors to ExitCodes prevents the errors from being printed + res.map_err(|e| e.code().into()) + } else { + res } } @@ -173,19 +189,40 @@ pub fn uu_app() -> App<'static, 'static> { ) } -fn parse_template(temp: &str) -> Option<(&str, usize, &str)> { +fn parse_template<'a>( + temp: &'a str, + suffix: Option<&'a str>, +) -> UResult<(&'a str, usize, &'a str)> { let right = match temp.rfind('X') { Some(r) => r + 1, - None => return None, + None => return Err(MkTempError::TooFewXs(temp.into()).into()), }; let left = temp[..right].rfind(|c| c != 'X').map_or(0, |i| i + 1); let prefix = &temp[..left]; let rand = right - left; - let suffix = &temp[right..]; - Some((prefix, rand, suffix)) + + if rand < 3 { + return Err(MkTempError::TooFewXs(temp.into()).into()); + } + + let mut suf = &temp[right..]; + + if let Some(s) = suffix { + if suf.is_empty() { + suf = s; + } else { + return Err(MkTempError::MustEndInX(temp.into()).into()); + } + }; + + if suf.chars().any(is_separator) { + return Err(MkTempError::ContainsDirSeparator(suf.into()).into()); + } + + Ok((prefix, rand, suf)) } -pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> i32 { +pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> UResult<()> { let len = prefix.len() + suffix.len() + rand; let mut buf = String::with_capacity(len); buf.push_str(prefix); @@ -208,51 +245,35 @@ pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> } tmpdir.push(buf); println!("{}", tmpdir.display()); - 0 + Ok(()) } -fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool, quiet: bool) -> i32 { - let res = if make_dir { - let tmpdir = Builder::new() - .prefix(prefix) - .rand_bytes(rand) - .suffix(suffix) - .tempdir_in(&dir); - - // `into_path` consumes the TempDir without removing it - tmpdir.map(|d| d.into_path().to_string_lossy().to_string()) - } else { - let tmpfile = Builder::new() - .prefix(prefix) - .rand_bytes(rand) - .suffix(suffix) - .tempfile_in(&dir); - - match tmpfile { - Ok(f) => { - // `keep` ensures that the file is not deleted - match f.keep() { - Ok((_, p)) => Ok(p.to_string_lossy().to_string()), - Err(e) => { - show_error!("'{}': {}", dir.display(), e); - return 1; - } - } - } - Err(x) => Err(x), - } +fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> { + let context = || { + format!( + "failed to create file via template '{}{}{}'", + prefix, + "X".repeat(rand), + suffix + ) }; - match res { - Ok(ref f) => { - println!("{}", f); - 0 - } - Err(e) => { - if !quiet { - show_error!("{}: {}", e, dir.display()); - } - 1 - } - } + let mut builder = Builder::new(); + builder.prefix(prefix).rand_bytes(rand).suffix(suffix); + + let path = if make_dir { + builder + .tempdir_in(&dir) + .map_err_context(context)? + .into_path() // `into_path` consumes the TempDir without removing it + } else { + builder + .tempfile_in(&dir) + .map_err_context(context)? + .keep() // `keep` ensures that the file is not deleted + .map_err(|e| MkTempError::PersistError(e.file.path().to_path_buf()))? + .1 + }; + println!("{}", path.display()); + Ok(()) } diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index bcf75ee20..e824df061 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -125,7 +125,8 @@ fn test_mktemp_mktemp_t() { .arg(TEST_TEMPLATE8) .fails() .no_stdout() - .stderr_contains("suffix cannot contain any path separators"); + .stderr_contains("invalid suffix") + .stderr_contains("contains directory separator"); } #[test] From 0cfaaeceda67947f98c938b69c6c7d2f2064cc83 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 28 Jun 2021 13:45:50 +0200 Subject: [PATCH 317/320] touch: adapt to standardized error handling --- src/uu/touch/src/touch.rs | 165 ++++++++++++++------------------------ 1 file changed, 62 insertions(+), 103 deletions(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 3e9ff5624..dd2b05d0e 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -16,9 +16,8 @@ extern crate uucore; use clap::{crate_version, App, Arg, ArgGroup}; use filetime::*; use std::fs::{self, File}; -use std::io::Error; use std::path::Path; -use std::process; +use uucore::error::{FromIo, UResult, USimpleError}; static ABOUT: &str = "Update the access and modification times of each FILE to the current time."; pub mod options { @@ -52,57 +51,38 @@ fn get_usage() -> String { format!("{0} [OPTION]... [USER]", executable!()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = get_usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); - let files: Vec = matches - .values_of(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); + let files = matches.values_of_os(ARG_FILES).unwrap(); - let (mut atime, mut mtime) = if matches.is_present(options::sources::REFERENCE) { - stat( - matches.value_of(options::sources::REFERENCE).unwrap(), - !matches.is_present(options::NO_DEREF), - ) - } else if matches.is_present(options::sources::DATE) - || matches.is_present(options::sources::CURRENT) - { - let timestamp = if matches.is_present(options::sources::DATE) { - parse_date(matches.value_of(options::sources::DATE).unwrap()) + let (mut atime, mut mtime) = + if let Some(reference) = matches.value_of_os(options::sources::REFERENCE) { + stat(Path::new(reference), !matches.is_present(options::NO_DEREF))? } else { - parse_timestamp(matches.value_of(options::sources::CURRENT).unwrap()) + let timestamp = if let Some(date) = matches.value_of(options::sources::DATE) { + parse_date(date)? + } else if let Some(current) = matches.value_of(options::sources::CURRENT) { + parse_timestamp(current)? + } else { + local_tm_to_filetime(time::now()) + }; + (timestamp, timestamp) }; - (timestamp, timestamp) - } else { - let now = local_tm_to_filetime(time::now()); - (now, now) - }; - let mut error_code = 0; - - for filename in &files { - let path = &filename[..]; - - if !Path::new(path).exists() { + for filename in files { + let path = Path::new(filename); + if !path.exists() { // no-dereference included here for compatibility if matches.is_present(options::NO_CREATE) || matches.is_present(options::NO_DEREF) { continue; } if let Err(e) = File::create(path) { - match e.kind() { - std::io::ErrorKind::NotFound => { - show_error!("cannot touch '{}': {}", path, "No such file or directory") - } - std::io::ErrorKind::PermissionDenied => { - show_error!("cannot touch '{}': {}", path, "Permission denied") - } - _ => show_error!("cannot touch '{}': {}", path, e), - } - error_code = 1; + show!(e.map_err_context(|| format!("cannot touch '{}'", path.display()))); continue; }; @@ -118,7 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { || matches.is_present(options::MODIFICATION) || matches.is_present(options::TIME) { - let st = stat(path, !matches.is_present(options::NO_DEREF)); + let st = stat(path, !matches.is_present(options::NO_DEREF))?; let time = matches.value_of(options::TIME).unwrap_or(""); if !(matches.is_present(options::ACCESS) @@ -138,29 +118,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } if matches.is_present(options::NO_DEREF) { - if let Err(e) = set_symlink_file_times(path, atime, mtime) { - // we found an error, it should fail in any case - error_code = 1; - if e.kind() == std::io::ErrorKind::PermissionDenied { - // GNU compatibility (not-owner.sh) - show_error!("setting times of '{}': {}", path, "Permission denied"); - } else { - show_error!("setting times of '{}': {}", path, e); - } - } - } else if let Err(e) = filetime::set_file_times(path, atime, mtime) { - // we found an error, it should fail in any case - error_code = 1; - - if e.kind() == std::io::ErrorKind::PermissionDenied { - // GNU compatibility (not-owner.sh) - show_error!("setting times of '{}': {}", path, "Permission denied"); - } else { - show_error!("setting times of '{}': {}", path, e); - } + set_symlink_file_times(path, atime, mtime) + } else { + filetime::set_file_times(path, atime, mtime) } + .map_err_context(|| format!("setting times of '{}'", path.display()))?; } - error_code + + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -238,28 +203,21 @@ pub fn uu_app() -> App<'static, 'static> { ])) } -fn stat(path: &str, follow: bool) -> (FileTime, FileTime) { +fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> { let metadata = if follow { fs::symlink_metadata(path) } else { fs::metadata(path) - }; - - match metadata { - Ok(m) => ( - FileTime::from_last_access_time(&m), - FileTime::from_last_modification_time(&m), - ), - Err(_) => crash!( - 1, - "failed to get attributes of '{}': {}", - path, - Error::last_os_error() - ), } + .map_err_context(|| format!("failed to get attributes of '{}'", path.display()))?; + + Ok(( + FileTime::from_last_access_time(&metadata), + FileTime::from_last_modification_time(&metadata), + )) } -fn parse_date(str: &str) -> FileTime { +fn parse_date(str: &str) -> UResult { // This isn't actually compatible with GNU touch, but there doesn't seem to // be any simple specification for what format this parameter allows and I'm // not about to implement GNU parse_datetime. @@ -267,18 +225,22 @@ fn parse_date(str: &str) -> FileTime { let formats = vec!["%c", "%F"]; for f in formats { if let Ok(tm) = time::strptime(str, f) { - return local_tm_to_filetime(to_local(tm)); + return Ok(local_tm_to_filetime(to_local(tm))); } } + if let Ok(tm) = time::strptime(str, "@%s") { // Don't convert to local time in this case - seconds since epoch are not time-zone dependent - return local_tm_to_filetime(tm); + return Ok(local_tm_to_filetime(tm)); } - show_error!("Unable to parse date: {}\n", str); - process::exit(1); + + Err(USimpleError::new( + 1, + format!("Unable to parse date: {}", str), + )) } -fn parse_timestamp(s: &str) -> FileTime { +fn parse_timestamp(s: &str) -> UResult { let now = time::now(); let (format, ts) = match s.chars().count() { 15 => ("%Y%m%d%H%M.%S", s.to_owned()), @@ -287,31 +249,28 @@ fn parse_timestamp(s: &str) -> FileTime { 10 => ("%y%m%d%H%M", s.to_owned()), 11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)), 8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)), - _ => panic!("Unknown timestamp format"), + _ => return Err(USimpleError::new(1, format!("invalid date format '{}'", s))), }; - match time::strptime(&ts, format) { - Ok(tm) => { - let mut local = to_local(tm); - local.tm_isdst = -1; - let ft = local_tm_to_filetime(local); + let tm = time::strptime(&ts, format) + .map_err(|_| USimpleError::new(1, format!("invalid date format '{}'", s)))?; - // We have to check that ft is valid time. Due to daylight saving - // time switch, local time can jump from 1:59 AM to 3:00 AM, - // in which case any time between 2:00 AM and 2:59 AM is not valid. - // Convert back to local time and see if we got the same value back. - let ts = time::Timespec { - sec: ft.unix_seconds(), - nsec: 0, - }; - let tm2 = time::at(ts); - if tm.tm_hour != tm2.tm_hour { - show_error!("invalid date format {}", s); - process::exit(1); - } + let mut local = to_local(tm); + local.tm_isdst = -1; + let ft = local_tm_to_filetime(local); - ft - } - Err(e) => panic!("Unable to parse timestamp\n{}", e), + // We have to check that ft is valid time. Due to daylight saving + // time switch, local time can jump from 1:59 AM to 3:00 AM, + // in which case any time between 2:00 AM and 2:59 AM is not valid. + // Convert back to local time and see if we got the same value back. + let ts = time::Timespec { + sec: ft.unix_seconds(), + nsec: 0, + }; + let tm2 = time::at(ts); + if tm.tm_hour != tm2.tm_hour { + return Err(USimpleError::new(1, format!("invalid date format '{}'", s))); } + + Ok(ft) } From 92bfaea3faf870abc78ebbcc6d3ca980b2ed48ce Mon Sep 17 00:00:00 2001 From: Dean Li Date: Tue, 29 Jun 2021 19:48:20 +0800 Subject: [PATCH 318/320] arch: use UResult --- src/uu/arch/src/arch.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 955e57389..0f15654cc 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -12,16 +12,18 @@ extern crate uucore; use platform_info::*; use clap::{crate_version, App}; +use uucore::error::{FromIo, UResult}; static ABOUT: &str = "Display machine architecture"; static SUMMARY: &str = "Determine architecture name for current machine."; -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { uu_app().get_matches_from(args); - let uts = return_if_err!(1, PlatformInfo::new()); + let uts = PlatformInfo::new().map_err_context(|| "arch: ".to_string())?; println!("{}", uts.machine().trim()); - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { From b21e01bcb00dad3f4dfe811950dd58214098e29e Mon Sep 17 00:00:00 2001 From: Dean Li Date: Wed, 30 Jun 2021 22:29:28 +0800 Subject: [PATCH 319/320] arch: match GNU error Follow up for #2466 as suggested by @miDeb --- src/uu/arch/src/arch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 0f15654cc..94ec97e98 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -21,7 +21,7 @@ static SUMMARY: &str = "Determine architecture name for current machine."; pub fn uumain(args: impl uucore::Args) -> UResult<()> { uu_app().get_matches_from(args); - let uts = PlatformInfo::new().map_err_context(|| "arch: ".to_string())?; + let uts = PlatformInfo::new().map_err_context(|| "cannot get system name".to_string())?; println!("{}", uts.machine().trim()); Ok(()) } From e46ce2947e3a153c4653b88648b2bdded0f031d9 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 2 Jul 2021 19:31:16 +0200 Subject: [PATCH 320/320] add usage error --- src/uucore/src/lib/macros.rs | 3 ++ src/uucore/src/lib/mods/error.rs | 50 ++++++++++++++++++++++++++++++++ src/uucore_procs/src/lib.rs | 3 ++ 3 files changed, 56 insertions(+) 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/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() } }