From 6f16cafe88d5c807c731a1b1d01b83e7c000c3ff Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 28 Apr 2021 22:58:28 +0200 Subject: [PATCH 01/71] who: move from getopts to clap (#2124) --- src/uu/who/Cargo.toml | 1 + src/uu/who/src/who.rs | 221 ++++++++++++++++++++++++++------------ tests/by-util/test_who.rs | 183 ++++++++++++++++++++++++++++--- 3 files changed, 322 insertions(+), 83 deletions(-) diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index c0cd63795..ff1c5b2af 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -17,6 +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 = "3.0.0-beta.2" [[bin]] name = "who" diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index e979d2d46..47c9dfa6e 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -12,79 +12,164 @@ extern crate uucore; use uucore::libc::{ttyname, STDIN_FILENO, S_IWGRP}; use uucore::utmpx::{self, time, Utmpx}; +use clap::{App, Arg}; use std::borrow::Cow; use std::ffi::CStr; use std::os::unix::fs::MetadataExt; use std::path::PathBuf; use uucore::InvalidEncodingHandling; -static SYNTAX: &str = "[OPTION]... [ FILE | ARG1 ARG2 ]"; -static SUMMARY: &str = "Print information about users who are currently logged in."; -static LONG_HELP: &str = " - -a, --all same as -b -d --login -p -r -t -T -u - -b, --boot time of last system boot - -d, --dead print dead processes - -H, --heading print line of column headings - -l, --login print system login processes - --lookup attempt to canonicalize hostnames via DNS - -m only hostname and user associated with stdin - -p, --process print active processes spawned by init - -q, --count all login names and number of users logged on - -r, --runlevel print current runlevel (not available on BSDs) - -s, --short print only name, line, and time (default) - -t, --time print last system clock change - -T, -w, --mesg add user's message status as +, - or ? - -u, --users list users logged in - --message same as -T - --writable same as -T - --help display this help and exit - --version output version information and exit +mod options { + pub const ALL: &str = "all"; + pub const BOOT: &str = "boot"; + pub const DEAD: &str = "dead"; + pub const HEADING: &str = "heading"; + pub const LOGIN: &str = "login"; + pub const LOOKUP: &str = "lookup"; + pub const ONLY_HOSTNAME_USER: &str = "only_hostname_user"; + pub const PROCESS: &str = "process"; + pub const COUNT: &str = "count"; + #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] + pub const RUNLEVEL: &str = "runlevel"; + pub const SHORT: &str = "short"; + pub const TIME: &str = "time"; + pub const USERS: &str = "users"; + pub const MESG: &str = "mesg"; // aliases: --message, --writable + pub const FILE: &str = "FILE"; // if length=1: FILE, if length=2: ARG1 ARG2 +} -If FILE is not specified, use /var/run/utmp. /var/log/wtmp as FILE is common. -If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual. -"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Print information about users who are currently logged in."; + +fn get_usage() -> String { + format!("{0} [OPTION]... [ FILE | ARG1 ARG2 ]", executable!()) +} + +fn get_long_usage() -> String { + String::from( + "If FILE is not specified, use /var/run/utmp. /var/log/wtmp as FILE is common.\n\ +If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual.", + ) +} pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let mut opts = app!(SYNTAX, SUMMARY, LONG_HELP); - opts.optflag("a", "all", "same as -b -d --login -p -r -t -T -u"); - opts.optflag("b", "boot", "time of last system boot"); - opts.optflag("d", "dead", "print dead processes"); - opts.optflag("H", "heading", "print line of column headings"); - opts.optflag("l", "login", "print system login processes"); - opts.optflag("", "lookup", "attempt to canonicalize hostnames via DNS"); - opts.optflag("m", "", "only hostname and user associated with stdin"); - opts.optflag("p", "process", "print active processes spawned by init"); - opts.optflag( - "q", - "count", - "all login names and number of users logged on", - ); - #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] - opts.optflag("r", "runlevel", "print current runlevel"); - opts.optflag("s", "short", "print only name, line, and time (default)"); - opts.optflag("t", "time", "print last system clock change"); - opts.optflag("u", "users", "list users logged in"); - opts.optflag("w", "mesg", "add user's message status as +, - or ?"); - // --message, --writable are the same as --mesg - opts.optflag("T", "message", ""); - opts.optflag("T", "writable", ""); + let usage = get_usage(); + let after_help = get_long_usage(); - opts.optflag("", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .override_usage(&usage[..]) + .after_help(&after_help[..]) + .arg( + Arg::new(options::ALL) + .long(options::ALL) + .short('a') + .about("same as -b -d --login -p -r -t -T -u"), + ) + .arg( + Arg::new(options::BOOT) + .long(options::BOOT) + .short('b') + .about("time of last system boot"), + ) + .arg( + Arg::new(options::DEAD) + .long(options::DEAD) + .short('d') + .about("print dead processes"), + ) + .arg( + Arg::new(options::HEADING) + .long(options::HEADING) + .short('H') + .about("print line of column headings"), + ) + .arg( + Arg::new(options::LOGIN) + .long(options::LOGIN) + .short('l') + .about("print system login processes"), + ) + .arg( + Arg::new(options::LOOKUP) + .long(options::LOOKUP) + .about("attempt to canonicalize hostnames via DNS"), + ) + .arg( + Arg::new(options::ONLY_HOSTNAME_USER) + .short('m') + .about("only hostname and user associated with stdin"), + ) + .arg( + Arg::new(options::PROCESS) + .long(options::PROCESS) + .short('p') + .about("print active processes spawned by init"), + ) + .arg( + Arg::new(options::COUNT) + .long(options::COUNT) + .short('q') + .about("all login names and number of users logged on"), + ) + .arg( + #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] + Arg::new(options::RUNLEVEL) + .long(options::RUNLEVEL) + .short('r') + .about("print current runlevel"), + ) + .arg( + Arg::new(options::SHORT) + .long(options::SHORT) + .short('s') + .about("print only name, line, and time (default)"), + ) + .arg( + Arg::new(options::TIME) + .long(options::TIME) + .short('t') + .about("print last system clock change"), + ) + .arg( + Arg::new(options::USERS) + .long(options::USERS) + .short('u') + .about("list users logged in"), + ) + .arg( + Arg::new(options::MESG) + .long(options::MESG) + .short('T') + .visible_short_alias('w') + .visible_aliases(&["message", "writable"]) + .about("add user's message status as +, - or ?"), + ) + .arg( + Arg::new(options::FILE) + .takes_value(true) + .min_values(1) + .max_values(2), + ) + .get_matches_from(args); - let matches = opts.parse(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.opt_present("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.opt_present("q"); + let short_list = matches.is_present(options::COUNT); // If true, display only name, line, and time fields. let mut short_output = false; @@ -95,12 +180,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mut include_idle = false; // If true, display a line at the top describing each field. - let include_heading = matches.opt_present("H"); + 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.opt_present("a") || matches.opt_present("T") || matches.opt_present("w"); + let include_mesg = matches.is_present(options::ALL) || matches.is_present(options::MESG); // If true, display process termination & exit status. let mut include_exit = false; @@ -133,7 +217,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { #[allow(clippy::useless_let_if_seq)] { - if matches.opt_present("a") { + if matches.is_present(options::ALL) { need_boottime = true; need_deadprocs = true; need_login = true; @@ -146,49 +230,49 @@ pub fn uumain(args: impl uucore::Args) -> i32 { assumptions = false; } - if matches.opt_present("b") { + if matches.is_present(options::BOOT) { need_boottime = true; assumptions = false; } - if matches.opt_present("d") { + if matches.is_present(options::DEAD) { need_deadprocs = true; include_idle = true; include_exit = true; assumptions = false; } - if matches.opt_present("l") { + if matches.is_present(options::LOGIN) { need_login = true; include_idle = true; assumptions = false; } - if matches.opt_present("m") || matches.free.len() == 2 { + if matches.is_present(options::ONLY_HOSTNAME_USER) || files.len() == 2 { my_line_only = true; } - if matches.opt_present("p") { + if matches.is_present(options::PROCESS) { need_initspawn = true; assumptions = false; } - if matches.opt_present("r") { + if matches.is_present(options::RUNLEVEL) { need_runlevel = true; include_idle = true; assumptions = false; } - if matches.opt_present("s") { + if matches.is_present(options::SHORT) { short_output = true; } - if matches.opt_present("t") { + if matches.is_present(options::TIME) { need_clockchange = true; assumptions = false; } - if matches.opt_present("u") { + if matches.is_present(options::USERS) { need_users = true; include_idle = true; assumptions = false; @@ -202,11 +286,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if include_exit { short_output = false; } - - if matches.free.len() > 2 { - show_usage_error!("{}", msg_wrong_number_of_arguments!()); - exit!(1); - } } let mut who = Who { @@ -225,7 +304,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { need_runlevel, need_users, my_line_only, - args: matches.free, + args: files, }; who.exec(); diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 32d2427e0..9bd607ec3 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -1,11 +1,13 @@ -#[cfg(target_os = "linux")] use crate::common::util::*; #[cfg(target_os = "linux")] #[test] fn test_count() { for opt in vec!["-q", "--count"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } @@ -13,17 +15,21 @@ fn test_count() { #[test] fn test_boot() { for opt in vec!["-b", "--boot"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } #[cfg(target_os = "linux")] #[test] fn test_heading() { - for opt in vec!["-H"] { + for opt in vec!["-H", "--heading"] { // allow whitespace variation - // * minor whitespace differences occur between platform built-in outputs; specifically number of TABs between "TIME" and "COMMENT" may be variant - let actual = new_ucmd!().arg(opt).run().stdout_move_str(); + // * minor whitespace differences occur between platform built-in outputs; + // specifically number of TABs between "TIME" and "COMMENT" may be variant + let actual = new_ucmd!().arg(opt).succeeds().stdout_move_str(); let expect = expected_result(opt); println!("actual: {:?}", actual); println!("expect: {:?}", expect); @@ -37,7 +43,10 @@ fn test_heading() { #[test] fn test_short() { for opt in vec!["-s", "--short"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } @@ -45,7 +54,10 @@ fn test_short() { #[test] fn test_login() { for opt in vec!["-l", "--login"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } @@ -53,7 +65,109 @@ fn test_login() { #[test] fn test_m() { for opt in vec!["-m"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_process() { + for opt in vec!["-p", "--process"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_runlevel() { + for opt in vec!["-r", "--runlevel"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_time() { + for opt in vec!["-t", "--time"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_mesg() { + for opt in vec!["-w", "-T", "--users", "--message", "--writable"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_arg1_arg2() { + let scene = TestScenario::new(util_name!()); + + let expected = scene + .cmd_keepenv(util_name!()) + .env("LANGUAGE", "C") + .arg("am") + .arg("i") + .succeeds(); + + scene + .ucmd() + .arg("am") + .arg("i") + .succeeds() + .stdout_is(expected.stdout_str()); +} + +#[test] +fn test_too_many_args() { + let expected = + "error: The value 'u' was provided to '...' but it wasn't expecting any more values"; + + new_ucmd!() + .arg("am") + .arg("i") + .arg("u") + .fails() + .stderr_contains(expected); +} + +#[cfg(target_os = "linux")] +#[test] +fn test_users() { + for opt in vec!["-u", "--users"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_lookup() { + for opt in vec!["--lookup"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } @@ -61,15 +175,60 @@ fn test_m() { #[test] fn test_dead() { for opt in vec!["-d", "--dead"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } +#[cfg(target_os = "linux")] +#[test] +fn test_all_separately() { + // -a, --all same as -b -d --login -p -r -t -T -u + let scene = TestScenario::new(util_name!()); + + let expected = scene + .cmd_keepenv(util_name!()) + .env("LANGUAGE", "C") + .arg("-b") + .arg("-d") + .arg("--login") + .arg("-p") + .arg("-r") + .arg("-t") + .arg("-T") + .arg("-u") + .succeeds(); + + scene + .ucmd() + .arg("-b") + .arg("-d") + .arg("--login") + .arg("-p") + .arg("-r") + .arg("-t") + .arg("-T") + .arg("-u") + .succeeds() + .stdout_is(expected.stdout_str()); + + scene + .ucmd() + .arg("--all") + .succeeds() + .stdout_is(expected.stdout_str()); +} + #[cfg(target_os = "linux")] #[test] fn test_all() { for opt in vec!["-a", "--all"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } @@ -79,6 +238,6 @@ fn expected_result(arg: &str) -> String { .cmd_keepenv(util_name!()) .env("LANGUAGE", "C") .args(&[arg]) - .run() + .succeeds() .stdout_move_str() } From 512d206f1e87b546428c2aa45f8a9ef93ce89bef Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 29 Apr 2021 00:11:21 +0200 Subject: [PATCH 02/71] who: move from getopts to clap 2.33.3 (#2124) --- src/uu/who/Cargo.toml | 2 +- src/uu/who/src/who.rs | 97 +++++++++++++++++++++------------------ tests/by-util/test_who.rs | 2 +- 3 files changed, 54 insertions(+), 47 deletions(-) diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index ff1c5b2af..4d8eccb45 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 = "3.0.0-beta.2" +clap = "2.33.3" [[bin]] name = "who" diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 47c9dfa6e..ba1360eff 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -63,95 +63,100 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = App::new(executable!()) .version(VERSION) .about(ABOUT) - .override_usage(&usage[..]) + .usage(&usage[..]) .after_help(&after_help[..]) .arg( - Arg::new(options::ALL) + Arg::with_name(options::ALL) .long(options::ALL) - .short('a') - .about("same as -b -d --login -p -r -t -T -u"), + .short("a") + .help("same as -b -d --login -p -r -t -T -u"), ) .arg( - Arg::new(options::BOOT) + Arg::with_name(options::BOOT) .long(options::BOOT) - .short('b') - .about("time of last system boot"), + .short("b") + .help("time of last system boot"), ) .arg( - Arg::new(options::DEAD) + Arg::with_name(options::DEAD) .long(options::DEAD) - .short('d') - .about("print dead processes"), + .short("d") + .help("print dead processes"), ) .arg( - Arg::new(options::HEADING) + Arg::with_name(options::HEADING) .long(options::HEADING) - .short('H') - .about("print line of column headings"), + .short("H") + .help("print line of column headings"), ) .arg( - Arg::new(options::LOGIN) + Arg::with_name(options::LOGIN) .long(options::LOGIN) - .short('l') - .about("print system login processes"), + .short("l") + .help("print system login processes"), ) .arg( - Arg::new(options::LOOKUP) + Arg::with_name(options::LOOKUP) .long(options::LOOKUP) - .about("attempt to canonicalize hostnames via DNS"), + .help("attempt to canonicalize hostnames via DNS"), ) .arg( - Arg::new(options::ONLY_HOSTNAME_USER) - .short('m') - .about("only hostname and user associated with stdin"), + Arg::with_name(options::ONLY_HOSTNAME_USER) + .short("m") + .help("only hostname and user associated with stdin"), ) .arg( - Arg::new(options::PROCESS) + Arg::with_name(options::PROCESS) .long(options::PROCESS) - .short('p') - .about("print active processes spawned by init"), + .short("p") + .help("print active processes spawned by init"), ) .arg( - Arg::new(options::COUNT) + Arg::with_name(options::COUNT) .long(options::COUNT) - .short('q') - .about("all login names and number of users logged on"), + .short("q") + .help("all login names and number of users logged on"), ) .arg( #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] - Arg::new(options::RUNLEVEL) + Arg::with_name(options::RUNLEVEL) .long(options::RUNLEVEL) - .short('r') - .about("print current runlevel"), + .short("r") + .help("print current runlevel"), ) .arg( - Arg::new(options::SHORT) + Arg::with_name(options::SHORT) .long(options::SHORT) - .short('s') - .about("print only name, line, and time (default)"), + .short("s") + .help("print only name, line, and time (default)"), ) .arg( - Arg::new(options::TIME) + Arg::with_name(options::TIME) .long(options::TIME) - .short('t') - .about("print last system clock change"), + .short("t") + .help("print last system clock change"), ) .arg( - Arg::new(options::USERS) + Arg::with_name(options::USERS) .long(options::USERS) - .short('u') - .about("list users logged in"), + .short("u") + .help("list users logged in"), ) .arg( - Arg::new(options::MESG) + Arg::with_name(options::MESG) .long(options::MESG) - .short('T') - .visible_short_alias('w') + .short("T") + // .visible_short_alias('w') // TODO: requires clap "3.0.0-beta.2" .visible_aliases(&["message", "writable"]) - .about("add user's message status as +, - or ?"), + .help("add user's message status as +, - or ?"), ) .arg( - Arg::new(options::FILE) + Arg::with_name("w") // work around for `Arg::visible_short_alias` + .short("w") + .help("same as -T"), + ) + .arg( + Arg::with_name(options::FILE) .takes_value(true) .min_values(1) .max_values(2), @@ -184,7 +189,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // 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); + 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; diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 9bd607ec3..a5637f23a 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -139,7 +139,7 @@ fn test_arg1_arg2() { #[test] fn test_too_many_args() { let expected = - "error: The value 'u' was provided to '...' but it wasn't expecting any more values"; + "error: The value 'u' was provided to '...', but it wasn't expecting any more values"; new_ucmd!() .arg("am") From 9f45431bf041053268bd4e76c10189432122a637 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 29 Apr 2021 18:02:06 +0200 Subject: [PATCH 03/71] sort: add some custom string comparisons This removes the need to allocate a new string for each line when used with -f, -d or -i. Instead, a custom string comparison algorithm takes care of these cases. The resulting performance improvement is about 20% per flag (i.e. there is a 60% improvement when combining all three flags) As a side-effect, the size of the Line struct was reduced from 96 to 80 bytes, reducing the overhead for each line. --- src/uu/sort/src/custom_str_cmp.rs | 64 +++++++++++++++++ src/uu/sort/src/sort.rs | 111 ++++++------------------------ 2 files changed, 86 insertions(+), 89 deletions(-) create mode 100644 src/uu/sort/src/custom_str_cmp.rs diff --git a/src/uu/sort/src/custom_str_cmp.rs b/src/uu/sort/src/custom_str_cmp.rs new file mode 100644 index 000000000..a087a9fc2 --- /dev/null +++ b/src/uu/sort/src/custom_str_cmp.rs @@ -0,0 +1,64 @@ +// * This file is part of the uutils coreutils package. +// * +// * (c) Michael Debertol +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + +//! Custom string comparisons. +//! +//! The goal is to compare strings without transforming them first (i.e. not allocating new strings) + +use std::cmp::Ordering; + +fn filter_char(c: char, ignore_non_printing: bool, ignore_non_dictionary: bool) -> bool { + if ignore_non_dictionary && !(c.is_ascii_alphanumeric() || c.is_ascii_whitespace()) { + return false; + } + if ignore_non_printing && (c.is_ascii_control() || !c.is_ascii()) { + return false; + } + true +} + +fn cmp_chars(a: char, b: char, ignore_case: bool) -> Ordering { + if ignore_case { + a.to_ascii_uppercase().cmp(&b.to_ascii_uppercase()) + } else { + a.cmp(&b) + } +} + +pub fn custom_str_cmp( + a: &str, + b: &str, + ignore_non_printing: bool, + ignore_non_dictionary: bool, + ignore_case: bool, +) -> 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); + } + let mut a_chars = a + .chars() + .filter(|&c| filter_char(c, ignore_non_printing, ignore_non_dictionary)); + let mut b_chars = b + .chars() + .filter(|&c| filter_char(c, ignore_non_printing, ignore_non_dictionary)); + loop { + let a_char = a_chars.next(); + let b_char = b_chars.next(); + match (a_char, b_char) { + (None, None) => return Ordering::Equal, + (Some(_), None) => return Ordering::Greater, + (None, Some(_)) => return Ordering::Less, + (Some(a_char), Some(b_char)) => { + let ordering = cmp_chars(a_char, b_char, ignore_case); + if ordering != Ordering::Equal { + return ordering; + } + } + } + } +} diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 18d9304fa..a18850e9d 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -15,10 +15,12 @@ #[macro_use] extern crate uucore; +mod custom_str_cmp; mod external_sort; mod numeric_str_cmp; use clap::{App, Arg}; +use custom_str_cmp::custom_str_cmp; use external_sort::{ExternalSorter, ExternallySortable}; use fnv::FnvHasher; use itertools::Itertools; @@ -206,33 +208,23 @@ impl From<&GlobalSettings> for KeySettings { #[derive(Debug, Serialize, Deserialize, Clone)] /// Represents the string selected by a FieldSelector. -enum SelectionRange { - /// If we had to transform this selection, we have to store a new string. - String(String), - /// If there was no transformation, we can store an index into the line. - ByIndex(Range), +struct SelectionRange { + range: Range, } impl SelectionRange { + fn new(range: Range) -> Self { + Self { range } + } + /// Gets the actual string slice represented by this Selection. - fn get_str<'a>(&'a self, line: &'a str) -> &'a str { - match self { - SelectionRange::String(string) => string.as_str(), - SelectionRange::ByIndex(range) => &line[range.to_owned()], - } + fn get_str<'a>(&self, line: &'a str) -> &'a str { + &line[self.range.to_owned()] } fn shorten(&mut self, new_range: Range) { - match self { - SelectionRange::String(string) => { - string.drain(new_range.end..); - string.drain(..new_range.start); - } - SelectionRange::ByIndex(range) => { - range.end = range.start + new_range.end; - range.start += new_range.start; - } - } + self.range.end = self.range.start + new_range.end; + self.range.start += new_range.start; } } @@ -303,14 +295,8 @@ impl Line { .selectors .iter() .map(|selector| { - let range = selector.get_selection(&line, fields.as_deref()); - let mut range = if let Some(transformed) = - transform(&line[range.to_owned()], &selector.settings) - { - SelectionRange::String(transformed) - } else { - SelectionRange::ByIndex(range) - }; + let mut range = + SelectionRange::new(selector.get_selection(&line, fields.as_deref())); let num_cache = if selector.settings.mode == SortMode::Numeric || selector.settings.mode == SortMode::HumanNumeric { @@ -460,34 +446,6 @@ impl Line { } } -/// Transform this line. Returns None if there's no need to transform. -fn transform(line: &str, settings: &KeySettings) -> Option { - let mut transformed = None; - if settings.ignore_case { - transformed = Some(line.to_uppercase()); - } - if settings.ignore_blanks { - transformed = Some( - transformed - .as_deref() - .unwrap_or(line) - .trim_start() - .to_string(), - ); - } - if settings.dictionary_order { - transformed = Some(remove_nondictionary_chars( - transformed.as_deref().unwrap_or(line), - )); - } - if settings.ignore_non_printing { - transformed = Some(remove_nonprinting_chars( - transformed.as_deref().unwrap_or(line), - )); - } - transformed -} - /// Tokenize a line into fields. fn tokenize(line: &str, separator: Option) -> Vec { if let Some(separator) = separator { @@ -1301,7 +1259,13 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering ), SortMode::Month => month_compare(a_str, b_str), SortMode::Version => version_compare(a_str, b_str), - SortMode::Default => default_compare(a_str, b_str), + SortMode::Default => custom_str_cmp( + a_str, + b_str, + settings.ignore_non_printing, + settings.dictionary_order, + settings.ignore_case, + ), } }; if cmp != Ordering::Equal { @@ -1313,7 +1277,7 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering let cmp = if global_settings.random || global_settings.stable || global_settings.unique { Ordering::Equal } else { - default_compare(&a.line, &b.line) + a.line.cmp(&b.line) }; if global_settings.reverse { @@ -1323,13 +1287,6 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering } } -// Test output against BSDs and GNU with their locale -// env var set to lc_ctype=utf-8 to enjoy the exact same output. -#[inline(always)] -fn default_compare(a: &str, b: &str) -> Ordering { - a.cmp(b) -} - // This function cleans up the initial comparison done by leading_num_common for a general numeric compare. // In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and // scientific notation, so we strip those lines only after the end of the following numeric string. @@ -1516,22 +1473,6 @@ fn version_compare(a: &str, b: &str) -> Ordering { } } -fn remove_nondictionary_chars(s: &str) -> String { - // According to GNU, dictionary chars are those of ASCII - // and a blank is a space or a tab - s.chars() - .filter(|c| c.is_ascii_alphanumeric() || c.is_ascii_whitespace()) - .collect::() -} - -fn remove_nonprinting_chars(s: &str) -> String { - // However, GNU says nonprinting chars are more permissive. - // All of ASCII except control chars ie, escape, newline - s.chars() - .filter(|c| c.is_ascii() && !c.is_ascii_control()) - .collect::() -} - fn print_sorted>(iter: T, settings: &GlobalSettings) { let mut file: Box = match settings.outfile { Some(ref filename) => match File::create(Path::new(&filename)) { @@ -1598,14 +1539,6 @@ mod tests { assert_eq!(Ordering::Equal, random_shuffle(a, b, c)); } - #[test] - fn test_default_compare() { - let a = "your own"; - let b = "your place"; - - assert_eq!(Ordering::Less, default_compare(a, b)); - } - #[test] fn test_month_compare() { let a = "JaN"; From a4813c26468963b7053de57fcddb0c95fa5b23fa Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 29 Apr 2021 18:03:00 +0200 Subject: [PATCH 04/71] sort: actually use the f64 cache This was probably reverted accidentally. --- src/uu/sort/src/sort.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index a18850e9d..6768c82c1 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1254,8 +1254,8 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering (b_str, b_selection.num_cache.as_num_info()), ), SortMode::GeneralNumeric => general_numeric_compare( - general_f64_parse(&a_str[get_leading_gen(a_str)]), - general_f64_parse(&b_str[get_leading_gen(b_str)]), + a_selection.num_cache.as_f64(), + b_selection.num_cache.as_f64(), ), SortMode::Month => month_compare(a_str, b_str), SortMode::Version => version_compare(a_str, b_str), From fecbf3dc856a37db6cf8d7a7a4ca15953b6b3f9d Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 29 Apr 2021 18:03:12 +0200 Subject: [PATCH 05/71] sort: remove an unneeded clone() --- 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 6768c82c1..c82524796 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1222,7 +1222,7 @@ fn ext_sort_by(unsorted: Vec, settings: GlobalSettings) -> Vec { settings.clone(), ); let iter = external_sorter - .sort_by(unsorted.into_iter(), settings.clone()) + .sort_by(unsorted.into_iter(), settings) .unwrap() .map(|x| x.unwrap()) .collect::>(); From d300895d28127befcc1f2922acba1b9279bc0f93 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Apr 2021 22:23:04 +0200 Subject: [PATCH 06/71] ls: add birth time for windows and attampt to fix test --- src/uu/ls/src/ls.rs | 1 + tests/by-util/test_ls.rs | 12 +++--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index d78e1977a..dc7ea4c08 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1559,6 +1559,7 @@ fn get_system_time(md: &Metadata, config: &Config) -> Option { match config.time { Time::Modification => md.modified().ok(), Time::Access => md.accessed().ok(), + Time::Birth => md.created().ok(), _ => None, } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index eeb7a6248..cfcbf9fd1 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -559,8 +559,6 @@ fn test_ls_long_ctime() { } #[test] -#[cfg(not(windows))] -// This test is currently failing on windows fn test_ls_order_birthtime() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -570,15 +568,11 @@ fn test_ls_order_birthtime() { After creating the first file try to sync it. This ensures the file gets created immediately instead of being saved inside the OS's IO operation buffer. - Without this, both files might accidentally be created at the same time, - even though we placed a timeout between creating the two. - - https://github.com/uutils/coreutils/pull/1986/#issuecomment-828490651 + Without this, both files might accidentally be created at the same time. */ at.make_file("test-birthtime-1").sync_all().unwrap(); - std::thread::sleep(std::time::Duration::from_millis(1)); - at.make_file("test-birthtime-2"); - at.touch("test-birthtime-1"); + at.make_file("test-birthtime-2").sync_all().unwrap(); + at.open("test-birthtime-1"); let result = scene.ucmd().arg("--time=birth").arg("-t").run(); From 45dd9d4e963c23ef0dce7f120ba43f80f14d2399 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 30 Apr 2021 20:19:43 +0200 Subject: [PATCH 07/71] tr/dirname: fix clap short_alias --- src/uu/dirname/src/dirname.rs | 33 +++++++++++++++++++-------------- src/uu/tr/src/tr.rs | 34 +++++++++++++++++++++++----------- tests/by-util/test_dirname.rs | 15 +++++++++++++++ tests/by-util/test_tr.rs | 14 ++++++++++++++ 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 5937f16ca..63693c982 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -12,37 +12,42 @@ use clap::{App, Arg}; use std::path::Path; use uucore::InvalidEncodingHandling; -static NAME: &str = "dirname"; -static SYNTAX: &str = "[OPTION] NAME..."; -static SUMMARY: &str = "strip last component from file name"; +static ABOUT: &str = "strip last component from file name"; static VERSION: &str = env!("CARGO_PKG_VERSION"); -static LONG_HELP: &str = " - Output each NAME with its last non-slash component and trailing slashes - removed; if NAME contains no /'s, output '.' (meaning the current - directory). -"; mod options { pub const ZERO: &str = "zero"; pub const DIR: &str = "dir"; } +fn get_usage() -> String { + format!("{0} [OPTION] NAME...", executable!()) +} + +fn get_long_usage() -> String { + String::from( + "Output each NAME with its last non-slash component and trailing slashes + removed; if NAME contains no /'s, output '.' (meaning the current directory).", + ) +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); + let usage = get_usage(); + let after_help = get_long_usage(); + let matches = App::new(executable!()) - .name(NAME) - .usage(SYNTAX) - .about(SUMMARY) - .after_help(LONG_HELP) + .about(ABOUT) + .usage(&usage[..]) + .after_help(&after_help[..]) .version(VERSION) .arg( Arg::with_name(options::ZERO) - .short(options::ZERO) + .long(options::ZERO) .short("z") - .takes_value(false) .help("separate output with NUL rather than newline"), ) .arg(Arg::with_name(options::DIR).hidden(true).multiple(true)) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 6c1c0746a..706151c35 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -23,11 +23,9 @@ use std::io::{stdin, stdout, BufRead, BufWriter, Write}; use crate::expand::ExpandSet; use uucore::InvalidEncodingHandling; -static NAME: &str = "tr"; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "translate or delete characters"; -static LONG_HELP: &str = "Translate, squeeze, and/or delete characters from standard input, -writing to standard output."; + const BUFFER_LEN: usize = 1024; mod options { @@ -186,24 +184,38 @@ fn get_usage() -> String { format!("{} [OPTION]... SET1 [SET2]", executable!()) } +fn get_long_usage() -> String { + String::from( + "Translate, squeeze, and/or delete characters from standard input, +writing to standard output.", + ) +} + pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); + let usage = get_usage(); + let after_help = get_long_usage(); + let matches = App::new(executable!()) .version(VERSION) .about(ABOUT) .usage(&usage[..]) - .after_help(LONG_HELP) + .after_help(&after_help[..]) .arg( Arg::with_name(options::COMPLEMENT) - .short("C") + // .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") @@ -216,8 +228,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .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", + listed in the last specified SET, with a single occurrence + of that character", ), ) .arg( @@ -230,7 +242,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .get_matches_from(args); let delete_flag = matches.is_present(options::DELETE); - let complement_flag = matches.is_present(options::COMPLEMENT); + let complement_flag = matches.is_present(options::COMPLEMENT) || matches.is_present("C"); let squeeze_flag = matches.is_present(options::SQUEEZE); let truncate_flag = matches.is_present(options::TRUNCATE); @@ -242,7 +254,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if sets.is_empty() { show_error!( "missing operand\nTry `{} --help` for more information.", - NAME + executable!() ); return 1; } @@ -251,7 +263,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { show_error!( "missing operand after ‘{}’\nTry `{} --help` for more information.", sets[0], - NAME + executable!() ); return 1; } diff --git a/tests/by-util/test_dirname.rs b/tests/by-util/test_dirname.rs index bcb4378d6..026ac22bb 100644 --- a/tests/by-util/test_dirname.rs +++ b/tests/by-util/test_dirname.rs @@ -16,6 +16,21 @@ fn test_path_without_trailing_slashes() { .stdout_is("/root/alpha/beta/gamma/delta/epsilon\n"); } +#[test] +fn test_path_without_trailing_slashes_and_zero() { + new_ucmd!() + .arg("-z") + .arg("/root/alpha/beta/gamma/delta/epsilon/omega") + .succeeds() + .stdout_is("/root/alpha/beta/gamma/delta/epsilon\u{0}"); + + new_ucmd!() + .arg("--zero") + .arg("/root/alpha/beta/gamma/delta/epsilon/omega") + .succeeds() + .stdout_is("/root/alpha/beta/gamma/delta/epsilon\u{0}"); +} + #[test] fn test_root() { new_ucmd!().arg("/").run().stdout_is("/\n"); diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 630c305c6..995fb6533 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -45,6 +45,20 @@ fn test_delete_complement() { .stdout_is("ac"); } +#[test] +fn test_delete_complement_2() { + new_ucmd!() + .args(&["-d", "-C", "0-9"]) + .pipe_in("Phone: 01234 567890") + .succeeds() + .stdout_is("01234567890"); + new_ucmd!() + .args(&["-d", "--complement", "0-9"]) + .pipe_in("Phone: 01234 567890") + .succeeds() + .stdout_is("01234567890"); +} + #[test] fn test_squeeze() { new_ucmd!() From 798a03331170766b0f83e8de8ad451b67bd6ca94 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 30 Apr 2021 15:23:54 +0200 Subject: [PATCH 08/71] pinky: move from getopts to clap (#2123) --- src/uu/pinky/Cargo.toml | 1 + src/uu/pinky/src/pinky.rs | 164 ++++++++++++++++++++++-------------- tests/by-util/test_pinky.rs | 30 +++++++ 3 files changed, 132 insertions(+), 63 deletions(-) diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index 3f4a75241..a3c36259a 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -17,6 +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" [[bin]] name = "pinky" diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index a02096bc8..e116a2382 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -19,67 +19,110 @@ use std::io::BufReader; use std::fs::File; use std::os::unix::fs::MetadataExt; +use clap::{App, Arg}; use std::path::PathBuf; use uucore::InvalidEncodingHandling; -static SYNTAX: &str = "[OPTION]... [USER]..."; -static SUMMARY: &str = "A lightweight 'finger' program; print user information."; - const BUFSIZE: usize = 1024; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "pinky - lightweight finger"; + +mod options { + pub const LONG_FORMAT: &str = "long_format"; + pub const OMIT_HOME_DIR: &str = "omit_home_dir"; + pub const OMIT_PROJECT_FILE: &str = "omit_project_file"; + pub const OMIT_PLAN_FILE: &str = "omit_plan_file"; + pub const SHORT_FORMAT: &str = "short_format"; + pub const OMIT_HEADINGS: &str = "omit_headings"; + pub const OMIT_NAME: &str = "omit_name"; + pub const OMIT_NAME_HOST: &str = "omit_name_host"; + pub const OMIT_NAME_HOST_TIME: &str = "omit_name_host_time"; + pub const USER: &str = "user"; +} + +fn get_usage() -> String { + format!("{0} [OPTION]... [USER]...", executable!()) +} + +fn get_long_usage() -> String { + format!( + "A lightweight 'finger' program; print user information.\n\ + The utmp file will be {}.", + utmpx::DEFAULT_FILE + ) +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let long_help = &format!( - " - -l produce long format output for the specified USERs - -b omit the user's home directory and shell in long format - -h omit the user's project file in long format - -p omit the user's plan file in long format - -s do short format output, this is the default - -f omit the line of column headings in short format - -w omit the user's full name in short format - -i omit the user's full name and remote host in short format - -q omit the user's full name, remote host and idle time - in short format - --help display this help and exit - --version output version information and exit + let usage = get_usage(); + let after_help = get_long_usage(); -The utmp file will be {}", - utmpx::DEFAULT_FILE - ); - let mut opts = app!(SYNTAX, SUMMARY, &long_help); - opts.optflag( - "l", - "", - "produce long format output for the specified USERs", - ); - opts.optflag( - "b", - "", - "omit the user's home directory and shell in long format", - ); - opts.optflag("h", "", "omit the user's project file in long format"); - opts.optflag("p", "", "omit the user's plan file in long format"); - opts.optflag("s", "", "do short format output, this is the default"); - opts.optflag("f", "", "omit the line of column headings in short format"); - opts.optflag("w", "", "omit the user's full name in short format"); - opts.optflag( - "i", - "", - "omit the user's full name and remote host in short format", - ); - opts.optflag( - "q", - "", - "omit the user's full name, remote host and idle time in short format", - ); - opts.optflag("", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .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 matches = opts.parse(args); + let users: Vec = matches + .values_of(options::USER) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); // If true, display the hours:minutes since each user has touched // the keyboard, or blank if within the last minute, or days followed @@ -87,45 +130,40 @@ The utmp file will be {}", let mut include_idle = true; // If true, display a line at the top describing each field. - let include_heading = !matches.opt_present("f"); + let include_heading = !matches.is_present(options::OMIT_HEADINGS); // if true, display the user's full name from pw_gecos. let mut include_fullname = true; // if true, display the user's ~/.project file when doing long format. - let include_project = !matches.opt_present("h"); + let include_project = !matches.is_present(options::OMIT_PROJECT_FILE); // if true, display the user's ~/.plan file when doing long format. - let include_plan = !matches.opt_present("p"); + let include_plan = !matches.is_present(options::OMIT_PLAN_FILE); // if true, display the user's home directory and shell // when doing long format. - let include_home_and_shell = !matches.opt_present("b"); + let include_home_and_shell = !matches.is_present(options::OMIT_HOME_DIR); // if true, use the "short" output format. - let do_short_format = !matches.opt_present("l"); + let do_short_format = !matches.is_present(options::LONG_FORMAT); /* if true, display the ut_host field. */ let mut include_where = true; - if matches.opt_present("w") { + if matches.is_present(options::OMIT_NAME) { include_fullname = false; } - if matches.opt_present("i") { + if matches.is_present(options::OMIT_NAME_HOST) { include_fullname = false; include_where = false; } - if matches.opt_present("q") { + if matches.is_present(options::OMIT_NAME_HOST_TIME) { include_fullname = false; include_idle = false; include_where = false; } - if !do_short_format && matches.free.is_empty() { - show_usage_error!("no username specified; at least one must be specified when using -l"); - return 1; - } - let pk = Pinky { include_idle, include_heading, @@ -134,7 +172,7 @@ The utmp file will be {}", include_plan, include_home_and_shell, include_where, - names: matches.free, + names: users, }; if do_short_format { diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index 7a4a3f3df..1a7ef8b61 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -34,6 +34,36 @@ fn test_long_format() { )); } +#[cfg(target_os = "linux")] +#[test] +fn test_long_format_multiple_users() { + let scene = TestScenario::new(util_name!()); + + let expected = scene + .cmd_keepenv(util_name!()) + .env("LANGUAGE", "C") + .arg("-l") + .arg("root") + .arg("root") + .arg("root") + .succeeds(); + + scene + .ucmd() + .arg("-l") + .arg("root") + .arg("root") + .arg("root") + .succeeds() + .stdout_is(expected.stdout_str()); +} + +#[test] +fn test_long_format_wo_user() { + // "no username specified; at least one must be specified when using -l" + new_ucmd!().arg("-l").fails().code_is(1); +} + #[cfg(target_os = "linux")] #[test] fn test_short_format_i() { From 0f3bc237393adef485182656b0edc986575c592d Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 29 Apr 2021 23:13:20 -0400 Subject: [PATCH 09/71] tr: implement translate and squeeze (-s) mode Add translate and squeeze mode to the `tr` program. For example: $ printf xx | tr -s x y y Fixes #2141. --- src/uu/tr/src/tr.rs | 54 ++++++++++++++++++++++++++++++++++++++-- tests/by-util/test_tr.rs | 18 ++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 6c1c0746a..09c4304a5 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -278,8 +278,58 @@ pub fn uumain(args: impl uucore::Args) -> i32 { translate_input(&mut locked_stdin, &mut buffered_stdout, op); } } else if squeeze_flag { - let op = SqueezeOperation::new(set1, complement_flag); - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + if sets.len() < 2 { + let op = SqueezeOperation::new(set1, complement_flag); + translate_input(&mut locked_stdin, &mut buffered_stdout, op); + } else { + // Define a closure that computes the translation using a hash map. + // + // The `unwrap()` should never panic because the + // `TranslateOperation.translate()` method always returns + // `Some`. + let mut set2 = ExpandSet::new(sets[1].as_ref()); + let translator = TranslateOperation::new(set1, &mut set2, truncate_flag); + let translate = |c| translator.translate(c, 0 as char).unwrap(); + + // Prepare some variables to be used for the closure that + // computes the squeeze operation. + // + // The `squeeze()` closure needs to be defined anew for + // each line of input, but these variables do not change + // while reading the input so they can be defined before + // the `while` loop. + let set2 = ExpandSet::new(sets[1].as_ref()); + let squeezer = SqueezeOperation::new(set2, complement_flag); + + // Prepare some memory to read each line of the input (`buf`) and to write + let mut buf = String::with_capacity(BUFFER_LEN + 4); + + // Loop over each line of stdin. + while let Ok(length) = locked_stdin.read_line(&mut buf) { + if length == 0 { + break; + } + + // Define a closure that computes the squeeze operation. + // + // We keep track of the previously seen character on + // each call to `squeeze()`, but we need to reset the + // `prev_c` variable at the beginning of each line of + // the input. That's why we define the closure inside + // the `while` loop. + let mut prev_c = 0 as char; + let squeeze = |c| { + let result = squeezer.translate(c, prev_c); + prev_c = c; + result + }; + + // First translate, then squeeze each character of the input line. + let filtered: String = buf.chars().map(translate).filter_map(squeeze).collect(); + buf.clear(); + buffered_stdout.write_all(filtered.as_bytes()).unwrap(); + } + } } else { let mut set2 = ExpandSet::new(sets[1].as_ref()); let op = TranslateOperation::new(set1, &mut set2, truncate_flag); diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 630c305c6..5d044a187 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -63,6 +63,24 @@ fn test_squeeze_complement() { .stdout_is("aaBcDcc"); } +#[test] +fn test_translate_and_squeeze() { + new_ucmd!() + .args(&["-s", "x", "y"]) + .pipe_in("xx") + .run() + .stdout_is("y"); +} + +#[test] +fn test_translate_and_squeeze_multiple_lines() { + new_ucmd!() + .args(&["-s", "x", "y"]) + .pipe_in("xxaax\nxaaxx") + .run() + .stdout_is("yaay\nyaay"); +} + #[test] fn test_delete_and_squeeze() { new_ucmd!() From 59ea28628bce74b5c17ea9d3462c27f82f5e5e84 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 13:11:41 +0200 Subject: [PATCH 10/71] printf: remove useless declaration --- .../printf/src/tokenize/num_format/formatters/base_conv/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs b/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs index 82971df3e..7d1d805c6 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs @@ -158,7 +158,6 @@ pub fn base_conv_float(src: &[u8], radix_src: u8, _radix_dest: u8) -> f64 { // to implement this for arbitrary string input. // until then, the below operates as an outline // of how it would work. - let result: Vec = vec![0]; let mut factor: f64 = 1_f64; let radix_src_float: f64 = f64::from(radix_src); let mut r: f64 = 0_f64; From 66930186319c6c0a502e8aad80383313ebb6cbc3 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 13:12:00 +0200 Subject: [PATCH 11/71] refresh cargo.lock with recent updates --- Cargo.lock | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31787e626..33d599762 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,11 +12,11 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ - "memchr 2.3.4", + "memchr 2.4.0", ] [[package]] @@ -111,7 +111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" dependencies = [ "lazy_static", - "memchr 2.3.4", + "memchr 2.4.0", "regex-automata", "serde", ] @@ -495,9 +495,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" +checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -508,9 +508,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" dependencies = [ "autocfg", "cfg-if 1.0.0", @@ -536,7 +536,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ - "memchr 2.3.4", + "memchr 2.4.0", ] [[package]] @@ -868,9 +868,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] name = "memoffset" @@ -1089,7 +1089,7 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ - "unicode-xid 0.2.1", + "unicode-xid 0.2.2", ] [[package]] @@ -1277,12 +1277,12 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.6" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" +checksum = "a068b905b8cb93815aa3069ae48653d90f382308aebd1d33d940ac1f1d771e2d" dependencies = [ "aho-corasick", - "memchr 2.3.4", + "memchr 2.4.0", "regex-syntax", ] @@ -1297,9 +1297,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.23" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" +checksum = "00efb87459ba4f6fb2169d20f68565555688e1250ee6825cdf6254f8b48fafb2" [[package]] name = "remove_dir_all" @@ -1489,7 +1489,7 @@ checksum = "ad184cc9470f9117b2ac6817bfe297307418819ba40552f9b3846f05c33d5373" dependencies = [ "proc-macro2", "quote 1.0.9", - "unicode-xid 0.2.1", + "unicode-xid 0.2.2", ] [[package]] @@ -1636,9 +1636,9 @@ checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "unindent" @@ -1806,7 +1806,7 @@ version = "0.0.6" dependencies = [ "bstr", "clap", - "memchr 2.3.4", + "memchr 2.4.0", "uucore", "uucore_procs", ] @@ -2175,7 +2175,7 @@ dependencies = [ "aho-corasick", "clap", "libc", - "memchr 2.3.4", + "memchr 2.4.0", "regex", "regex-syntax", "uucore", @@ -2276,7 +2276,7 @@ dependencies = [ "aho-corasick", "clap", "libc", - "memchr 2.3.4", + "memchr 2.4.0", "regex", "regex-syntax", "uucore", From d2913f80804430c3101f7bdb33f89df15d91204b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 13:12:10 +0200 Subject: [PATCH 12/71] rustfmt the recent change --- src/uu/factor/src/factor.rs | 8 ++++---- src/uu/factor/src/table.rs | 2 +- tests/by-util/test_sort.rs | 10 +++++----- tests/by-util/test_truncate.rs | 10 ++++++++-- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 42586d1da..5a85194c4 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -125,7 +125,7 @@ fn _factor(num: u64, f: Factors) -> Factors let n = A::new(num); let divisor = match miller_rabin::test::(n) { Prime => { - #[cfg(feature="coz")] + #[cfg(feature = "coz")] coz::progress!("factor found"); let mut r = f; r.push(num); @@ -141,7 +141,7 @@ fn _factor(num: u64, f: Factors) -> Factors } pub fn factor(mut n: u64) -> Factors { - #[cfg(feature="coz")] + #[cfg(feature = "coz")] coz::begin!("factorization"); let mut factors = Factors::one(); @@ -156,7 +156,7 @@ pub fn factor(mut n: u64) -> Factors { } if n == 1 { - #[cfg(feature="coz")] + #[cfg(feature = "coz")] coz::end!("factorization"); return factors; } @@ -169,7 +169,7 @@ pub fn factor(mut n: u64) -> Factors { _factor::>(n, factors) }; - #[cfg(feature="coz")] + #[cfg(feature = "coz")] coz::end!("factorization"); return r; diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs index 6291b92c1..94ad6df4c 100644 --- a/src/uu/factor/src/table.rs +++ b/src/uu/factor/src/table.rs @@ -33,7 +33,7 @@ pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) { if x <= ceil { num = x; k += 1; - #[cfg(feature="coz")] + #[cfg(feature = "coz")] coz::progress!("factor found"); } else { if k > 0 { diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index cd3a3a496..eac9490a5 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -16,15 +16,15 @@ fn test_helper(file_name: &str, args: &str) { } // FYI, the initialization size of our Line struct is 96 bytes. -// -// At very small buffer sizes, with that overhead we are certainly going -// to overrun our buffer way, way, way too quickly because of these excess +// +// At very small buffer sizes, with that overhead we are certainly going +// to overrun our buffer way, way, way too quickly because of these excess // bytes for the struct. // // For instance, seq 0..20000 > ...text = 108894 bytes // But overhead is 1920000 + 108894 = 2028894 bytes // -// Or kjvbible-random.txt = 4332506 bytes, but minimum size of its +// Or kjvbible-random.txt = 4332506 bytes, but minimum size of its // 99817 lines in memory * 96 bytes = 9582432 bytes // // Here, we test 108894 bytes with a 50K buffer @@ -59,7 +59,7 @@ fn test_human_numeric_whitespace() { test_helper("human-numeric-whitespace", "-h"); } -// This tests where serde often fails when reading back JSON +// This tests where serde often fails when reading back JSON // if it finds a null value #[test] fn test_extsort_as64_bailout() { diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index d524c096f..8f88f4c74 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -224,8 +224,14 @@ fn test_size_and_reference() { let mut file1 = at.make_file(TFILE1); let mut file2 = at.make_file(TFILE2); file1.write_all(b"1234567890").unwrap(); - ucmd.args(&["--reference", TFILE1, "--size", "+5", TFILE2]).succeeds(); + ucmd.args(&["--reference", TFILE1, "--size", "+5", TFILE2]) + .succeeds(); file2.seek(SeekFrom::End(0)).unwrap(); let actual = file2.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } From e1cc434c24144fba951266d12508a3eafbfb26dd Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 15:27:54 +0200 Subject: [PATCH 13/71] ignore the test_ls_styles --- tests/by-util/test_ls.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index cfcbf9fd1..4258331e0 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -583,6 +583,7 @@ fn test_ls_order_birthtime() { } #[test] +#[ignore] fn test_ls_styles() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; From 308bdd7fc99fdf6aba21cb48f09c86bc3791f5a6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 15:55:58 +0200 Subject: [PATCH 14/71] ignore test_ls_order_birthtime too --- tests/by-util/test_ls.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 4258331e0..2b8f311d1 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -559,6 +559,7 @@ fn test_ls_long_ctime() { } #[test] +#[ignore] fn test_ls_order_birthtime() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; From 70ab0d01d294575f0f9b769ae53339e63c6b9a40 Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Sat, 1 May 2021 08:48:18 +0200 Subject: [PATCH 15/71] kill: change default signal The default signal is SIGTERM, not SIGKILL. --- src/uu/kill/src/kill.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 916c13cc3..fe925ce37 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -59,7 +59,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return kill( &matches .opt_str("signal") - .unwrap_or_else(|| obs_signal.unwrap_or_else(|| "9".to_owned())), + .unwrap_or_else(|| obs_signal.unwrap_or_else(|| "TERM".to_owned())), matches.free, ) } From 0ff10589984ca8a7c81bf496cbca4d5df8f1da93 Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Mon, 26 Apr 2021 22:14:59 +0200 Subject: [PATCH 16/71] kill: add integration tests --- tests/by-util/test_kill.rs | 127 ++++++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index 651491045..637aea9a2 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -1 +1,126 @@ -// ToDO: add tests +use crate::common::util::*; +use regex::Regex; +use std::os::unix::process::ExitStatusExt; +use std::process::{Child, Command}; + +// A child process the tests will try to kill. +struct Target { + child: Child, + killed: bool, +} + +impl Target { + // Creates a target that will naturally die after some time if not killed + // fast enough. + // This timeout avoids hanging failing tests. + fn new() -> Target { + Target { + child: Command::new("sleep") + .arg("30") + .spawn() + .expect("cannot spawn target"), + killed: false, + } + } + + // Waits for the target to complete and returns the signal it received if any. + fn wait_for_signal(&mut self) -> Option { + let sig = self.child.wait().expect("cannot wait on target").signal(); + self.killed = true; + sig + } + + fn pid(&self) -> u32 { + self.child.id() + } +} + +impl Drop for Target { + // Terminates this target to avoid littering test boxes with zombi processes + // when a test fails after creating a target but before killing it. + fn drop(&mut self) { + if !self.killed { + self.child.kill().expect("cannot kill target"); + } + } +} + +#[test] +fn test_kill_list_all_signals() { + // Check for a few signals. Do not try to be comprehensive. + new_ucmd!() + .arg("-l") + .succeeds() + .stdout_contains("KILL") + .stdout_contains("TERM") + .stdout_contains("HUP"); +} + +#[test] +fn test_kill_list_all_signals_as_table() { + // Check for a few signals. Do not try to be comprehensive. + new_ucmd!() + .arg("-t") + .succeeds() + .stdout_contains("KILL") + .stdout_contains("TERM") + .stdout_contains("HUP"); +} + +#[test] +fn test_kill_list_one_signal_from_name() { + // Use SIGKILL because it is 9 on all unixes. + new_ucmd!() + .arg("-l") + .arg("KILL") + .succeeds() + .stdout_matches(&Regex::new("\\b9\\b").unwrap()); +} + +#[test] +fn test_kill_set_bad_signal_name() { + new_ucmd!() + .arg("-s") + .arg("IAMNOTASIGNAL") + .fails() + .stderr_contains("unknown signal"); +} + +#[test] +fn test_kill_with_default_signal() { + let mut target = Target::new(); + new_ucmd!().arg(format!("{}", target.pid())).succeeds(); + assert_eq!(target.wait_for_signal(), Some(libc::SIGTERM)); +} + +#[test] +fn test_kill_with_signal_number_old_form() { + let mut target = Target::new(); + new_ucmd!() + .arg("-9") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(9)); +} + +#[test] +fn test_kill_with_signal_number_new_form() { + let mut target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("9") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(9)); +} + +#[test] +fn test_kill_with_signal_name_new_form() { + let mut target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("KILL") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL)); +} From 01d178cf172bb205721be32c4e9d52e0e38b722d Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 1 May 2021 16:53:34 +0200 Subject: [PATCH 17/71] sort: don't rely on serde-json for extsort It is much faster to just write the lines to disk, separated by \n (or \0 if zero-terminated is enabled), instead of serializing to json. external_sort now knows of the Line struct instead of interacting with it using the ExternallySortable trait. Similarly, it now uses the crash_if_err! macro to handle errors, instead of bubbling them up. Some functions were changed from taking &[Line] as the input to taking an Iterator. This removes the need to collect to a Vec when not necessary. --- Cargo.lock | 8 - src/uu/sort/Cargo.toml | 4 +- src/uu/sort/src/external_sort/mod.rs | 356 ++++++++++----------------- src/uu/sort/src/numeric_str_cmp.rs | 7 +- src/uu/sort/src/sort.rs | 220 ++++++++--------- 5 files changed, 249 insertions(+), 346 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31787e626..9ffc56720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1372,9 +1372,6 @@ name = "serde" version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" -dependencies = [ - "serde_derive", -] [[package]] name = "serde_cbor" @@ -1453,9 +1450,6 @@ name = "smallvec" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" -dependencies = [ - "serde", -] [[package]] name = "strsim" @@ -2391,8 +2385,6 @@ dependencies = [ "rand 0.7.3", "rayon", "semver", - "serde", - "serde_json", "smallvec 1.6.1", "tempdir", "unicode-width", diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 80ffc92c9..3784ccbb0 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -15,15 +15,13 @@ edition = "2018" path = "src/sort.rs" [dependencies] -serde_json = { version = "1.0.64", default-features = false, features = ["alloc"] } -serde = { version = "1.0", features = ["derive"] } rayon = "1.5" rand = "0.7" clap = "2.33" fnv = "1.0.7" itertools = "0.10.0" semver = "0.9.0" -smallvec = { version="1.6.1", features=["serde"] } +smallvec = "1.6.1" unicode-width = "0.1.8" 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/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index fd942d4a7..725b17bbd 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -1,50 +1,32 @@ -use std::clone::Clone; -use std::cmp::Ordering::Less; +use std::cmp::Ordering; use std::collections::VecDeque; -use std::error::Error; use std::fs::{File, OpenOptions}; -use std::io::SeekFrom::Start; +use std::io::SeekFrom; use std::io::{BufRead, BufReader, BufWriter, Seek, Write}; -use std::marker::PhantomData; -use std::path::PathBuf; +use std::path::Path; -use serde::de::DeserializeOwned; -use serde::Serialize; -use serde_json; use tempdir::TempDir; use super::{GlobalSettings, Line}; -/// Trait for types that can be used by -/// [ExternalSorter](struct.ExternalSorter.html). Must be sortable, cloneable, -/// serializeable, and able to report on it's size -pub trait ExternallySortable: Clone + Serialize + DeserializeOwned { - /// Get the size, in bytes, of this object (used to constrain the buffer - /// used in the external sort). - fn get_size(&self) -> u64; -} - /// Iterator that provides sorted `T`s -pub struct ExtSortedIterator { +pub struct ExtSortedIterator { buffers: Vec>, chunk_offsets: Vec, - max_per_chunk: u64, - chunks: u64, + max_per_chunk: usize, + chunks: usize, tmp_dir: TempDir, settings: GlobalSettings, failed: bool, } -impl Iterator for ExtSortedIterator -where - Line: ExternallySortable, -{ - type Item = Result>; +impl Iterator for ExtSortedIterator { + type Item = Line; /// # Errors /// /// This method can fail due to issues reading intermediate sorted chunks - /// from disk, or due to serde deserialization issues + /// from disk fn next(&mut self) -> Option { if self.failed { return None; @@ -53,29 +35,18 @@ where let mut empty = true; for chunk_num in 0..self.chunks { if self.buffers[chunk_num as usize].is_empty() { - let mut f = match File::open(self.tmp_dir.path().join(chunk_num.to_string())) { - Ok(f) => f, - Err(e) => { - self.failed = true; - return Some(Err(Box::new(e))); - } - }; - match f.seek(Start(self.chunk_offsets[chunk_num as usize])) { - Ok(_) => (), - Err(e) => { - self.failed = true; - return Some(Err(Box::new(e))); - } - } - let bytes_read = - match fill_buff(&mut self.buffers[chunk_num as usize], f, self.max_per_chunk) { - Ok(bytes_read) => bytes_read, - Err(e) => { - self.failed = true; - return Some(Err(e)); - } - }; - self.chunk_offsets[chunk_num as usize] += bytes_read; + let mut f = crash_if_err!( + 1, + File::open(self.tmp_dir.path().join(chunk_num.to_string())) + ); + crash_if_err!(1, f.seek(SeekFrom::Start(self.chunk_offsets[chunk_num]))); + let bytes_read = fill_buff( + &mut self.buffers[chunk_num as usize], + f, + self.max_per_chunk, + &self.settings, + ); + self.chunk_offsets[chunk_num as usize] += bytes_read as u64; if !self.buffers[chunk_num as usize].is_empty() { empty = false; } @@ -91,205 +62,150 @@ where // check is_empty() before unwrap()ing let mut idx = 0; for chunk_num in 0..self.chunks as usize { - if !self.buffers[chunk_num].is_empty() { - if self.buffers[idx].is_empty() - || (super::compare_by)( + if !self.buffers[chunk_num].is_empty() + && (self.buffers[idx].is_empty() + || super::compare_by( self.buffers[chunk_num].front().unwrap(), self.buffers[idx].front().unwrap(), &self.settings, - ) == Less - { - idx = chunk_num; - } + ) == Ordering::Less) + { + idx = chunk_num; } } // unwrap due to checks above let r = self.buffers[idx].pop_front().unwrap(); - Some(Ok(r)) + Some(r) } } -/// Perform an external sort on an unsorted stream of incoming data -pub struct ExternalSorter -where - Line: ExternallySortable, -{ - tmp_dir: Option, - buffer_bytes: u64, - phantom: PhantomData, - settings: GlobalSettings, -} +/// Sort (based on `compare`) the `T`s provided by `unsorted` and return an +/// iterator +/// +/// # Errors +/// +/// This method can fail due to issues writing intermediate sorted chunks +/// to disk. +pub fn ext_sort( + unsorted: impl Iterator, + settings: &GlobalSettings, +) -> ExtSortedIterator { + let tmp_dir = crash_if_err!(1, TempDir::new_in(&settings.tmp_dir, "uutils_sort")); -impl ExternalSorter -where - Line: ExternallySortable, -{ - /// Create a new `ExternalSorter` with a specified memory buffer and - /// temporary directory - pub fn new( - buffer_bytes: u64, - tmp_dir: Option, - settings: GlobalSettings, - ) -> ExternalSorter { - ExternalSorter { - buffer_bytes, - tmp_dir, - phantom: PhantomData, + let mut iter = ExtSortedIterator { + buffers: Vec::new(), + chunk_offsets: Vec::new(), + max_per_chunk: 0, + chunks: 0, + tmp_dir, + settings: settings.clone(), + failed: false, + }; + + let mut total_read = 0; + let mut chunk = Vec::new(); + + // make the initial chunks on disk + for seq in unsorted { + let seq_size = seq.estimate_size(); + total_read += seq_size; + + chunk.push(seq); + + if total_read >= settings.buffer_size { + super::sort_by(&mut chunk, &settings); + write_chunk( + settings, + &iter.tmp_dir.path().join(iter.chunks.to_string()), + &mut chunk, + ); + chunk.clear(); + total_read = 0; + iter.chunks += 1; + } + } + // write the last chunk + if !chunk.is_empty() { + super::sort_by(&mut chunk, &settings); + write_chunk( settings, - } + &iter.tmp_dir.path().join(iter.chunks.to_string()), + &mut chunk, + ); + iter.chunks += 1; } - /// Sort (based on `compare`) the `T`s provided by `unsorted` and return an - /// iterator - /// - /// # Errors - /// - /// This method can fail due to issues writing intermediate sorted chunks - /// to disk, or due to serde serialization issues - pub fn sort_by( - &self, - unsorted: I, - settings: GlobalSettings, - ) -> Result, Box> - where - I: Iterator, - { - let tmp_dir = match self.tmp_dir { - Some(ref p) => TempDir::new_in(p, "uutils_sort")?, - None => TempDir::new("uutils_sort")?, - }; - // creating the thing we need to return first due to the face that we need to - // borrow tmp_dir and move it out - let mut iter = ExtSortedIterator { - buffers: Vec::new(), - chunk_offsets: Vec::new(), - max_per_chunk: 0, - chunks: 0, - tmp_dir, - settings, - failed: false, - }; + // initialize buffers for each chunk + // + // Having a right sized buffer for each chunk for smallish values seems silly to me? + // + // We will have to have the entire iter in memory sometime right? + // Set minimum to the size of the writer buffer, ~8K - { - let mut total_read = 0; - let mut chunk = Vec::new(); - // Initial buffer is specified by user - let mut adjusted_buffer_size = self.buffer_bytes; - let (iter_size, _) = unsorted.size_hint(); - - // make the initial chunks on disk - for seq in unsorted { - let seq_size = seq.get_size(); - total_read += seq_size; - - // GNU minimum is 16 * (sizeof struct + 2), but GNU uses about - // 1/10 the memory that we do. And GNU even says in the code it may - // not work on small buffer sizes. - // - // The following seems to work pretty well, and has about the same max - // RSS as lower minimum values. - // - let minimum_buffer_size: u64 = iter_size as u64 * seq_size / 8; - - adjusted_buffer_size = - // Grow buffer size for a struct/Line larger than buffer - if adjusted_buffer_size < seq_size { - seq_size - } else if adjusted_buffer_size < minimum_buffer_size { - minimum_buffer_size - } else { - adjusted_buffer_size - }; - chunk.push(seq); - - if total_read >= adjusted_buffer_size { - super::sort_by(&mut chunk, &self.settings); - self.write_chunk( - &iter.tmp_dir.path().join(iter.chunks.to_string()), - &mut chunk, - )?; - chunk.clear(); - total_read = 0; - iter.chunks += 1; - } - } - // write the last chunk - if chunk.len() > 0 { - super::sort_by(&mut chunk, &self.settings); - self.write_chunk( - &iter.tmp_dir.path().join(iter.chunks.to_string()), - &mut chunk, - )?; - iter.chunks += 1; - } - - // initialize buffers for each chunk - // - // Having a right sized buffer for each chunk for smallish values seems silly to me? - // - // We will have to have the entire iter in memory sometime right? - // Set minimum to the size of the writer buffer, ~8K - // - const MINIMUM_READBACK_BUFFER: u64 = 8200; - let right_sized_buffer = adjusted_buffer_size - .checked_div(iter.chunks) - .unwrap_or(adjusted_buffer_size); - iter.max_per_chunk = if right_sized_buffer > MINIMUM_READBACK_BUFFER { - right_sized_buffer - } else { - MINIMUM_READBACK_BUFFER - }; - iter.buffers = vec![VecDeque::new(); iter.chunks as usize]; - iter.chunk_offsets = vec![0 as u64; iter.chunks as usize]; - for chunk_num in 0..iter.chunks { - let offset = fill_buff( - &mut iter.buffers[chunk_num as usize], - File::open(iter.tmp_dir.path().join(chunk_num.to_string()))?, - iter.max_per_chunk, - )?; - iter.chunk_offsets[chunk_num as usize] = offset; - } - } - - Ok(iter) + const MINIMUM_READBACK_BUFFER: usize = 8200; + let right_sized_buffer = settings + .buffer_size + .checked_div(iter.chunks) + .unwrap_or(settings.buffer_size); + iter.max_per_chunk = if right_sized_buffer > MINIMUM_READBACK_BUFFER { + right_sized_buffer + } else { + MINIMUM_READBACK_BUFFER + }; + iter.buffers = vec![VecDeque::new(); iter.chunks]; + iter.chunk_offsets = vec![0; iter.chunks]; + for chunk_num in 0..iter.chunks { + let offset = fill_buff( + &mut iter.buffers[chunk_num], + crash_if_err!( + 1, + File::open(iter.tmp_dir.path().join(chunk_num.to_string())) + ), + iter.max_per_chunk, + &settings, + ); + iter.chunk_offsets[chunk_num] = offset as u64; } - fn write_chunk(&self, file: &PathBuf, chunk: &mut Vec) -> Result<(), Box> { - let new_file = OpenOptions::new().create(true).append(true).open(file)?; - let mut buf_write = Box::new(BufWriter::new(new_file)) as Box; - for s in chunk { - let mut serialized = serde_json::to_string(&s).expect("JSON write error: "); - serialized.push_str("\n"); - buf_write.write(serialized.as_bytes())?; - } - buf_write.flush()?; - - Ok(()) - } + iter } -fn fill_buff( +fn write_chunk(settings: &GlobalSettings, file: &Path, chunk: &mut Vec) { + let new_file = crash_if_err!(1, OpenOptions::new().create(true).append(true).open(file)); + let mut buf_write = BufWriter::new(new_file); + for s in chunk { + crash_if_err!(1, buf_write.write_all(s.line.as_bytes())); + crash_if_err!( + 1, + buf_write.write_all(if settings.zero_terminated { "\0" } else { "\n" }.as_bytes(),) + ); + } + crash_if_err!(1, buf_write.flush()); +} + +fn fill_buff( vec: &mut VecDeque, file: File, - max_bytes: u64, -) -> Result> -where - Line: ExternallySortable, -{ + max_bytes: usize, + settings: &GlobalSettings, +) -> usize { let mut total_read = 0; let mut bytes_read = 0; - for line in BufReader::new(file).lines() { - let line_s = line?; + for line in BufReader::new(file).split(if settings.zero_terminated { + b'\0' + } else { + b'\n' + }) { + let line_s = String::from_utf8(crash_if_err!(1, line)).unwrap(); bytes_read += line_s.len() + 1; - // This is where the bad stuff happens usually - let deserialized: Line = serde_json::from_str(&line_s).expect("JSON read error: "); - total_read += deserialized.get_size(); + let deserialized = Line::new(line_s, settings); + total_read += deserialized.estimate_size(); vec.push_back(deserialized); if total_read > max_bytes { break; } } - Ok(bytes_read as u64) + bytes_read } diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index b74d97867..f8666b701 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -14,21 +14,20 @@ //! More specifically, exponent can be understood so that the original number is in (1..10)*10^exponent. //! From that follows the constraints of this algorithm: It is able to compare numbers in ±(1*10^[i64::MIN]..10*10^[i64::MAX]). -use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, ops::Range}; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] enum Sign { Negative, Positive, } -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +#[derive(Debug, PartialEq, Clone)] pub struct NumInfo { exponent: i64, sign: Sign, } -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +#[derive(Debug, PartialEq, Clone)] pub struct NumInfoParseSettings { pub accept_si_units: bool, pub thousands_separator: Option, diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 18d9304fa..7c053e4ad 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -19,7 +19,7 @@ mod external_sort; mod numeric_str_cmp; use clap::{App, Arg}; -use external_sort::{ExternalSorter, ExternallySortable}; +use external_sort::ext_sort; use fnv::FnvHasher; use itertools::Itertools; use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; @@ -27,14 +27,13 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rayon::prelude::*; use semver::Version; -use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use std::cmp::Ordering; use std::collections::BinaryHeap; use std::env; use std::fs::File; use std::hash::{Hash, Hasher}; -use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write}; +use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::mem::replace; use std::ops::Range; use std::path::Path; @@ -104,7 +103,7 @@ enum SortMode { Default, } #[derive(Clone)] -struct GlobalSettings { +pub struct GlobalSettings { mode: SortMode, debug: bool, ignore_blanks: bool, @@ -204,7 +203,7 @@ impl From<&GlobalSettings> for KeySettings { } } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Clone)] /// Represents the string selected by a FieldSelector. enum SelectionRange { /// If we had to transform this selection, we have to store a new string. @@ -236,7 +235,7 @@ impl SelectionRange { } } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Clone)] enum NumCache { AsF64(GeneralF64ParseResult), WithInfo(NumInfo), @@ -257,7 +256,8 @@ impl NumCache { } } } -#[derive(Serialize, Deserialize, Clone)] + +#[derive(Clone)] struct Selection { range: SelectionRange, num_cache: NumCache, @@ -272,22 +272,19 @@ impl Selection { type Field = Range; -#[derive(Serialize, Deserialize, Clone)] -struct Line { +#[derive(Clone)] +pub struct Line { line: String, // The common case is not to specify fields. Let's make this fast. selections: SmallVec<[Selection; 1]>, } -impl ExternallySortable for Line { - fn get_size(&self) -> u64 { - // Currently 96 bytes, but that could change, so we get that size here - std::mem::size_of::() as u64 - } -} - impl Line { - fn new(line: String, settings: &GlobalSettings) -> Self { + pub fn estimate_size(&self) -> usize { + self.line.capacity() + self.selections.capacity() * std::mem::size_of::() + } + + pub fn new(line: String, settings: &GlobalSettings) -> Self { let fields = if settings .selectors .iter() @@ -299,7 +296,7 @@ impl Line { None }; - let selections = settings + let selections: SmallVec<[Selection; 1]> = settings .selectors .iter() .map(|selector| { @@ -725,7 +722,7 @@ impl FieldSelector { } struct MergeableFile<'a> { - lines: Lines>>, + lines: Box + 'a>, current_line: Line, settings: &'a GlobalSettings, } @@ -765,11 +762,11 @@ impl<'a> FileMerger<'a> { settings, } } - fn push_file(&mut self, mut lines: Lines>>) { - if let Some(Ok(next_line)) = lines.next() { + fn push_file(&mut self, mut lines: Box + 'a>) { + if let Some(next_line) = lines.next() { let mergeable_file = MergeableFile { lines, - current_line: Line::new(next_line, &self.settings), + current_line: next_line, settings: &self.settings, }; self.heap.push(mergeable_file); @@ -783,11 +780,8 @@ impl<'a> Iterator for FileMerger<'a> { match self.heap.pop() { Some(mut current) => { match current.lines.next() { - Some(Ok(next_line)) => { - let ret = replace( - &mut current.current_line, - Line::new(next_line, &self.settings), - ); + Some(next_line) => { + let ret = replace(&mut current.current_line, next_line); self.heap.push(current); Some(ret) } @@ -1155,90 +1149,108 @@ pub fn uumain(args: impl uucore::Args) -> i32 { exec(files, settings) } -fn exec(files: Vec, settings: GlobalSettings) -> i32 { - let mut lines = Vec::new(); - let mut file_merger = FileMerger::new(&settings); +fn file_to_lines_iter<'a>( + file: &str, + settings: &'a GlobalSettings, +) -> Option + 'a> { + let (reader, _) = match open(file) { + Some(x) => x, + None => return None, + }; - for path in &files { - let (reader, _) = match open(path) { - Some(x) => x, - None => continue, - }; + let buf_reader = BufReader::new(reader); - let buf_reader = BufReader::new(reader); - - if settings.merge { - file_merger.push_file(buf_reader.lines()); - } else if settings.zero_terminated { - for line in buf_reader.split(b'\0').flatten() { - lines.push(Line::new( - std::str::from_utf8(&line) - .expect("Could not parse string from zero terminated input.") + Some( + buf_reader + .split(if settings.zero_terminated { + b'\0' + } else { + b'\n' + }) + .map(move |line| { + Line::new( + std::str::from_utf8(&line.unwrap()) + .expect("input is not valid utf-8") .to_string(), - &settings, - )); - } + settings, + ) + }), + ) +} + +fn output_sorted_lines(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); + } +} + +fn exec(files: Vec, settings: GlobalSettings) -> i32 { + if settings.merge { + let mut file_merger = FileMerger::new(&settings); + for lines in files + .iter() + .filter_map(|file| file_to_lines_iter(file, &settings)) + { + file_merger.push_file(Box::new(lines)); + } + output_sorted_lines(file_merger, &settings); + } else { + let lines = files + .iter() + .filter_map(|file| file_to_lines_iter(file, &settings)) + .flatten(); + + if settings.check { + return exec_check_file(lines, &settings); + } + + // Only use ext_sorter when we need to. + // Probably faster that we don't create + // an owned value each run + if settings.ext_sort { + let sorted_lines = ext_sort(lines, &settings); + output_sorted_lines(sorted_lines, &settings); } else { - for line in buf_reader.lines() { - if let Ok(n) = line { - lines.push(Line::new(n, &settings)); + let mut lines = vec![]; + + // This is duplicated from fn file_to_lines_iter, but using that function directly results in a performance regression. + for (file, _) in files.iter().map(|file| open(file)).flatten() { + let buf_reader = BufReader::new(file); + for line in buf_reader.split(if settings.zero_terminated { + b'\0' } else { - break; + b'\n' + }) { + let string = String::from_utf8(line.unwrap()).unwrap(); + lines.push(Line::new(string, &settings)); } } + + sort_by(&mut lines, &settings); + output_sorted_lines(lines.into_iter(), &settings); } } - if settings.check { - return exec_check_file(&lines, &settings); - } - - // Only use ext_sorter when we need to. - // Probably faster that we don't create - // an owned value each run - if settings.ext_sort { - lines = ext_sort_by(lines, settings.clone()); - } else { - sort_by(&mut lines, &settings); - } - - if settings.merge { - if settings.unique { - print_sorted( - file_merger.dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal), - &settings, - ) - } else { - print_sorted(file_merger, &settings) - } - } else if settings.unique { - print_sorted( - lines - .into_iter() - .dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal), - &settings, - ) - } else { - print_sorted(lines.into_iter(), &settings) - } - 0 } -fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { +fn exec_check_file(unwrapped_lines: impl Iterator, settings: &GlobalSettings) -> i32 { // errors yields the line before each disorder, // plus the last line (quirk of .coalesce()) - let mut errors = - unwrapped_lines - .iter() - .enumerate() - .coalesce(|(last_i, last_line), (i, line)| { - if compare_by(&last_line, &line, &settings) == Ordering::Greater { - Err(((last_i, last_line), (i, line))) - } else { - Ok((i, line)) - } - }); + let mut errors = unwrapped_lines + .enumerate() + .coalesce(|(last_i, last_line), (i, line)| { + if compare_by(&last_line, &line, &settings) == Ordering::Greater { + Err(((last_i, last_line), (i, line))) + } else { + Ok((i, line)) + } + }); if let Some((first_error_index, _line)) = errors.next() { // Check for a second "error", as .coalesce() always returns the last // line, no matter what our merging function does. @@ -1257,20 +1269,6 @@ fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { } } -fn ext_sort_by(unsorted: Vec, settings: GlobalSettings) -> Vec { - let external_sorter = ExternalSorter::new( - settings.buffer_size as u64, - Some(settings.tmp_dir.clone()), - settings.clone(), - ); - let iter = external_sorter - .sort_by(unsorted.into_iter(), settings.clone()) - .unwrap() - .map(|x| x.unwrap()) - .collect::>(); - iter -} - fn sort_by(unsorted: &mut Vec, settings: &GlobalSettings) { if settings.stable || settings.unique { unsorted.par_sort_by(|a, b| compare_by(a, b, &settings)) @@ -1375,7 +1373,7 @@ fn get_leading_gen(input: &str) -> Range { leading_whitespace_len..input.len() } -#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, PartialOrd)] +#[derive(Copy, Clone, PartialEq, PartialOrd)] enum GeneralF64ParseResult { Invalid, NaN, From 11f387dc93f4d0f218870cc63ad1f1d5d4a80532 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 1 May 2021 17:29:00 +0200 Subject: [PATCH 18/71] ls: fix style test --- tests/by-util/test_ls.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 2b8f311d1..79e26f200 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -584,7 +584,6 @@ fn test_ls_order_birthtime() { } #[test] -#[ignore] fn test_ls_styles() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -598,7 +597,7 @@ fn test_ls_styles() { Regex::new(r"[a-z-]* \d* \w* \w* \d* \d{4}-\d{2}-\d{2} \d{2}:\d{2} test\n").unwrap(); let re_iso = Regex::new(r"[a-z-]* \d* \w* \w* \d* \d{2}-\d{2} \d{2}:\d{2} test\n").unwrap(); let re_locale = - Regex::new(r"[a-z-]* \d* \w* \w* \d* [A-Z][a-z]{2} \d{2} \d{2}:\d{2} test\n").unwrap(); + Regex::new(r"[a-z-]* \d* \w* \w* \d* [A-Z][a-z]{2} ( |\d)\d \d{2}:\d{2} test\n").unwrap(); //full-iso let result = scene From 5567f32f5811a7b48a83f4e57ba284a35b2d199a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 17:49:45 +0200 Subject: [PATCH 19/71] refresh cargo.lock with recent updates --- Cargo.lock | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33d599762..c988d6d12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,9 +106,9 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" dependencies = [ "lazy_static", "memchr 2.4.0", @@ -1277,9 +1277,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a068b905b8cb93815aa3069ae48653d90f382308aebd1d33d940ac1f1d771e2d" +checksum = "1efb2352a0f4d4b128f734b5c44c79ff80117351138733f12f982fe3e2b13343" dependencies = [ "aho-corasick", "memchr 2.4.0", @@ -2247,6 +2247,7 @@ dependencies = [ name = "uu_pinky" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] From 117e84eed3adabb54fa852030712848f08c319f1 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sat, 1 May 2021 18:46:13 +0200 Subject: [PATCH 20/71] tr: implement complement separately from delete or squeeze (#2147) --- src/uu/tr/src/tr.rs | 34 ++++++++++++++++++++++++---------- tests/by-util/test_tr.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 6c1c0746a..f8fabd69f 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -125,10 +125,17 @@ impl SymbolTranslator for DeleteAndSqueezeOperation { struct TranslateOperation { translate_map: FnvHashMap, + complement: bool, + s2_last: char, } impl TranslateOperation { - fn new(set1: ExpandSet, set2: &mut ExpandSet, truncate: bool) -> TranslateOperation { + fn new( + set1: ExpandSet, + set2: &mut ExpandSet, + truncate: bool, + complement: bool, + ) -> TranslateOperation { let mut map = FnvHashMap::default(); let mut s2_prev = '_'; for i in set1 { @@ -141,13 +148,25 @@ impl TranslateOperation { map.insert(i as usize, s2_prev); } } - TranslateOperation { translate_map: map } + TranslateOperation { + translate_map: map, + complement, + s2_last: s2_prev, + } } } impl SymbolTranslator for TranslateOperation { fn translate(&self, c: char, _prev_c: char) -> Option { - Some(*self.translate_map.get(&(c as usize)).unwrap_or(&c)) + if self.complement { + Some(if self.translate_map.contains_key(&(c as usize)) { + c + } else { + self.s2_last + }) + } else { + Some(*self.translate_map.get(&(c as usize)).unwrap_or(&c)) + } } } @@ -256,11 +275,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return 1; } - if complement_flag && !delete_flag && !squeeze_flag { - show_error!("-c is only supported with -d or -s"); - return 1; - } - let stdin = stdin(); let mut locked_stdin = stdin.lock(); let stdout = stdout(); @@ -282,8 +296,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { translate_input(&mut locked_stdin, &mut buffered_stdout, op); } else { let mut set2 = ExpandSet::new(sets[1].as_ref()); - let op = TranslateOperation::new(set1, &mut set2, truncate_flag); - translate_input(&mut locked_stdin, &mut buffered_stdout, op) + let op = TranslateOperation::new(set1, &mut set2, truncate_flag, complement_flag); + translate_input(&mut locked_stdin, &mut buffered_stdout, op); } 0 diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 630c305c6..637c9b109 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -45,6 +45,33 @@ fn test_delete_complement() { .stdout_is("ac"); } +#[test] +fn test_complement1() { + new_ucmd!() + .args(&["-c", "a", "X"]) + .pipe_in("ab") + .run() + .stdout_is("aX"); +} + +#[test] +fn test_complement2() { + new_ucmd!() + .args(&["-c", "0-9", "x"]) + .pipe_in("Phone: 01234 567890") + .run() + .stdout_is("xxxxxxx01234x567890"); +} + +#[test] +fn test_complement3() { + new_ucmd!() + .args(&["-c", "abcdefgh", "123"]) + .pipe_in("the cat and the bat") + .run() + .stdout_is("3he3ca33a3d33he3ba3"); +} + #[test] fn test_squeeze() { new_ucmd!() From 5674d093275ea9ddc3be9ffbb3627eb5d6cae3a1 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 1 May 2021 13:01:55 -0400 Subject: [PATCH 21/71] fixup! tr: implement translate and squeeze (-s) mode --- src/uu/tr/src/tr.rs | 89 +++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 09c4304a5..e04737a45 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -151,6 +151,35 @@ impl SymbolTranslator for TranslateOperation { } } +struct TranslateAndSqueezeOperation { + translate: TranslateOperation, + squeeze: SqueezeOperation, +} + +impl TranslateAndSqueezeOperation { + fn new( + set1: ExpandSet, + set2: &mut ExpandSet, + set2_: ExpandSet, + truncate: bool, + complement: bool, + ) -> TranslateAndSqueezeOperation { + TranslateAndSqueezeOperation { + translate: TranslateOperation::new(set1, set2, truncate), + squeeze: SqueezeOperation::new(set2_, complement), + } + } +} + +impl SymbolTranslator for TranslateAndSqueezeOperation { + fn translate(&self, c: char, prev_c: char) -> Option { + // `unwrap()` will never panic because `Translate.translate()` + // always returns `Some`. + self.squeeze + .translate(self.translate.translate(c, 0 as char).unwrap(), prev_c) + } +} + fn translate_input( input: &mut dyn BufRead, output: &mut dyn Write, @@ -168,8 +197,11 @@ fn translate_input( // isolation to make borrow checker happy let filtered = buf.chars().filter_map(|c| { let res = translator.translate(c, prev_c); + // Set `prev_c` to the post-translate character. This + // allows the squeeze operation to correctly function + // after the translate operation. if res.is_some() { - prev_c = c; + prev_c = res.unwrap(); } res }); @@ -282,53 +314,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let op = SqueezeOperation::new(set1, complement_flag); translate_input(&mut locked_stdin, &mut buffered_stdout, op); } else { - // Define a closure that computes the translation using a hash map. - // - // The `unwrap()` should never panic because the - // `TranslateOperation.translate()` method always returns - // `Some`. let mut set2 = ExpandSet::new(sets[1].as_ref()); - let translator = TranslateOperation::new(set1, &mut set2, truncate_flag); - let translate = |c| translator.translate(c, 0 as char).unwrap(); - - // Prepare some variables to be used for the closure that - // computes the squeeze operation. - // - // The `squeeze()` closure needs to be defined anew for - // each line of input, but these variables do not change - // while reading the input so they can be defined before - // the `while` loop. - let set2 = ExpandSet::new(sets[1].as_ref()); - let squeezer = SqueezeOperation::new(set2, complement_flag); - - // Prepare some memory to read each line of the input (`buf`) and to write - let mut buf = String::with_capacity(BUFFER_LEN + 4); - - // Loop over each line of stdin. - while let Ok(length) = locked_stdin.read_line(&mut buf) { - if length == 0 { - break; - } - - // Define a closure that computes the squeeze operation. - // - // We keep track of the previously seen character on - // each call to `squeeze()`, but we need to reset the - // `prev_c` variable at the beginning of each line of - // the input. That's why we define the closure inside - // the `while` loop. - let mut prev_c = 0 as char; - let squeeze = |c| { - let result = squeezer.translate(c, prev_c); - prev_c = c; - result - }; - - // First translate, then squeeze each character of the input line. - let filtered: String = buf.chars().map(translate).filter_map(squeeze).collect(); - buf.clear(); - buffered_stdout.write_all(filtered.as_bytes()).unwrap(); - } + let set2_ = ExpandSet::new(sets[1].as_ref()); + let op = TranslateAndSqueezeOperation::new( + set1, + &mut set2, + set2_, + complement_flag, + truncate_flag, + ); + translate_input(&mut locked_stdin, &mut buffered_stdout, op); } } else { let mut set2 = ExpandSet::new(sets[1].as_ref()); From 05b20c32a996e56c9ced87e520e7a23cd19358af Mon Sep 17 00:00:00 2001 From: Ricardo Iglesias Date: Wed, 28 Apr 2021 00:36:27 -0700 Subject: [PATCH 22/71] base64: Moved argument parsing to clap. Moved argument parsing to clap and added tests to cover using "-" as stdin, passing in too many file arguments, and updated the "wrap" error message in the tests. --- Cargo.lock | 1 + src/uu/base64/Cargo.toml | 1 + src/uu/base64/src/base64.rs | 139 +++++++++++++++++++++++-- src/uu/base64/src/base_common.rs | 61 +---------- tests/by-util/test_base64.rs | 40 ++++++- tests/fixtures/base64/input-simple.txt | 1 + 6 files changed, 171 insertions(+), 72 deletions(-) create mode 100644 tests/fixtures/base64/input-simple.txt diff --git a/Cargo.lock b/Cargo.lock index c988d6d12..254ae8fda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1688,6 +1688,7 @@ dependencies = [ name = "uu_base64" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index 841ab140c..97241a571 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/base64.rs" [dependencies] +clap = "2.33" 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/src/base64.rs b/src/uu/base64/src/base64.rs index 61a8dc5cb..62d36cc5f 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -8,12 +8,19 @@ #[macro_use] extern crate uucore; + use uucore::encoding::Format; use uucore::InvalidEncodingHandling; +use std::fs::File; +use std::io::{stdin, BufReader}; +use std::path::Path; + +use clap::{App, Arg}; + mod base_common; -static SYNTAX: &str = "[OPTION]... [FILE]"; +static BASE64_ARG_ERROR: i32 = 1; static SUMMARY: &str = "Base64 encode or decode FILE, or standard input, to standard output."; static LONG_HELP: &str = " With no FILE, or when FILE is -, read standard input. @@ -24,14 +31,128 @@ static LONG_HELP: &str = " to attempt to recover from any other non-alphabet bytes in the encoded stream. "; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn get_usage() -> String { + format!("{0} [OPTION]... [FILE]", executable!()) +} + +pub mod options { + pub static DECODE: &str = "decode"; + pub static WRAP: &str = "wrap"; + pub static IGNORE_GARBAGE: &str = "ignore-garbage"; + pub static FILE: &str = "file"; +} + +struct Config { + decode: bool, + ignore_garbage: bool, + wrap_cols: Option, + to_read: Option, +} + +impl Config { + fn from(options: clap::ArgMatches) -> Config { + let file: Option = match options.values_of(options::FILE) { + Some(mut values) => { + let name = values.next().unwrap(); + if values.len() != 0 { + crash!(BASE64_ARG_ERROR, "extra operand ‘{}’", name); + } + + if name == "-" { + None + } else { + if !Path::exists(Path::new(name)) { + crash!(BASE64_ARG_ERROR, "{}: No such file or directory", name); + } + Some(name.to_owned()) + } + } + None => None, + }; + + let cols = match options.value_of(options::WRAP) { + Some(num) => match num.parse::() { + Ok(n) => Some(n), + Err(e) => { + crash!(BASE64_ARG_ERROR, "invalid wrap size: ‘{}’: {}", num, e); + } + }, + None => None, + }; + + Config { + decode: options.is_present(options::DECODE), + ignore_garbage: options.is_present(options::IGNORE_GARBAGE), + wrap_cols: cols, + to_read: file, + } + } +} pub fn uumain(args: impl uucore::Args) -> i32 { - base_common::execute( - args.collect_str(InvalidEncodingHandling::Ignore) - .accept_any(), - SYNTAX, - SUMMARY, - LONG_HELP, - Format::Base64, - ) + let format = Format::Base64; + let usage = get_usage(); + let app = App::new(executable!()) + .version(VERSION) + .about(SUMMARY) + .usage(&usage[..]) + .about(LONG_HELP) + // Format arguments. + .arg( + Arg::with_name(options::DECODE) + .short("d") + .long(options::DECODE) + .help("decode data"), + ) + .arg( + Arg::with_name(options::IGNORE_GARBAGE) + .short("i") + .long(options::IGNORE_GARBAGE) + .help("when decoding, ignore non-alphabetic characters"), + ) + .arg( + Arg::with_name(options::WRAP) + .short("w") + .long(options::WRAP) + .takes_value(true) + .help( + "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", + ), + ) + // "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(); + let config: Config = Config::from(app.get_matches_from(arg_list)); + match config.to_read { + // Read from file. + Some(name) => { + let file_buf = safe_unwrap!(File::open(Path::new(&name))); + let mut input = BufReader::new(file_buf); + base_common::handle_input( + &mut input, + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + ); + } + // stdin + None => { + base_common::handle_input( + &mut stdin().lock(), + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + ); + } + }; + + 0 } diff --git a/src/uu/base64/src/base_common.rs b/src/uu/base64/src/base_common.rs index 3f1436fb2..a4b49e499 100644 --- a/src/uu/base64/src/base_common.rs +++ b/src/uu/base64/src/base_common.rs @@ -7,68 +7,11 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -use std::fs::File; -use std::io::{stdin, stdout, BufReader, Read, Write}; -use std::path::Path; +use std::io::{stdout, Read, Write}; use uucore::encoding::{wrap_print, Data, Format}; -pub fn execute( - args: Vec, - syntax: &str, - summary: &str, - long_help: &str, - format: Format, -) -> i32 { - let matches = app!(syntax, summary, long_help) - .optflag("d", "decode", "decode data") - .optflag( - "i", - "ignore-garbage", - "when decoding, ignore non-alphabetic characters", - ) - .optopt( - "w", - "wrap", - "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", - "COLS", - ) - .parse(args); - - let line_wrap = matches.opt_str("wrap").map(|s| match s.parse() { - Ok(n) => n, - Err(e) => { - crash!(1, "invalid wrap size: ‘{}’: {}", s, e); - } - }); - let ignore_garbage = matches.opt_present("ignore-garbage"); - let decode = matches.opt_present("decode"); - - if matches.free.len() > 1 { - show_usage_error!("extra operand ‘{}’", matches.free[0]); - return 1; - } - - if matches.free.is_empty() || &matches.free[0][..] == "-" { - let stdin_raw = stdin(); - handle_input( - &mut stdin_raw.lock(), - format, - line_wrap, - ignore_garbage, - decode, - ); - } else { - let path = Path::new(matches.free[0].as_str()); - let file_buf = safe_unwrap!(File::open(&path)); - let mut input = BufReader::new(file_buf); - handle_input(&mut input, format, line_wrap, ignore_garbage, decode); - }; - - 0 -} - -fn handle_input( +pub fn handle_input( input: &mut R, format: Format, line_wrap: Option, diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index 6bc0436c5..c9adc5c0f 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -7,6 +7,21 @@ fn test_encode() { .pipe_in(input) .succeeds() .stdout_only("aGVsbG8sIHdvcmxkIQ==\n"); + + // Using '-' as our file + new_ucmd!() + .arg("-") + .pipe_in(input) + .succeeds() + .stdout_only("aGVsbG8sIHdvcmxkIQ==\n"); +} + +#[test] +fn test_base64_encode_file() { + new_ucmd!() + .arg("input-simple.txt") + .succeeds() + .stdout_only("SGVsbG8sIFdvcmxkIQo=\n"); } #[test] @@ -60,10 +75,9 @@ fn test_wrap() { #[test] fn test_wrap_no_arg() { for wrap_param in vec!["-w", "--wrap"] { - new_ucmd!().arg(wrap_param).fails().stderr_only(format!( - "base64: error: Argument to option '{}' missing\n", - if wrap_param == "-w" { "w" } else { "wrap" } - )); + new_ucmd!().arg(wrap_param).fails().stderr_contains( + &"The argument '--wrap ' requires a value but none was supplied", + ); } } @@ -77,3 +91,21 @@ fn test_wrap_bad_arg() { .stderr_only("base64: error: invalid wrap size: ‘b’: invalid digit found in string\n"); } } + +#[test] +fn test_base64_extra_operand() { + // Expect a failure when multiple files are specified. + new_ucmd!() + .arg("a.txt") + .arg("a.txt") + .fails() + .stderr_only("base64: error: extra operand ‘a.txt’"); +} + +#[test] +fn test_base64_file_not_found() { + new_ucmd!() + .arg("a.txt") + .fails() + .stderr_only("base64: error: a.txt: No such file or directory"); +} diff --git a/tests/fixtures/base64/input-simple.txt b/tests/fixtures/base64/input-simple.txt new file mode 100644 index 000000000..8ab686eaf --- /dev/null +++ b/tests/fixtures/base64/input-simple.txt @@ -0,0 +1 @@ +Hello, World! From f307de22d0636247c7cfbb4b6db88ea29971a7d6 Mon Sep 17 00:00:00 2001 From: Ricardo Iglesias Date: Thu, 29 Apr 2021 01:59:43 -0700 Subject: [PATCH 23/71] base64: Refactor argument parsing Moved most of the argument parsing logic to `base32/base_common.rs` to allow for significant code reuse. --- Cargo.lock | 1 + src/uu/base32/src/base32.rs | 152 +++++++------------------------ src/uu/base32/src/base_common.rs | 125 ++++++++++++++++++++++++- src/uu/base64/Cargo.toml | 1 + src/uu/base64/src/base64.rs | 151 ++++++------------------------ src/uu/base64/src/base_common.rs | 40 -------- tests/by-util/test_base32.rs | 2 +- tests/by-util/test_base64.rs | 2 +- 8 files changed, 187 insertions(+), 287 deletions(-) delete mode 100644 src/uu/base64/src/base_common.rs diff --git a/Cargo.lock b/Cargo.lock index 254ae8fda..54ddbd88e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1689,6 +1689,7 @@ name = "uu_base64" version = "0.0.6" dependencies = [ "clap", + "uu_base32", "uucore", "uucore_procs", ] diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 6d9f7f08f..f0e187c31 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -7,19 +7,14 @@ #[macro_use] extern crate uucore; + +use std::io::{stdin, Read}; + use uucore::encoding::Format; -use uucore::InvalidEncodingHandling; -use std::fs::File; -use std::io::{stdin, BufReader}; -use std::path::Path; +pub mod base_common; -use clap::{App, Arg}; - -mod base_common; - -static SUMMARY: &str = "Base32 encode or decode FILE, or standard input, to standard output."; -static LONG_HELP: &str = " +static ABOUT: &str = " With no FILE, or when FILE is -, read standard input. The data are encoded as described for the base32 alphabet in RFC @@ -30,126 +25,41 @@ static LONG_HELP: &str = " "; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static BASE_CMD_PARSE_ERROR: i32 = 1; + fn get_usage() -> String { format!("{0} [OPTION]... [FILE]", executable!()) } -pub mod options { - pub static DECODE: &str = "decode"; - pub static WRAP: &str = "wrap"; - pub static IGNORE_GARBAGE: &str = "ignore-garbage"; - pub static FILE: &str = "file"; -} - -struct Config { - decode: bool, - ignore_garbage: bool, - wrap_cols: Option, - to_read: Option, -} - -impl Config { - fn from(options: clap::ArgMatches) -> Config { - let file: Option = match options.values_of(options::FILE) { - Some(mut values) => { - let name = values.next().unwrap(); - if values.len() != 0 { - crash!(3, "extra operand ‘{}’", name); - } - - if name == "-" { - None - } else { - if !Path::exists(Path::new(name)) { - crash!(2, "{}: No such file or directory", name); - } - Some(name.to_owned()) - } - } - None => None, - }; - - let cols = match options.value_of(options::WRAP) { - Some(num) => match num.parse::() { - Ok(n) => Some(n), - Err(e) => { - crash!(1, "invalid wrap size: ‘{}’: {}", num, e); - } - }, - None => None, - }; - - Config { - decode: options.is_present(options::DECODE), - ignore_garbage: options.is_present(options::IGNORE_GARBAGE), - wrap_cols: cols, - to_read: file, - } - } -} - pub fn uumain(args: impl uucore::Args) -> i32 { let format = Format::Base32; let usage = get_usage(); - let app = App::new(executable!()) - .version(VERSION) - .about(SUMMARY) - .usage(&usage[..]) - .about(LONG_HELP) - // Format arguments. - .arg( - Arg::with_name(options::DECODE) - .short("d") - .long(options::DECODE) - .help("decode data"), - ) - .arg( - Arg::with_name(options::IGNORE_GARBAGE) - .short("i") - .long(options::IGNORE_GARBAGE) - .help("when decoding, ignore non-alphabetic characters"), - ) - .arg( - Arg::with_name(options::WRAP) - .short("w") - .long(options::WRAP) - .takes_value(true) - .help( - "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", - ), - ) - // "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 name = executable!(); - let arg_list = args - .collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(); - let config: Config = Config::from(app.get_matches_from(arg_list)); - match config.to_read { - // Read from file. - Some(name) => { - let file_buf = safe_unwrap!(File::open(Path::new(&name))); - let mut input = BufReader::new(file_buf); - base_common::handle_input( - &mut input, - format, - config.wrap_cols, - config.ignore_garbage, - config.decode, - ); + 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), } - // stdin - None => { - base_common::handle_input( - &mut stdin().lock(), - format, - config.wrap_cols, - config.ignore_garbage, - config.decode, - ); - } - }; + } + + // 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( + &mut input, + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + name, + ); 0 } diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index a4b49e499..d0f47f500 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -10,6 +10,122 @@ use std::io::{stdout, Read, Write}; use uucore::encoding::{wrap_print, Data, Format}; +use uucore::InvalidEncodingHandling; + +use std::fs::File; +use std::io::{BufReader, Stdin}; +use std::path::Path; + +use clap::{App, Arg}; + +// Config. +pub struct Config { + pub decode: bool, + pub ignore_garbage: bool, + pub wrap_cols: Option, + pub to_read: Option, +} + +pub mod options { + pub static DECODE: &str = "decode"; + pub static WRAP: &str = "wrap"; + pub static IGNORE_GARBAGE: &str = "ignore-garbage"; + pub static FILE: &str = "file"; +} + +impl Config { + fn from(options: clap::ArgMatches) -> Result { + let file: Option = match options.values_of(options::FILE) { + Some(mut values) => { + let name = values.next().unwrap(); + if values.len() != 0 { + return Err(format!("extra operand ‘{}’", name)); + } + + if name == "-" { + None + } else { + if !Path::exists(Path::new(name)) { + return Err(format!("{}: No such file or directory", name)); + } + Some(name.to_owned()) + } + } + 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, + }; + + Ok(Config { + decode: options.is_present(options::DECODE), + ignore_garbage: options.is_present(options::IGNORE_GARBAGE), + wrap_cols: cols, + to_read: file, + }) + } +} + +pub fn parse_base_cmd_args( + args: impl uucore::Args, + name: &str, + version: &str, + about: &str, + usage: &str, +) -> Result { + let app = App::new(name) + .version(version) + .about(about) + .usage(&usage[..]) + // Format arguments. + .arg( + Arg::with_name(options::DECODE) + .short("d") + .long(options::DECODE) + .help("decode data"), + ) + .arg( + Arg::with_name(options::IGNORE_GARBAGE) + .short("i") + .long(options::IGNORE_GARBAGE) + .help("when decoding, ignore non-alphabetic characters"), + ) + .arg( + Arg::with_name(options::WRAP) + .short("w") + .long(options::WRAP) + .takes_value(true) + .help( + "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", + ), + ) + // "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)) +} + +pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box { + match &config.to_read { + Some(name) => { + let file_buf = safe_unwrap!(File::open(Path::new(name))); + Box::new(BufReader::new(file_buf)) // as Box + } + None => { + Box::new(stdin_ref.lock()) // as Box + } + } +} pub fn handle_input( input: &mut R, @@ -17,6 +133,7 @@ pub fn handle_input( line_wrap: Option, ignore_garbage: bool, decode: bool, + name: &str, ) { let mut data = Data::new(input, format).ignore_garbage(ignore_garbage); if let Some(wrap) = line_wrap { @@ -31,10 +148,14 @@ pub fn handle_input( Ok(s) => { if stdout().write_all(&s).is_err() { // on windows console, writing invalid utf8 returns an error - crash!(1, "Cannot write non-utf8 data"); + eprintln!("{}: error: Cannot write non-utf8 data", name); + exit!(1) } } - Err(_) => crash!(1, "invalid input"), + Err(_) => { + eprintln!("{}: error: invalid input", name); + exit!(1) + } } } } diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index 97241a571..d4ee69f03 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -18,6 +18,7 @@ path = "src/base64.rs" clap = "2.33" 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"} [[bin]] name = "base64" diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 62d36cc5f..810df4fe8 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -9,20 +9,13 @@ #[macro_use] extern crate uucore; +use uu_base32::base_common; + use uucore::encoding::Format; -use uucore::InvalidEncodingHandling; -use std::fs::File; -use std::io::{stdin, BufReader}; -use std::path::Path; +use std::io::{stdin, Read}; -use clap::{App, Arg}; - -mod base_common; - -static BASE64_ARG_ERROR: i32 = 1; -static SUMMARY: &str = "Base64 encode or decode FILE, or standard input, to standard output."; -static LONG_HELP: &str = " +static ABOUT: &str = " With no FILE, or when FILE is -, read standard input. The data are encoded as described for the base64 alphabet in RFC @@ -33,126 +26,40 @@ static LONG_HELP: &str = " "; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static BASE_CMD_PARSE_ERROR: i32 = 1; + fn get_usage() -> String { format!("{0} [OPTION]... [FILE]", executable!()) } -pub mod options { - pub static DECODE: &str = "decode"; - pub static WRAP: &str = "wrap"; - pub static IGNORE_GARBAGE: &str = "ignore-garbage"; - pub static FILE: &str = "file"; -} - -struct Config { - decode: bool, - ignore_garbage: bool, - wrap_cols: Option, - to_read: Option, -} - -impl Config { - fn from(options: clap::ArgMatches) -> Config { - let file: Option = match options.values_of(options::FILE) { - Some(mut values) => { - let name = values.next().unwrap(); - if values.len() != 0 { - crash!(BASE64_ARG_ERROR, "extra operand ‘{}’", name); - } - - if name == "-" { - None - } else { - if !Path::exists(Path::new(name)) { - crash!(BASE64_ARG_ERROR, "{}: No such file or directory", name); - } - Some(name.to_owned()) - } - } - None => None, - }; - - let cols = match options.value_of(options::WRAP) { - Some(num) => match num.parse::() { - Ok(n) => Some(n), - Err(e) => { - crash!(BASE64_ARG_ERROR, "invalid wrap size: ‘{}’: {}", num, e); - } - }, - None => None, - }; - - Config { - decode: options.is_present(options::DECODE), - ignore_garbage: options.is_present(options::IGNORE_GARBAGE), - wrap_cols: cols, - to_read: file, - } - } -} - pub fn uumain(args: impl uucore::Args) -> i32 { let format = Format::Base64; let usage = get_usage(); - let app = App::new(executable!()) - .version(VERSION) - .about(SUMMARY) - .usage(&usage[..]) - .about(LONG_HELP) - // Format arguments. - .arg( - Arg::with_name(options::DECODE) - .short("d") - .long(options::DECODE) - .help("decode data"), - ) - .arg( - Arg::with_name(options::IGNORE_GARBAGE) - .short("i") - .long(options::IGNORE_GARBAGE) - .help("when decoding, ignore non-alphabetic characters"), - ) - .arg( - Arg::with_name(options::WRAP) - .short("w") - .long(options::WRAP) - .takes_value(true) - .help( - "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", - ), - ) - // "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 name = executable!(); + let config_result: Result = + base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); - let arg_list = args - .collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(); - let config: Config = Config::from(app.get_matches_from(arg_list)); - match config.to_read { - // Read from file. - Some(name) => { - let file_buf = safe_unwrap!(File::open(Path::new(&name))); - let mut input = BufReader::new(file_buf); - base_common::handle_input( - &mut input, - format, - config.wrap_cols, - config.ignore_garbage, - config.decode, - ); + if config_result.is_err() { + match config_result { + Ok(_) => panic!(), + Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s), } - // stdin - None => { - base_common::handle_input( - &mut stdin().lock(), - format, - config.wrap_cols, - config.ignore_garbage, - config.decode, - ); - } - }; + } + + // 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( + &mut input, + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + name, + ); 0 } diff --git a/src/uu/base64/src/base_common.rs b/src/uu/base64/src/base_common.rs deleted file mode 100644 index a4b49e499..000000000 --- a/src/uu/base64/src/base_common.rs +++ /dev/null @@ -1,40 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// (c) Jordy Dickinson -// (c) Jian Zeng -// (c) Alex Lyon -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. - -use std::io::{stdout, Read, Write}; - -use uucore::encoding::{wrap_print, Data, Format}; - -pub fn handle_input( - input: &mut R, - format: Format, - line_wrap: Option, - ignore_garbage: bool, - decode: bool, -) { - let mut data = Data::new(input, format).ignore_garbage(ignore_garbage); - if let Some(wrap) = line_wrap { - data = data.line_wrap(wrap); - } - - if !decode { - let encoded = data.encode(); - wrap_print(&data, encoded); - } else { - match data.decode() { - Ok(s) => { - if stdout().write_all(&s).is_err() { - // on windows console, writing invalid utf8 returns an error - crash!(1, "Cannot write non-utf8 data"); - } - } - Err(_) => crash!(1, "invalid input"), - } - } -} diff --git a/tests/by-util/test_base32.rs b/tests/by-util/test_base32.rs index 157503d83..fd49aa951 100644 --- a/tests/by-util/test_base32.rs +++ b/tests/by-util/test_base32.rs @@ -98,7 +98,7 @@ fn test_wrap_bad_arg() { .arg(wrap_param) .arg("b") .fails() - .stderr_only("base32: error: invalid wrap size: ‘b’: invalid digit found in string\n"); + .stderr_only("base32: error: Invalid wrap size: ‘b’: invalid digit found in string\n"); } } diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index c9adc5c0f..8d9dc5639 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -88,7 +88,7 @@ fn test_wrap_bad_arg() { .arg(wrap_param) .arg("b") .fails() - .stderr_only("base64: error: invalid wrap size: ‘b’: invalid digit found in string\n"); + .stderr_only("base64: error: Invalid wrap size: ‘b’: invalid digit found in string\n"); } } From 193ad56c2a5e9a615618de8e74368c8cc2c1baad Mon Sep 17 00:00:00 2001 From: Ricardo Iglesias Date: Thu, 29 Apr 2021 02:07:59 -0700 Subject: [PATCH 24/71] Removed clippy warnings. --- src/uu/base32/src/base_common.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index d0f47f500..ee5fe8675 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -83,7 +83,7 @@ pub fn parse_base_cmd_args( let app = App::new(name) .version(version) .about(about) - .usage(&usage[..]) + .usage(usage) // Format arguments. .arg( Arg::with_name(options::DECODE) From 83554f4475a2c1c9ddbfe33bedc9ef53bafd922d Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 1 May 2021 17:48:01 +0200 Subject: [PATCH 25/71] add benchmarking instructions --- src/uu/sort/BENCHMARKING.md | 6 ++++++ src/uu/sort/src/sort.rs | 6 ++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/uu/sort/BENCHMARKING.md b/src/uu/sort/BENCHMARKING.md index 1caea0326..1810e8a4e 100644 --- a/src/uu/sort/BENCHMARKING.md +++ b/src/uu/sort/BENCHMARKING.md @@ -69,6 +69,12 @@ Run `cargo build --release` before benchmarking after you make a change! - Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers_si.txt -h -o output.txt"`. +## External sorting + +Try running commands with the `-S` option set to an amount of memory to be used, such as `1M`. Additionally, you could try sorting +huge files (ideally multiple Gigabytes) with `-S`. Creating such a large file can be achieved by running `cat shuffled_wordlist.txt | sort -R >> shuffled_wordlist.txt` +multiple times (this will add the contents of `shuffled_wordlist.txt` to itself). + ## Stdout and stdin performance Try to run the above benchmarks by piping the input through stdin (standard input) and redirect the diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 7c053e4ad..be7944a0f 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1169,9 +1169,7 @@ fn file_to_lines_iter<'a>( }) .map(move |line| { Line::new( - std::str::from_utf8(&line.unwrap()) - .expect("input is not valid utf-8") - .to_string(), + crash_if_err!(1, String::from_utf8(crash_if_err!(1, line))), settings, ) }), @@ -1226,7 +1224,7 @@ fn exec(files: Vec, settings: GlobalSettings) -> i32 { } else { b'\n' }) { - let string = String::from_utf8(line.unwrap()).unwrap(); + let string = crash_if_err!(1, String::from_utf8(crash_if_err!(1, line))); lines.push(Line::new(string, &settings)); } } From b21a309c3f75d77b7e1c4f21a145e8ff893509e3 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 1 May 2021 21:29:18 +0200 Subject: [PATCH 26/71] add a benchmarking example --- src/uu/sort/BENCHMARKING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/sort/BENCHMARKING.md b/src/uu/sort/BENCHMARKING.md index 1810e8a4e..17a944b29 100644 --- a/src/uu/sort/BENCHMARKING.md +++ b/src/uu/sort/BENCHMARKING.md @@ -74,6 +74,7 @@ Run `cargo build --release` before benchmarking after you make a change! Try running commands with the `-S` option set to an amount of memory to be used, such as `1M`. Additionally, you could try sorting huge files (ideally multiple Gigabytes) with `-S`. Creating such a large file can be achieved by running `cat shuffled_wordlist.txt | sort -R >> shuffled_wordlist.txt` multiple times (this will add the contents of `shuffled_wordlist.txt` to itself). +Example: Run `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -S 1M"` ## Stdout and stdin performance From 484558e37dbc95ba583a4210205164ef5521655b Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 1 May 2021 21:38:36 +0200 Subject: [PATCH 27/71] Update src/uu/sort/BENCHMARKING.md Co-authored-by: Sylvestre Ledru --- src/uu/sort/BENCHMARKING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/BENCHMARKING.md b/src/uu/sort/BENCHMARKING.md index 17a944b29..71c331105 100644 --- a/src/uu/sort/BENCHMARKING.md +++ b/src/uu/sort/BENCHMARKING.md @@ -74,7 +74,8 @@ Run `cargo build --release` before benchmarking after you make a change! Try running commands with the `-S` option set to an amount of memory to be used, such as `1M`. Additionally, you could try sorting huge files (ideally multiple Gigabytes) with `-S`. Creating such a large file can be achieved by running `cat shuffled_wordlist.txt | sort -R >> shuffled_wordlist.txt` multiple times (this will add the contents of `shuffled_wordlist.txt` to itself). -Example: Run `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -S 1M"` +Example: Run `hyperfine './target/release/coreutils sort shuffled_wordlist.txt -S 1M' 'sort shuffled_wordlist.txt -S 1M'` +` ## Stdout and stdin performance From c3912d53ac1430b718cebbcf724de3af59a06e2e Mon Sep 17 00:00:00 2001 From: Daniel Rocco Date: Thu, 22 Apr 2021 20:29:04 -0400 Subject: [PATCH 28/71] test: add tests for basic tests & edge cases Some edge cases covered: - no args - operator by itself (!, -a, etc.) - string, file tests of nothing - compound negations --- tests/by-util/test_test.rs | 442 ++++++++++++++++++++++++++++- tests/fixtures/test/non_empty_file | 1 + tests/fixtures/test/regular_file | 0 3 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/test/non_empty_file create mode 100644 tests/fixtures/test/regular_file diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 4b9a9ff55..2173371fc 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -2,6 +2,7 @@ // This file is part of the uutils coreutils package. // // (c) mahkoh (ju.orth [at] gmail [dot] com) +// (c) Daniel Rocco // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -9,6 +10,436 @@ use crate::common::util::*; +#[test] +fn test_empty_test_equivalent_to_false() { + new_ucmd!().run().status_code(1); +} + +#[test] +fn test_empty_string_is_false() { + new_ucmd!().arg("").run().status_code(1); +} + +#[test] +fn test_solo_not() { + new_ucmd!().arg("!").succeeds(); +} + +#[test] +fn test_solo_and_or_or_is_a_literal() { + // /bin/test '' -a '' => 1; so test(1) must interpret `-a` by itself as + // a literal string + new_ucmd!().arg("-a").succeeds(); + new_ucmd!().arg("-o").succeeds(); +} + +#[test] +fn test_double_not_is_false() { + new_ucmd!().args(&["!", "!"]).run().status_code(1); +} + +#[test] +#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] +fn test_and_not_is_false() { + new_ucmd!().args(&["-a", "!"]).run().status_code(1); +} + +#[test] +fn test_not_and_is_false() { + // `-a` is a literal here & has nonzero length + new_ucmd!().args(&["!", "-a"]).run().status_code(1); +} + +#[test] +#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 0"] +fn test_not_and_not_succeeds() { + new_ucmd!().args(&["!", "-a", "!"]).succeeds(); +} + +#[test] +fn test_simple_or() { + new_ucmd!().args(&["foo", "-o", ""]).succeeds(); +} + +#[test] +#[ignore = "fixme: parse/evaluation error (code 0); GNU returns 1"] +fn test_negated_or() { + new_ucmd!() + .args(&["!", "foo", "-o", "bar"]) + .run() + .status_code(1); + new_ucmd!().args(&["foo", "-o", "!", "bar"]).succeeds(); + new_ucmd!() + .args(&["!", "foo", "-o", "!", "bar"]) + .run() + .status_code(1); +} + +#[test] +fn test_strlen_of_nothing() { + // odd but matches GNU, which must interpret -n as a literal here + new_ucmd!().arg("-n").succeeds(); +} + +#[test] +fn test_strlen_of_empty() { + new_ucmd!().args(&["-n", ""]).run().status_code(1); + + // STRING equivalent to -n STRING + new_ucmd!().arg("").run().status_code(1); +} + +#[test] +fn test_nothing_is_empty() { + // -z is a literal here and has nonzero length + new_ucmd!().arg("-z").succeeds(); +} + +#[test] +fn test_zero_len_of_empty() { + new_ucmd!().args(&["-z", ""]).succeeds(); +} + +#[test] +fn test_solo_paren_is_literal() { + let scenario = TestScenario::new(util_name!()); + let tests = [["("], [")"]]; + + for test in &tests { + scenario.ucmd().args(&test[..]).succeeds(); + } +} + +#[test] +#[ignore = "GNU considers this an error"] +fn test_solo_empty_parenthetical_is_error() { + new_ucmd!() + .args(&["(", ")"]) + .run() + .status_code(2) + .stderr_is("test: missing argument after ‘)’"); +} + +#[test] +fn test_zero_len_equals_zero_len() { + new_ucmd!().args(&["", "=", ""]).succeeds(); +} + +#[test] +fn test_zero_len_not_equals_zero_len_is_false() { + new_ucmd!().args(&["", "!=", ""]).run().status_code(1); +} + +#[test] +fn test_string_comparison() { + let scenario = TestScenario::new(util_name!()); + let tests = [ + ["foo", "!=", "bar"], + ["contained\nnewline", "=", "contained\nnewline"], + ["(", "=", "("], + ["(", "!=", ")"], + ["!", "=", "!"], + ]; + + for test in &tests { + scenario.ucmd().args(&test[..]).succeeds(); + } +} + +#[test] +#[ignore = "fixme: error reporting"] +fn test_dangling_string_comparison_is_error() { + new_ucmd!() + .args(&["missing_something", "="]) + .run() + .status_code(2) + .stderr_is("test: missing argument after ‘=’"); +} + +#[test] +fn test_stringop_is_literal_after_bang() { + let scenario = TestScenario::new(util_name!()); + let tests = [ + ["!", "="], + ["!", "!="], + ["!", "-eq"], + ["!", "-ne"], + ["!", "-lt"], + ["!", "-le"], + ["!", "-gt"], + ["!", "-ge"], + ["!", "-ef"], + ["!", "-nt"], + ["!", "-ot"], + ]; + + for test in &tests { + scenario.ucmd().args(&test[..]).run().status_code(1); + } +} + +#[test] +fn test_a_bunch_of_not() { + new_ucmd!() + .args(&["!", "", "!=", "", "-a", "!", "", "!=", ""]) + .succeeds(); +} + +#[test] +fn test_pseudofloat_equal() { + new_ucmd!().args(&["123.45", "=", "123.45"]).succeeds(); +} + +#[test] +fn test_pseudofloat_not_equal() { + new_ucmd!().args(&["123.45", "!=", "123.450"]).succeeds(); +} + +#[test] +fn test_negative_arg_is_a_string() { + new_ucmd!().arg("-12345").succeeds(); + new_ucmd!().arg("--qwert").succeeds(); +} + +#[test] +fn test_some_int_compares() { + let scenario = TestScenario::new(util_name!()); + + let tests = [ + ["0", "-eq", "0"], + ["0", "-ne", "1"], + ["421", "-lt", "3720"], + ["0", "-le", "0"], + ["11", "-gt", "10"], + ["1024", "-ge", "512"], + ["9223372036854775806", "-le", "9223372036854775807"], + ]; + + for test in &tests { + scenario.ucmd().args(&test[..]).succeeds(); + } +} + +#[test] +#[ignore = "fixme: evaluation error (code 1); GNU returns 0"] +fn test_values_greater_than_i64_allowed() { + new_ucmd!() + .args(&["9223372036854775808", "-gt", "0"]) + .succeeds(); +} + +#[test] +fn test_negative_int_compare() { + let scenario = TestScenario::new(util_name!()); + + let tests = [ + ["-1", "-eq", "-1"], + ["-1", "-ne", "-2"], + ["-3720", "-lt", "-421"], + ["-10", "-le", "-10"], + ["-21", "-gt", "-22"], + ["-128", "-ge", "-256"], + ["-9223372036854775808", "-le", "-9223372036854775807"], + ]; + + for test in &tests { + scenario.ucmd().args(&test[..]).succeeds(); + } +} + +#[test] +#[ignore = "fixme: error reporting"] +fn test_float_inequality_is_error() { + new_ucmd!() + .args(&["123.45", "-ge", "6"]) + .run() + .status_code(2) + .stderr_is("test: invalid integer ‘123.45’"); +} + +#[test] +#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] +fn test_file_is_itself() { + new_ucmd!() + .args(&["regular_file", "-ef", "regular_file"]) + .succeeds(); +} + +#[test] +#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] +fn test_file_is_newer_than_and_older_than_itself() { + // odd but matches GNU + new_ucmd!() + .args(&["regular_file", "-nt", "regular_file"]) + .run() + .status_code(1); + new_ucmd!() + .args(&["regular_file", "-ot", "regular_file"]) + .run() + .status_code(1); +} + +#[test] +#[ignore = "todo: implement these"] +fn test_newer_file() { + let scenario = TestScenario::new(util_name!()); + + scenario.cmd("touch").arg("newer_file").succeeds(); + scenario + .cmd("touch") + .args(&["-m", "-d", "last Thursday", "regular_file"]) + .succeeds(); + + scenario + .ucmd() + .args(&["newer_file", "-nt", "regular_file"]) + .succeeds(); + scenario + .ucmd() + .args(&["regular_file", "-ot", "newer_file"]) + .succeeds(); +} + +#[test] +fn test_file_exists() { + new_ucmd!().args(&["-e", "regular_file"]).succeeds(); +} + +#[test] +fn test_nonexistent_file_does_not_exist() { + new_ucmd!() + .args(&["-e", "nonexistent_file"]) + .run() + .status_code(1); +} + +#[test] +fn test_nonexistent_file_is_not_regular() { + new_ucmd!() + .args(&["-f", "nonexistent_file"]) + .run() + .status_code(1); +} + +#[test] +fn test_file_exists_and_is_regular() { + new_ucmd!().args(&["-f", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] // FIXME: implement on Windows +fn test_file_is_readable() { + new_ucmd!().args(&["-r", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] // FIXME: implement on Windows +fn test_file_is_not_readable() { + let scenario = TestScenario::new(util_name!()); + let mut ucmd = scenario.ucmd(); + let mut chmod = scenario.cmd("chmod"); + + scenario.fixtures.touch("crypto_file"); + chmod.args(&["u-r", "crypto_file"]).succeeds(); + + ucmd.args(&["!", "-r", "crypto_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] // FIXME: implement on Windows +fn test_file_is_writable() { + new_ucmd!().args(&["-w", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] // FIXME: implement on Windows +fn test_file_is_not_writable() { + let scenario = TestScenario::new(util_name!()); + let mut ucmd = scenario.ucmd(); + let mut chmod = scenario.cmd("chmod"); + + scenario.fixtures.touch("immutable_file"); + chmod.args(&["u-w", "immutable_file"]).succeeds(); + + ucmd.args(&["!", "-w", "immutable_file"]).succeeds(); +} + +#[test] +fn test_file_is_not_executable() { + new_ucmd!().args(&["!", "-x", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] // FIXME: implement on Windows +fn test_file_is_executable() { + let scenario = TestScenario::new(util_name!()); + let mut chmod = scenario.cmd("chmod"); + + chmod.args(&["u+x", "regular_file"]).succeeds(); + + scenario.ucmd().args(&["-x", "regular_file"]).succeeds(); +} + +#[test] +fn test_is_not_empty() { + new_ucmd!().args(&["-s", "non_empty_file"]).succeeds(); +} + +#[test] +fn test_nonexistent_file_size_test_is_false() { + new_ucmd!() + .args(&["-s", "nonexistent_file"]) + .run() + .status_code(1); +} + +#[test] +fn test_not_is_not_empty() { + new_ucmd!().args(&["!", "-s", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] +fn test_symlink_is_symlink() { + let scenario = TestScenario::new(util_name!()); + let mut ln = scenario.cmd("ln"); + + // creating symlinks requires admin on Windows + ln.args(&["-s", "regular_file", "symlink"]).succeeds(); + + // FIXME: implement on Windows + scenario.ucmd().args(&["-h", "symlink"]).succeeds(); + scenario.ucmd().args(&["-L", "symlink"]).succeeds(); +} + +#[test] +fn test_file_is_not_symlink() { + let scenario = TestScenario::new(util_name!()); + + scenario + .ucmd() + .args(&["!", "-h", "regular_file"]) + .succeeds(); + scenario + .ucmd() + .args(&["!", "-L", "regular_file"]) + .succeeds(); +} + +#[test] +fn test_nonexistent_file_is_not_symlink() { + let scenario = TestScenario::new(util_name!()); + + scenario + .ucmd() + .args(&["!", "-h", "nonexistent_file"]) + .succeeds(); + scenario + .ucmd() + .args(&["!", "-L", "nonexistent_file"]) + .succeeds(); +} + #[test] fn test_op_prec_and_or_1() { new_ucmd!().args(&[" ", "-o", "", "-a", ""]).succeeds(); @@ -23,5 +454,14 @@ fn test_op_prec_and_or_2() { #[test] fn test_or_as_filename() { - new_ucmd!().args(&["x", "-a", "-z", "-o"]).fails(); + new_ucmd!() + .args(&["x", "-a", "-z", "-o"]) + .run() + .status_code(1); +} + +#[test] +#[ignore = "GNU considers this an error"] +fn test_strlen_and_nothing() { + new_ucmd!().args(&["-n", "a", "-a"]).run().status_code(2); } diff --git a/tests/fixtures/test/non_empty_file b/tests/fixtures/test/non_empty_file new file mode 100644 index 000000000..34425caf6 --- /dev/null +++ b/tests/fixtures/test/non_empty_file @@ -0,0 +1 @@ +Not empty! diff --git a/tests/fixtures/test/regular_file b/tests/fixtures/test/regular_file new file mode 100644 index 000000000..e69de29bb From 3c126bad72f9f26245b4aad02b1267c38b248032 Mon Sep 17 00:00:00 2001 From: Daniel Rocco Date: Mon, 26 Apr 2021 21:27:29 -0400 Subject: [PATCH 29/71] test: implement parenthesized expressions, additional tests - Replace the parser with a recursive descent implementation that handles parentheses and produces a stack of operations in postfix order. Parsing now operates directly on OsStrings passed by the uucore framework. - Replace the dispatch mechanism with a stack machine operating on the symbol stack produced by the parser. - Add tests for parenthesized expressions. - Begin testing character encoding handling. --- src/uu/test/src/parser.rs | 292 +++++++++++++++++++++++++ src/uu/test/src/test.rs | 436 ++++++++++++------------------------- tests/by-util/test_test.rs | 84 ++++++- 3 files changed, 504 insertions(+), 308 deletions(-) create mode 100644 src/uu/test/src/parser.rs diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs new file mode 100644 index 000000000..f1ca9dad6 --- /dev/null +++ b/src/uu/test/src/parser.rs @@ -0,0 +1,292 @@ +// This file is part of the uutils coreutils package. +// +// (c) Daniel Rocco +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use std::ffi::OsString; +use std::iter::Peekable; + +/// Represents a parsed token from a test expression +#[derive(Debug, PartialEq)] +pub enum Symbol { + LParen, + Bang, + BoolOp(OsString), + Literal(OsString), + StringOp(OsString), + IntOp(OsString), + FileOp(OsString), + StrlenOp(OsString), + FiletestOp(OsString), + None, +} + +impl Symbol { + /// Create a new Symbol from an OsString. + /// + /// Returns Symbol::None in place of None + fn new(token: Option) -> Symbol { + match token { + Some(s) => match s.to_string_lossy().as_ref() { + "(" => Symbol::LParen, + "!" => Symbol::Bang, + "-a" | "-o" => Symbol::BoolOp(s), + "=" | "!=" => Symbol::StringOp(s), + "-eq" | "-ge" | "-gt" | "-le" | "-lt" | "-ne" => Symbol::IntOp(s), + "-ef" | "-nt" | "-ot" => Symbol::FileOp(s), + "-n" | "-z" => Symbol::StrlenOp(s), + "-b" | "-c" | "-d" | "-e" | "-f" | "-g" | "-G" | "-h" | "-k" | "-L" | "-O" + | "-p" | "-r" | "-s" | "-S" | "-t" | "-u" | "-w" | "-x" => Symbol::FiletestOp(s), + _ => Symbol::Literal(s), + }, + None => Symbol::None, + } + } + + /// Convert this Symbol into a Symbol::Literal, useful for cases where + /// test treats an operator as a string operand (test has no reserved + /// words). + /// + /// # Panics + /// + /// Panics if `self` is Symbol::None + fn into_literal(self) -> Symbol { + Symbol::Literal(match self { + Symbol::LParen => OsString::from("("), + Symbol::Bang => OsString::from("!"), + Symbol::BoolOp(s) + | Symbol::Literal(s) + | Symbol::StringOp(s) + | Symbol::IntOp(s) + | Symbol::FileOp(s) + | Symbol::StrlenOp(s) + | Symbol::FiletestOp(s) => s, + Symbol::None => panic!(), + }) + } +} + +/// Recursive descent parser for test, which converts a list of OsStrings +/// (typically command line arguments) into a stack of Symbols in postfix +/// order. +/// +/// Grammar: +/// +/// EXPR → TERM | EXPR BOOLOP EXPR +/// TERM → ( EXPR ) +/// TERM → ( ) +/// TERM → ! EXPR +/// TERM → UOP str +/// UOP → STRLEN | FILETEST +/// TERM → str OP str +/// TERM → str | 𝜖 +/// OP → STRINGOP | INTOP | FILEOP +/// STRINGOP → = | != +/// INTOP → -eq | -ge | -gt | -le | -lt | -ne +/// FILEOP → -ef | -nt | -ot +/// STRLEN → -n | -z +/// FILETEST → -b | -c | -d | -e | -f | -g | -G | -h | -k | -L | -O | -p | +/// -r | -s | -S | -t | -u | -w | -x +/// BOOLOP → -a | -o +/// +#[derive(Debug)] +struct Parser { + tokens: Peekable>, + pub stack: Vec, +} + +impl Parser { + /// Construct a new Parser from a `Vec` of tokens. + fn new(tokens: Vec) -> Parser { + Parser { + tokens: tokens.into_iter().peekable(), + stack: vec![], + } + } + + /// Fetch the next token from the input stream as a Symbol. + fn next_token(&mut self) -> Symbol { + Symbol::new(self.tokens.next()) + } + + /// Peek at the next token from the input stream, returning it as a Symbol. + /// The stream is unchanged and will return the same Symbol on subsequent + /// calls to `next()` or `peek()`. + fn peek(&mut self) -> Symbol { + Symbol::new(self.tokens.peek().map(|s| s.to_os_string())) + } + + /// Test if the next token in the stream is a BOOLOP (-a or -o), without + /// removing the token from the stream. + fn peek_is_boolop(&mut self) -> bool { + if let Symbol::BoolOp(_) = self.peek() { + true + } else { + false + } + } + + /// Parse an expression. + /// + /// EXPR → TERM | EXPR BOOLOP EXPR + fn expr(&mut self) { + if !self.peek_is_boolop() { + self.term(); + } + self.maybe_boolop(); + } + + /// Parse a term token and possible subsequent symbols: "(", "!", UOP, + /// literal, or None. + fn term(&mut self) { + let symbol = self.next_token(); + + match symbol { + Symbol::LParen => self.lparen(), + Symbol::Bang => self.bang(), + Symbol::StrlenOp(_) => self.uop(symbol), + Symbol::FiletestOp(_) => self.uop(symbol), + Symbol::None => self.stack.push(symbol), + literal => self.literal(literal), + } + } + + /// Parse a (possibly) parenthesized expression. + /// + /// test has no reserved keywords, so "(" will be interpreted as a literal + /// if it is followed by nothing or a comparison operator OP. + fn lparen(&mut self) { + match self.peek() { + // lparen is a literal when followed by nothing or comparison + Symbol::None | Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) => { + self.literal(Symbol::Literal(OsString::from("("))); + } + // empty parenthetical + Symbol::Literal(s) if s == ")" => {} + _ => { + self.expr(); + match self.next_token() { + Symbol::Literal(s) if s == ")" => (), + _ => panic!("expected ‘)’"), + } + } + } + } + + /// Parse a (possibly) negated expression. + /// + /// Example cases: + /// + /// * `! =`: negate the result of the implicit string length test of `=` + /// * `! = foo`: compare the literal strings `!` and `foo` + /// * `! `: negate the result of the expression + /// + fn bang(&mut self) { + if let Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) = self.peek() { + // we need to peek ahead one more token to disambiguate the first + // two cases listed above: case 1 — `! ` — and + // case 2: ` OP str`. + let peek2 = self.tokens.clone().nth(1); + + if peek2.is_none() { + // op is literal + let op = self.next_token().into_literal(); + self.stack.push(op); + self.stack.push(Symbol::Bang); + } else { + // bang is literal; parsing continues with op + self.literal(Symbol::Literal(OsString::from("!"))); + } + } else { + self.expr(); + self.stack.push(Symbol::Bang); + } + } + + /// Peek at the next token and parse it as a BOOLOP or string literal, + /// as appropriate. + fn maybe_boolop(&mut self) { + if self.peek_is_boolop() { + let token = self.tokens.next().unwrap(); // safe because we peeked + + // BoolOp by itself interpreted as Literal + if let Symbol::None = self.peek() { + self.literal(Symbol::Literal(token)) + } else { + self.boolop(Symbol::BoolOp(token)) + } + } + } + + /// Parse a Boolean expression. + /// + /// Logical and (-a) has higher precedence than or (-o), so in an + /// expression like `foo -o '' -a ''`, the and subexpression is evaluated + /// first. + fn boolop(&mut self, op: Symbol) { + if op == Symbol::BoolOp(OsString::from("-a")) { + self.term(); + self.stack.push(op); + self.maybe_boolop(); + } else { + self.expr(); + self.stack.push(op); + } + } + + /// Parse a (possible) unary argument test (string length or file + /// attribute check). + /// + /// If a UOP is followed by nothing it is interpreted as a literal string. + fn uop(&mut self, op: Symbol) { + match self.next_token() { + Symbol::None => self.stack.push(op.into_literal()), + symbol => { + self.stack.push(symbol.into_literal()); + self.stack.push(op); + } + } + } + + /// Parse a string literal, optionally followed by a comparison operator + /// and a second string literal. + fn literal(&mut self, token: Symbol) { + self.stack.push(token.into_literal()); + + // EXPR → str OP str + match self.peek() { + Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) => { + let op = self.next_token(); + + match self.next_token() { + Symbol::None => panic!("missing argument after {:?}", op), + token => self.stack.push(token.into_literal()), + } + + self.stack.push(op); + } + _ => {} + } + } + + /// Parser entry point: parse the token stream `self.tokens`, storing the + /// resulting `Symbol` stack in `self.stack`. + fn parse(&mut self) -> Result<(), String> { + self.expr(); + + match self.tokens.next() { + Some(token) => Err(format!("extra argument ‘{}’", token.to_string_lossy())), + None => Ok(()), + } + } +} + +/// Parse the token stream `args`, returning a `Symbol` stack representing the +/// operations to perform in postfix order. +pub fn parse(args: Vec) -> Result, String> { + let mut p = Parser::new(args); + p.parse()?; + Ok(p.stack) +} diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index f882ff5ae..3e97af0a6 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -1,144 +1,154 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) mahkoh (ju.orth [at] gmail [dot] com) -// * -// * 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) mahkoh (ju.orth [at] gmail [dot] com) +// (c) Daniel Rocco +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. // spell-checker:ignore (ToDO) retval paren prec subprec cond -use std::collections::HashMap; -use std::str::from_utf8; +mod parser; -static NAME: &str = "test"; +use parser::{parse, Symbol}; +use std::ffi::{OsStr, OsString}; pub fn uumain(args: impl uucore::Args) -> i32 { - let args: Vec<_> = args.collect(); - // This is completely disregarding valid windows paths that aren't valid unicode - let args = args - .iter() - .map(|a| a.to_str().unwrap().as_bytes()) - .collect::>(); - if args.is_empty() { - return 2; - } - let args = if !args[0].ends_with(NAME.as_bytes()) { - &args[1..] - } else { - &args[..] - }; - let args = match args[0] { - b"[" => match args[args.len() - 1] { - b"]" => &args[1..args.len() - 1], - _ => return 2, - }, - _ => &args[1..args.len()], - }; - let mut error = false; - let retval = 1 - parse_expr(args, &mut error) as i32; - if error { - 2 - } else { - retval - } -} + // TODO: handle being called as `[` + let args: Vec<_> = args.skip(1).collect(); -fn one(args: &[&[u8]]) -> bool { - !args[0].is_empty() -} + let result = parse(args).and_then(|mut stack| eval(&mut stack)); -fn two(args: &[&[u8]], error: &mut bool) -> bool { - match args[0] { - b"!" => !one(&args[1..]), - b"-b" => path(args[1], PathCondition::BlockSpecial), - b"-c" => path(args[1], PathCondition::CharacterSpecial), - b"-d" => path(args[1], PathCondition::Directory), - b"-e" => path(args[1], PathCondition::Exists), - b"-f" => path(args[1], PathCondition::Regular), - b"-g" => path(args[1], PathCondition::GroupIdFlag), - b"-h" => path(args[1], PathCondition::SymLink), - b"-L" => path(args[1], PathCondition::SymLink), - b"-n" => one(&args[1..]), - b"-p" => path(args[1], PathCondition::Fifo), - b"-r" => path(args[1], PathCondition::Readable), - b"-S" => path(args[1], PathCondition::Socket), - b"-s" => path(args[1], PathCondition::NonEmpty), - b"-t" => isatty(args[1]), - b"-u" => path(args[1], PathCondition::UserIdFlag), - b"-w" => path(args[1], PathCondition::Writable), - b"-x" => path(args[1], PathCondition::Executable), - b"-z" => !one(&args[1..]), - _ => { - *error = true; - false - } - } -} - -fn three(args: &[&[u8]], error: &mut bool) -> bool { - match args[1] { - b"=" => args[0] == args[2], - b"==" => args[0] == args[2], - b"!=" => args[0] != args[2], - b"-eq" => integers(args[0], args[2], IntegerCondition::Equal), - b"-ne" => integers(args[0], args[2], IntegerCondition::Unequal), - b"-gt" => integers(args[0], args[2], IntegerCondition::Greater), - b"-ge" => integers(args[0], args[2], IntegerCondition::GreaterEqual), - b"-lt" => integers(args[0], args[2], IntegerCondition::Less), - b"-le" => integers(args[0], args[2], IntegerCondition::LessEqual), - _ => match args[0] { - b"!" => !two(&args[1..], error), - _ => { - *error = true; - false + match result { + Ok(result) => { + if result { + 0 + } else { + 1 } - }, - } -} - -fn four(args: &[&[u8]], error: &mut bool) -> bool { - match args[0] { - b"!" => !three(&args[1..], error), - _ => { - *error = true; - false + } + Err(e) => { + eprintln!("test: {}", e); + 2 } } } -enum IntegerCondition { - Equal, - Unequal, - Greater, - GreaterEqual, - Less, - LessEqual, -} +/// Evaluate a stack of Symbols, returning the result of the evaluation or +/// an error message if evaluation failed. +fn eval(stack: &mut Vec) -> Result { + macro_rules! pop_literal { + () => { + match stack.pop() { + Some(Symbol::Literal(s)) => s, + _ => panic!(), + } + }; + } -fn integers(a: &[u8], b: &[u8], cond: IntegerCondition) -> bool { - let (a, b): (&str, &str) = match (from_utf8(a), from_utf8(b)) { - (Ok(a), Ok(b)) => (a, b), - _ => return false, - }; - let (a, b): (i64, i64) = match (a.parse(), b.parse()) { - (Ok(a), Ok(b)) => (a, b), - _ => return false, - }; - match cond { - IntegerCondition::Equal => a == b, - IntegerCondition::Unequal => a != b, - IntegerCondition::Greater => a > b, - IntegerCondition::GreaterEqual => a >= b, - IntegerCondition::Less => a < b, - IntegerCondition::LessEqual => a <= b, + let s = stack.pop(); + + match s { + Some(Symbol::Bang) => { + let result = eval(stack)?; + + Ok(!result) + } + Some(Symbol::StringOp(op)) => { + let b = stack.pop(); + let a = stack.pop(); + Ok(if op == "=" { a == b } else { a != b }) + } + Some(Symbol::IntOp(op)) => { + let b = pop_literal!(); + let a = pop_literal!(); + + Ok(integers(&a, &b, &op)?) + } + Some(Symbol::FileOp(_op)) => unimplemented!(), + Some(Symbol::StrlenOp(op)) => { + let s = match stack.pop() { + Some(Symbol::Literal(s)) => s, + Some(Symbol::None) => OsString::from(""), + None => { + return Ok(true); + } + _ => { + return Err(format!("missing argument after ‘{:?}’", op)); + } + }; + + Ok(if op == "-z" { + s.is_empty() + } else { + !s.is_empty() + }) + } + Some(Symbol::FiletestOp(op)) => { + let op = op.to_string_lossy(); + + let f = pop_literal!(); + + Ok(match op.as_ref() { + "-b" => path(&f, PathCondition::BlockSpecial), + "-c" => path(&f, PathCondition::CharacterSpecial), + "-d" => path(&f, PathCondition::Directory), + "-e" => path(&f, PathCondition::Exists), + "-f" => path(&f, PathCondition::Regular), + "-g" => path(&f, PathCondition::GroupIdFlag), + "-h" => path(&f, PathCondition::SymLink), + "-L" => path(&f, PathCondition::SymLink), + "-p" => path(&f, PathCondition::Fifo), + "-r" => path(&f, PathCondition::Readable), + "-S" => path(&f, PathCondition::Socket), + "-s" => path(&f, PathCondition::NonEmpty), + "-t" => isatty(&f)?, + "-u" => path(&f, PathCondition::UserIdFlag), + "-w" => path(&f, PathCondition::Writable), + "-x" => path(&f, PathCondition::Executable), + _ => panic!(), + }) + } + Some(Symbol::Literal(s)) => Ok(!s.is_empty()), + Some(Symbol::None) => Ok(false), + Some(Symbol::BoolOp(op)) => { + let b = eval(stack)?; + let a = eval(stack)?; + + Ok(if op == "-a" { a && b } else { a || b }) + } + None => Ok(false), + _ => Err("expected value".to_string()), } } -fn isatty(fd: &[u8]) -> bool { - from_utf8(fd) - .ok() - .and_then(|s| s.parse().ok()) - .map_or(false, |i| { +fn integers(a: &OsStr, b: &OsStr, cond: &OsStr) -> Result { + let format_err = |value| format!("invalid integer ‘{}’", value); + + let a = a.to_string_lossy(); + let a: i64 = a.parse().map_err(|_| format_err(a))?; + + let b = b.to_string_lossy(); + let b: i64 = b.parse().map_err(|_| format_err(b))?; + + let cond = cond.to_string_lossy(); + Ok(match cond.as_ref() { + "-eq" => a == b, + "-ne" => a != b, + "-gt" => a > b, + "-ge" => a >= b, + "-lt" => a < b, + "-le" => a <= b, + _ => return Err(format!("unknown operator ‘{}’", cond)), + }) +} + +fn isatty(fd: &OsStr) -> Result { + let fd = fd.to_string_lossy(); + + fd.parse() + .map_err(|_| format!("invalid integer ‘{}’", fd)) + .map(|i| { #[cfg(not(target_os = "redox"))] unsafe { libc::isatty(i) == 1 @@ -148,173 +158,6 @@ fn isatty(fd: &[u8]) -> bool { }) } -fn dispatch(args: &mut &[&[u8]], error: &mut bool) -> bool { - let (val, idx) = match args.len() { - 0 => { - *error = true; - (false, 0) - } - 1 => (one(*args), 1), - 2 => dispatch_two(args, error), - 3 => dispatch_three(args, error), - _ => dispatch_four(args, error), - }; - *args = &(*args)[idx..]; - val -} - -fn dispatch_two(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) { - let val = two(*args, error); - if *error { - *error = false; - (one(*args), 1) - } else { - (val, 2) - } -} - -fn dispatch_three(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) { - let val = three(*args, error); - if *error { - *error = false; - dispatch_two(args, error) - } else { - (val, 3) - } -} - -fn dispatch_four(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) { - let val = four(*args, error); - if *error { - *error = false; - dispatch_three(args, error) - } else { - (val, 4) - } -} - -#[derive(Clone, Copy)] -enum Precedence { - Unknown = 0, - Paren, // FIXME: this is useless (parentheses have not been implemented) - Or, - And, - BUnOp, - BinOp, - UnOp, -} - -fn parse_expr(mut args: &[&[u8]], error: &mut bool) -> bool { - if args.is_empty() { - false - } else { - let hashmap = setup_hashmap(); - let lhs = dispatch(&mut args, error); - - if !args.is_empty() { - parse_expr_helper(&hashmap, &mut args, lhs, Precedence::Unknown, error) - } else { - lhs - } - } -} - -fn parse_expr_helper<'a>( - hashmap: &HashMap<&'a [u8], Precedence>, - args: &mut &[&'a [u8]], - mut lhs: bool, - min_prec: Precedence, - error: &mut bool, -) -> bool { - let mut prec = *hashmap.get(&args[0]).unwrap_or_else(|| { - *error = true; - &min_prec - }); - while !*error && !args.is_empty() && prec as usize >= min_prec as usize { - let op = args[0]; - *args = &(*args)[1..]; - let mut rhs = dispatch(args, error); - while !args.is_empty() { - let subprec = *hashmap.get(&args[0]).unwrap_or_else(|| { - *error = true; - &min_prec - }); - if subprec as usize <= prec as usize || *error { - break; - } - rhs = parse_expr_helper(hashmap, args, rhs, subprec, error); - } - lhs = match prec { - Precedence::UnOp | Precedence::BUnOp => { - *error = true; - false - } - Precedence::And => lhs && rhs, - Precedence::Or => lhs || rhs, - Precedence::BinOp => three( - &[ - if lhs { b" " } else { b"" }, - op, - if rhs { b" " } else { b"" }, - ], - error, - ), - Precedence::Paren => unimplemented!(), // TODO: implement parentheses - _ => unreachable!(), - }; - if !args.is_empty() { - prec = *hashmap.get(&args[0]).unwrap_or_else(|| { - *error = true; - &min_prec - }); - } - } - lhs -} - -#[inline] -fn setup_hashmap<'a>() -> HashMap<&'a [u8], Precedence> { - let mut hashmap = HashMap::<&'a [u8], Precedence>::new(); - - hashmap.insert(b"-b", Precedence::UnOp); - hashmap.insert(b"-c", Precedence::UnOp); - hashmap.insert(b"-d", Precedence::UnOp); - hashmap.insert(b"-e", Precedence::UnOp); - hashmap.insert(b"-f", Precedence::UnOp); - hashmap.insert(b"-g", Precedence::UnOp); - hashmap.insert(b"-h", Precedence::UnOp); - hashmap.insert(b"-L", Precedence::UnOp); - hashmap.insert(b"-n", Precedence::UnOp); - hashmap.insert(b"-p", Precedence::UnOp); - hashmap.insert(b"-r", Precedence::UnOp); - hashmap.insert(b"-S", Precedence::UnOp); - hashmap.insert(b"-s", Precedence::UnOp); - hashmap.insert(b"-t", Precedence::UnOp); - hashmap.insert(b"-u", Precedence::UnOp); - hashmap.insert(b"-w", Precedence::UnOp); - hashmap.insert(b"-x", Precedence::UnOp); - hashmap.insert(b"-z", Precedence::UnOp); - - hashmap.insert(b"=", Precedence::BinOp); - hashmap.insert(b"!=", Precedence::BinOp); - hashmap.insert(b"-eq", Precedence::BinOp); - hashmap.insert(b"-ne", Precedence::BinOp); - hashmap.insert(b"-gt", Precedence::BinOp); - hashmap.insert(b"-ge", Precedence::BinOp); - hashmap.insert(b"-lt", Precedence::BinOp); - hashmap.insert(b"-le", Precedence::BinOp); - - hashmap.insert(b"!", Precedence::BUnOp); - - hashmap.insert(b"-a", Precedence::And); - hashmap.insert(b"-o", Precedence::Or); - - hashmap.insert(b"(", Precedence::Paren); - hashmap.insert(b")", Precedence::Paren); - - hashmap -} - #[derive(Eq, PartialEq)] enum PathCondition { BlockSpecial, @@ -334,14 +177,10 @@ enum PathCondition { } #[cfg(not(windows))] -fn path(path: &[u8], cond: PathCondition) -> bool { - use std::ffi::OsStr; +fn path(path: &OsStr, cond: PathCondition) -> bool { use std::fs::{self, Metadata}; - use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::{FileTypeExt, MetadataExt}; - let path = OsStr::from_bytes(path); - const S_ISUID: u32 = 0o4000; const S_ISGID: u32 = 0o2000; @@ -403,13 +242,14 @@ fn path(path: &[u8], cond: PathCondition) -> bool { } #[cfg(windows)] -fn path(path: &[u8], cond: PathCondition) -> bool { +fn path(path: &OsStr, cond: PathCondition) -> bool { use std::fs::metadata; - let path = from_utf8(path).unwrap(); + let stat = match metadata(path) { Ok(s) => s, _ => return false, }; + match cond { PathCondition::BlockSpecial => false, PathCondition::CharacterSpecial => false, diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 2173371fc..000013d9c 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -39,7 +39,6 @@ fn test_double_not_is_false() { } #[test] -#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] fn test_and_not_is_false() { new_ucmd!().args(&["-a", "!"]).run().status_code(1); } @@ -51,7 +50,6 @@ fn test_not_and_is_false() { } #[test] -#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 0"] fn test_not_and_not_succeeds() { new_ucmd!().args(&["!", "-a", "!"]).succeeds(); } @@ -62,7 +60,6 @@ fn test_simple_or() { } #[test] -#[ignore = "fixme: parse/evaluation error (code 0); GNU returns 1"] fn test_negated_or() { new_ucmd!() .args(&["!", "foo", "-o", "bar"]) @@ -111,13 +108,8 @@ fn test_solo_paren_is_literal() { } #[test] -#[ignore = "GNU considers this an error"] fn test_solo_empty_parenthetical_is_error() { - new_ucmd!() - .args(&["(", ")"]) - .run() - .status_code(2) - .stderr_is("test: missing argument after ‘)’"); + new_ucmd!().args(&["(", ")"]).run().status_code(2); } #[test] @@ -248,7 +240,6 @@ fn test_negative_int_compare() { } #[test] -#[ignore = "fixme: error reporting"] fn test_float_inequality_is_error() { new_ucmd!() .args(&["123.45", "-ge", "6"]) @@ -257,6 +248,32 @@ fn test_float_inequality_is_error() { .stderr_is("test: invalid integer ‘123.45’"); } +#[test] +#[cfg(not(windows))] +fn test_invalid_utf8_integer_compare() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + let source = [0x66, 0x6f, 0x80, 0x6f]; + let arg = OsStr::from_bytes(&source[..]); + + let mut cmd = new_ucmd!(); + cmd.arg("123").arg("-ne"); + cmd.raw.arg(arg); + + cmd.run() + .status_code(2) + .stderr_is("test: invalid integer ‘fo�o’"); + + let mut cmd = new_ucmd!(); + cmd.raw.arg(arg); + cmd.arg("-eq").arg("456"); + + cmd.run() + .status_code(2) + .stderr_is("test: invalid integer ‘fo�o’"); +} + #[test] #[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] fn test_file_is_itself() { @@ -445,6 +462,14 @@ fn test_op_prec_and_or_1() { new_ucmd!().args(&[" ", "-o", "", "-a", ""]).succeeds(); } +#[test] +fn test_op_prec_and_or_1_overridden_by_parentheses() { + new_ucmd!() + .args(&["(", " ", "-o", "", ")", "-a", ""]) + .run() + .status_code(1); +} + #[test] fn test_op_prec_and_or_2() { new_ucmd!() @@ -452,6 +477,45 @@ fn test_op_prec_and_or_2() { .succeeds(); } +#[test] +fn test_op_prec_and_or_2_overridden_by_parentheses() { + new_ucmd!() + .args(&["", "-a", "(", "", "-o", " ", ")", "-a", " "]) + .run() + .status_code(1); +} + +#[test] +#[ignore = "fixme: error reporting"] +fn test_dangling_parenthesis() { + new_ucmd!() + .args(&["(", "(", "a", "!=", "b", ")", "-o", "-n", "c"]) + .run() + .status_code(2); + new_ucmd!() + .args(&["(", "(", "a", "!=", "b", ")", "-o", "-n", "c", ")"]) + .succeeds(); +} + +#[test] +fn test_complicated_parenthesized_expression() { + new_ucmd!() + .args(&[ + "(", "(", "!", "(", "a", "=", "b", ")", "-o", "c", "=", "d", ")", "-a", "(", "q", "!=", + "r", ")", ")", + ]) + .succeeds(); +} + +#[test] +fn test_erroneous_parenthesized_expression() { + new_ucmd!() + .args(&["a", "!=", "(", "b", "-a", "b", ")", "!=", "c"]) + .run() + .status_code(2) + .stderr_is("test: extra argument ‘b’"); +} + #[test] fn test_or_as_filename() { new_ucmd!() From f5c7d9bd80c27341efad1af981e69d9e47d47234 Mon Sep 17 00:00:00 2001 From: Dean Li Date: Sat, 1 May 2021 09:51:31 +0800 Subject: [PATCH 30/71] link: replace getopts with clap --- Cargo.lock | 1 + src/uu/link/Cargo.toml | 1 + src/uu/link/src/link.rs | 44 +++++++++++++++++++++++++++++------------ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31787e626..217533df6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2050,6 +2050,7 @@ dependencies = [ name = "uu_link" version = "0.0.6" dependencies = [ + "clap", "libc", "uucore", "uucore_procs", diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index 13c3453cf..14a6ac7c9 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -18,6 +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" [[bin]] name = "link" diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index b82d7cfac..bd8b33355 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -8,14 +8,21 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::fs::hard_link; use std::io::Error; use std::path::Path; -use uucore::InvalidEncodingHandling; -static SYNTAX: &str = "[OPTIONS] FILE1 FILE2"; -static SUMMARY: &str = "Create a link named FILE2 to FILE1"; -static LONG_HELP: &str = ""; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1."; + +pub mod options { + pub static FILES: &str = "FILES"; +} + +fn get_usage() -> String { + format!("{0} FILE1 FILE2", executable!()) +} pub fn normalize_error_message(e: Error) -> String { match e.raw_os_error() { @@ -25,16 +32,27 @@ pub fn normalize_error_message(e: Error) -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse( - args.collect_str(InvalidEncodingHandling::Ignore) - .accept_any(), - ); - if matches.free.len() != 2 { - crash!(1, "{}", msg_wrong_number_of_arguments!(2)); - } + let usage = get_usage(); + let matches = App::new(executable!()) + .version(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 old = Path::new(&matches.free[0]); - let new = Path::new(&matches.free[1]); + let files: Vec<_> = matches + .values_of_os(options::FILES) + .unwrap_or_default() + .collect(); + let old = Path::new(files[0]); + let new = Path::new(files[1]); match hard_link(old, new) { Ok(_) => 0, From 42756610206e5efd8ac1a0687b9d856d7b21e68a Mon Sep 17 00:00:00 2001 From: bashi8128 Date: Sun, 2 May 2021 13:47:36 +0900 Subject: [PATCH 31/71] tests/basename: add tests for --version, --help --- tests/by-util/test_basename.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 8d32b4008..6c7a3ecae 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -1,6 +1,29 @@ use crate::common::util::*; use std::ffi::OsStr; +#[test] +fn test_help() { + for help_flg in vec!["-h", "--help"] { + new_ucmd!() + .arg(&help_flg) + .succeeds() + .no_stderr() + .stdout_contains("USAGE:"); + } +} + +#[test] +fn test_version() { + for version_flg in vec!["-V", "--version"] { + assert!(new_ucmd!() + .arg(&version_flg) + .succeeds() + .no_stderr() + .stdout_str() + .starts_with("basename")); + } +} + #[test] fn test_directory() { new_ucmd!() From 5e82b195bd3f8da8e34e4ccf061227498af3abf1 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:35:00 +0200 Subject: [PATCH 32/71] ls: remove redundant import --- src/uu/ls/src/ls.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index dc7ea4c08..381612189 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -39,8 +39,6 @@ use std::{ time::Duration, }; -use chrono; - use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use unicode_width::UnicodeWidthStr; From e723b8db4321a56ddfe1fec5c538ac88370aff40 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:35:59 +0200 Subject: [PATCH 33/71] factor: unneeded statement --- src/uu/factor/src/factor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 5a85194c4..138254b51 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -172,7 +172,7 @@ pub fn factor(mut n: u64) -> Factors { #[cfg(feature = "coz")] coz::end!("factorization"); - return r; + r } #[cfg(test)] From 108f9928ef77a6d1b811140a87a0ad053e400ef5 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:39:09 +0200 Subject: [PATCH 34/71] cp: fix 'variable does not need to be mutable' --- src/uu/cp/src/cp.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index ca564e37c..3d6faf66a 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -551,6 +551,10 @@ impl FromStr for Attribute { fn add_all_attributes() -> Vec { use Attribute::*; + #[cfg(target_os = "windows")] + let attr = vec![Ownership, Timestamps, Context, Xattr, Links]; + + #[cfg(not(target_os = "windows"))] let mut attr = vec![Ownership, Timestamps, Context, Xattr, Links]; #[cfg(unix)] From c03a7d88561106f1fef24c1ea805513bd0283ff2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:41:04 +0200 Subject: [PATCH 35/71] uname(test): fix 'unused variable: result' --- tests/by-util/test_uname.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_uname.rs b/tests/by-util/test_uname.rs index 6b8d2d59d..da901d985 100644 --- a/tests/by-util/test_uname.rs +++ b/tests/by-util/test_uname.rs @@ -36,7 +36,12 @@ fn test_uname_kernel_version() { fn test_uname_kernel() { let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("-o").succeeds(); #[cfg(target_os = "linux")] - assert!(result.stdout_str().to_lowercase().contains("linux")); + { + let result = ucmd.arg("-o").succeeds(); + assert!(result.stdout_str().to_lowercase().contains("linux")); + } + + #[cfg(not(target_os = "linux"))] + let result = ucmd.arg("-o").succeeds(); } From a34b49ad6053e29ba457f2a43deab21f48ccdf68 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:42:30 +0200 Subject: [PATCH 36/71] relpath(test) - fix: 'value assigned to 'result_stdout' is never read' --- tests/by-util/test_relpath.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/by-util/test_relpath.rs b/tests/by-util/test_relpath.rs index cc17b45c3..5094d25a8 100644 --- a/tests/by-util/test_relpath.rs +++ b/tests/by-util/test_relpath.rs @@ -103,7 +103,7 @@ fn test_relpath_with_from_with_d() { at.mkdir_all(from); // d is part of subpath -> expect relative path - let mut result_stdout = scene + let mut _result_stdout = scene .ucmd() .arg(to) .arg(from) @@ -112,17 +112,17 @@ fn test_relpath_with_from_with_d() { .stdout_move_str(); // relax rules for windows test environment #[cfg(not(windows))] - assert!(Path::new(&result_stdout).is_relative()); + assert!(Path::new(&_result_stdout).is_relative()); // d is not part of subpath -> expect absolut path - result_stdout = scene + _result_stdout = scene .ucmd() .arg(to) .arg(from) .arg("-dnon_existing") .succeeds() .stdout_move_str(); - assert!(Path::new(&result_stdout).is_absolute()); + assert!(Path::new(&_result_stdout).is_absolute()); } } @@ -135,12 +135,12 @@ fn test_relpath_no_from_no_d() { let to: &str = &convert_path(test.to); at.mkdir_all(to); - let result_stdout = scene.ucmd().arg(to).succeeds().stdout_move_str(); + let _result_stdout = scene.ucmd().arg(to).succeeds().stdout_move_str(); #[cfg(not(windows))] - assert_eq!(result_stdout, format!("{}\n", to)); + assert_eq!(_result_stdout, format!("{}\n", to)); // relax rules for windows test environment #[cfg(windows)] - assert!(result_stdout.ends_with(&format!("{}\n", to))); + assert!(_result_stdout.ends_with(&format!("{}\n", to))); } } From d0512618d5212d8991556a98c44bb688144c4d45 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:43:32 +0200 Subject: [PATCH 37/71] refresh cargo.lock with recent updates --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 091758278..c74f4e2be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1277,9 +1277,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efb2352a0f4d4b128f734b5c44c79ff80117351138733f12f982fe3e2b13343" +checksum = "ce5f1ceb7f74abbce32601642fcf8e8508a8a8991e0621c7d750295b9095702b" dependencies = [ "aho-corasick", "memchr 2.4.0", @@ -1297,9 +1297,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.24" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00efb87459ba4f6fb2169d20f68565555688e1250ee6825cdf6254f8b48fafb2" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "remove_dir_all" From 28c7800f73ec2712a296fd930a9e7706efa6a9bd Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 2 May 2021 10:03:01 +0200 Subject: [PATCH 38/71] ls: fix subdirectory name --- src/uu/ls/src/ls.rs | 25 ++++++++++------ tests/by-util/test_ls.rs | 63 ++++++++++++++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 18 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index dc7ea4c08..4ebcc479c 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1122,14 +1122,21 @@ impl PathData { fn new( p_buf: PathBuf, file_type: Option>, + file_name: Option, config: &Config, command_line: bool, ) -> Self { - let name = p_buf - .file_name() - .unwrap_or_else(|| p_buf.iter().next_back().unwrap()) - .to_string_lossy() - .into_owned(); + // We cannot use `Path::ends_with` or `Path::Components`, because they remove occurrences of '.' + // For '..', the filename is None + let name = if let Some(name) = file_name { + name + } else { + p_buf + .file_name() + .unwrap_or_else(|| p_buf.iter().next_back().unwrap()) + .to_string_lossy() + .into_owned() + }; let must_dereference = match &config.dereference { Dereference::All => true, Dereference::Args => command_line, @@ -1192,7 +1199,7 @@ fn list(locs: Vec, config: Config) -> i32 { continue; } - let path_data = PathData::new(p, None, &config, true); + let path_data = PathData::new(p, None, None, &config, true); let show_dir_contents = if let Some(ft) = path_data.file_type() { !config.directory && ft.is_dir() @@ -1283,8 +1290,8 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool { fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) { let mut entries: Vec<_> = if config.files == Files::All { vec![ - PathData::new(dir.p_buf.join("."), None, config, false), - PathData::new(dir.p_buf.join(".."), None, config, false), + PathData::new(dir.p_buf.join("."), None, Some(".".into()), config, false), + PathData::new(dir.p_buf.join(".."), None, Some("..".into()), config, false), ] } else { vec![] @@ -1293,7 +1300,7 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) let mut temp: Vec<_> = safe_unwrap!(fs::read_dir(&dir.p_buf)) .map(|res| safe_unwrap!(res)) .filter(|e| should_display(e, config)) - .map(|e| PathData::new(DirEntry::path(&e), Some(e.file_type()), config, false)) + .map(|e| PathData::new(DirEntry::path(&e), Some(e.file_type()), None, config, false)) .collect(); sort_entries(&mut temp, config); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 2b8f311d1..0331d0214 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -43,23 +43,68 @@ fn test_ls_a() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch(".test-1"); + at.mkdir("some-dir"); + at.touch( + Path::new("some-dir") + .join(".test-2") + .as_os_str() + .to_str() + .unwrap(), + ); - let result = scene.ucmd().succeeds(); - let stdout = result.stdout_str(); - assert!(!stdout.contains(".test-1")); - assert!(!stdout.contains("..")); + let re_pwd = Regex::new(r"^\.\n").unwrap(); + + // Using the present working directory + scene + .ucmd() + .succeeds() + .stdout_does_not_contain(".test-1") + .stdout_does_not_contain("..") + .stdout_does_not_match(&re_pwd); scene .ucmd() .arg("-a") .succeeds() .stdout_contains(&".test-1") - .stdout_contains(&".."); + .stdout_contains(&"..") + .stdout_matches(&re_pwd); - let result = scene.ucmd().arg("-A").succeeds(); - result.stdout_contains(".test-1"); - let stdout = result.stdout_str(); - assert!(!stdout.contains("..")); + scene + .ucmd() + .arg("-A") + .succeeds() + .stdout_contains(".test-1") + .stdout_does_not_contain("..") + .stdout_does_not_match(&re_pwd); + + // Using a subdirectory + scene + .ucmd() + .arg("some-dir") + .succeeds() + .stdout_does_not_contain(".test-2") + .stdout_does_not_contain("..") + .stdout_does_not_match(&re_pwd); + + scene + .ucmd() + .arg("-a") + .arg("some-dir") + .succeeds() + .stdout_contains(&".test-2") + .stdout_contains(&"..") + .no_stderr() + .stdout_matches(&re_pwd); + + scene + .ucmd() + .arg("-A") + .arg("some-dir") + .succeeds() + .stdout_contains(".test-2") + .stdout_does_not_contain("..") + .stdout_does_not_match(&re_pwd); } #[test] From 361408cbe53e37e5b42a4d2b496cb03b2334f406 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 2 May 2021 10:04:11 +0200 Subject: [PATCH 39/71] ls: remove case-insensitivity and leading period of name sort --- src/uu/ls/BENCHMARKING.md | 2 ++ src/uu/ls/src/ls.rs | 10 ++-------- tests/by-util/test_ls.rs | 3 +-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/uu/ls/BENCHMARKING.md b/src/uu/ls/BENCHMARKING.md index 852220496..b009b703a 100644 --- a/src/uu/ls/BENCHMARKING.md +++ b/src/uu/ls/BENCHMARKING.md @@ -36,6 +36,8 @@ args="$@" hyperfine "ls $args" "target/release/coreutils ls $args" ``` +**Note**: No localization is currently implemented. This means that the comparison above is not really fair. We can fix this by setting `LC_ALL=C`, so GNU `ls` can ignore localization. + ## Checking system call count - Another thing to look at would be system calls count using strace (on linux) or equivalent on other operating systems. diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 4ebcc479c..2e8a5e5ed 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1244,14 +1244,8 @@ fn sort_entries(entries: &mut Vec, config: &Config) { entries.sort_by_key(|k| Reverse(k.md().as_ref().map(|md| md.len()).unwrap_or(0))) } // The default sort in GNU ls is case insensitive - Sort::Name => entries.sort_by_cached_key(|k| { - let has_dot: bool = k.file_name.starts_with('.'); - let filename_nodot: &str = &k.file_name[if has_dot { 1 } else { 0 }..]; - // We want hidden files to appear before regular files of the same - // name, so we need to negate the "has_dot" variable. - (filename_nodot.to_lowercase(), !has_dot) - }), - Sort::Version => entries.sort_by(|k, j| version_cmp::version_cmp(&k.p_buf, &j.p_buf)), + Sort::Name => entries.sort_by(|a, b| a.file_name.cmp(&b.file_name)), + Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(&a.p_buf, &b.p_buf)), Sort::Extension => entries.sort_by(|a, b| { a.p_buf .extension() diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 0331d0214..4be24a99a 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -527,7 +527,6 @@ fn test_ls_sort_name() { .succeeds() .stdout_is(["test-1", "test-2", "test-3\n"].join(sep)); - // Order of a named sort ignores leading dots. let scene_dot = TestScenario::new(util_name!()); let at = &scene_dot.fixtures; at.touch(".a"); @@ -540,7 +539,7 @@ fn test_ls_sort_name() { .arg("--sort=name") .arg("-A") .succeeds() - .stdout_is([".a", "a", ".b", "b\n"].join(sep)); + .stdout_is([".a", ".b", "a", "b\n"].join(sep)); } #[test] From 47a5dd0f9737cebe2859fc1ee5f9fff5e239f3e2 Mon Sep 17 00:00:00 2001 From: bashi8128 Date: Sun, 2 May 2021 17:08:14 +0900 Subject: [PATCH 40/71] basename: move from getopts to clap (#2117) Use clap for argument parsing instead of getopts Also, make the following changes * Use `executable!()` macro to output the name of utility * Add another usage to help message --- src/uu/basename/Cargo.toml | 1 + src/uu/basename/src/basename.rs | 91 +++++++++++++++++++++------------ 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index 92d0ca4cd..0072619b7 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/basename.rs" [dependencies] +clap = "2.33.2" 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/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 68b705d53..b54a69f44 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -10,80 +10,103 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::path::{is_separator, PathBuf}; use uucore::InvalidEncodingHandling; -static NAME: &str = "basename"; -static SYNTAX: &str = "NAME [SUFFIX]"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); static SUMMARY: &str = "Print NAME with any leading directory components removed - If specified, also remove a trailing SUFFIX"; -static LONG_HELP: &str = ""; +If specified, also remove a trailing SUFFIX"; + +fn get_usage() -> String { + format!( + "{0} NAME [SUFFIX] + {0} OPTION... NAME...", + executable!() + ) +} + +pub mod options { + pub static MULTIPLE: &str = "multiple"; + pub static NAME: &str = "name"; + pub static SUFFIX: &str = "suffix"; + pub static ZERO: &str = "zero"; +} pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); + let usage = get_usage(); // // Argument parsing // - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optflag( - "a", - "multiple", - "Support more than one argument. Treat every argument as a name.", + let matches = App::new(executable!()) + .version(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"), ) - .optopt( - "s", - "suffix", - "Remove a trailing suffix. This option implies the -a option.", - "SUFFIX", + .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"), ) - .optflag( - "z", - "zero", - "Output a zero byte (ASCII NUL) at the end of each line, rather than a newline.", + .arg( + Arg::with_name(options::ZERO) + .short("z") + .long(options::ZERO) + .help("end each output line with NUL, not newline"), ) - .parse(args); + .get_matches_from(args); // too few arguments - if matches.free.is_empty() { + if !matches.is_present(options::NAME) { crash!( 1, "{0}: {1}\nTry '{0} --help' for more information.", - NAME, + executable!(), "missing operand" ); } - let opt_s = matches.opt_present("s"); - let opt_a = matches.opt_present("a"); - let opt_z = matches.opt_present("z"); + + let opt_s = matches.is_present(options::SUFFIX); + let opt_a = matches.is_present(options::MULTIPLE); + let opt_z = matches.is_present(options::ZERO); let multiple_paths = opt_s || opt_a; // too many arguments - if !multiple_paths && matches.free.len() > 2 { + if !multiple_paths && matches.occurrences_of(options::NAME) > 2 { crash!( 1, "{0}: extra operand '{1}'\nTry '{0} --help' for more information.", - NAME, - matches.free[2] + executable!(), + matches.values_of(options::NAME).unwrap().nth(2).unwrap() ); } let suffix = if opt_s { - matches.opt_str("s").unwrap() - } else if !opt_a && matches.free.len() > 1 { - matches.free[1].clone() + matches.value_of(options::SUFFIX).unwrap() + } else if !opt_a && matches.occurrences_of(options::NAME) > 1 { + matches.values_of(options::NAME).unwrap().nth(1).unwrap() } else { - "".to_owned() + "" }; // // Main Program Processing // - let paths = if multiple_paths { - &matches.free[..] + let paths: Vec<_> = if multiple_paths { + matches.values_of(options::NAME).unwrap().collect() } else { - &matches.free[0..1] + matches.values_of(options::NAME).unwrap().take(1).collect() }; let line_ending = if opt_z { "\0" } else { "\n" }; From eb3206737b61dab810e7c115ac3fbd077f348702 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 2 May 2021 10:20:14 +0200 Subject: [PATCH 41/71] ls: give '.' a file_type --- src/uu/ls/src/ls.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 2e8a5e5ed..482648e79 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1284,7 +1284,13 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool { fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) { let mut entries: Vec<_> = if config.files == Files::All { vec![ - PathData::new(dir.p_buf.join("."), None, Some(".".into()), config, false), + PathData::new( + dir.p_buf.clone(), + Some(Ok(*dir.file_type().unwrap())), + Some(".".into()), + config, + false, + ), PathData::new(dir.p_buf.join(".."), None, Some("..".into()), config, false), ] } else { From 09178360d8286a83e683c16ac18e7e622c1d92fb Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 10:30:28 +0200 Subject: [PATCH 42/71] date: unneeded 'return' statement --- src/uu/date/src/date.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 43573437d..317fd72d4 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -348,7 +348,7 @@ fn set_system_datetime(_date: DateTime) -> i32 { #[cfg(target_os = "macos")] fn set_system_datetime(_date: DateTime) -> i32 { eprintln!("date: setting the date is not supported by macOS"); - return 1; + 1 } #[cfg(all(unix, not(target_os = "macos")))] From 9554710ab5d71ce9d42dd86d41e752eea607f4ec Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 10:31:28 +0200 Subject: [PATCH 43/71] cat: the function 'unistd::write' doesn't need a mutable reference --- src/uu/cat/src/splice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index ccc625467..bd6be60f1 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -81,7 +81,7 @@ fn copy_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result< let mut buf = [0; BUF_SIZE]; loop { let read = unistd::read(read_fd, &mut buf[..left])?; - let written = unistd::write(write_fd, &mut buf[..read])?; + let written = unistd::write(write_fd, &buf[..read])?; left -= written; if left == 0 { break; From 34c22dc3ad359ebdd08ebbfcf6d3651c433de2df Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 2 May 2021 12:15:16 +0200 Subject: [PATCH 44/71] tr: fix complement if set2 is range --- src/uu/tr/src/tr.rs | 2 +- tests/by-util/test_tr.rs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index b1143b4bb..68543229e 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -149,7 +149,7 @@ impl TranslateOperation { TranslateOperation { translate_map: map, complement, - s2_last: s2_prev, + s2_last: set2.last().unwrap_or(s2_prev), } } } diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 064fbf779..f840dee62 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -86,6 +86,26 @@ fn test_complement3() { .stdout_is("3he3ca33a3d33he3ba3"); } +#[test] +fn test_complement4() { + // $ echo -n '0x1y2z3' | tr -c '0-@' '*-~' + // 0~1~2~3 + new_ucmd!() + .args(&["-c", "0-@", "*-~"]) + .pipe_in("0x1y2z3") + .run() + .stdout_is("0~1~2~3"); + + // TODO: fix this + // $ echo '0x1y2z3' | tr -c '\0-@' '*-~' + // 0a1b2c3 + // new_ucmd!() + // .args(&["-c", "\\0-@", "*-~"]) + // .pipe_in("0x1y2z3") + // .run() + // .stdout_is("0a1b2c3"); +} + #[test] fn test_squeeze() { new_ucmd!() From 1dccbfd21e2859b06008307e001a20c9516d0cf9 Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Sat, 1 May 2021 15:51:12 +0200 Subject: [PATCH 45/71] kill: migrate to clap Fixes #2122. --- Cargo.lock | 1 + src/uu/kill/Cargo.toml | 1 + src/uu/kill/src/kill.rs | 89 +++++++++++++++++++++++++++++------------ 3 files changed, 65 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31787e626..05040dff1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2041,6 +2041,7 @@ dependencies = [ name = "uu_kill" version = "0.0.6" dependencies = [ + "clap", "libc", "uucore", "uucore_procs", diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index 6b66806bc..e33411c70 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/kill.rs" [dependencies] +clap = "2.33" 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/kill/src/kill.rs b/src/uu/kill/src/kill.rs index fe925ce37..362f13f18 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -10,18 +10,26 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use libc::{c_int, pid_t}; use std::io::Error; use uucore::signals::ALL_SIGNALS; use uucore::InvalidEncodingHandling; -static SYNTAX: &str = "[options] [...]"; -static SUMMARY: &str = ""; -static LONG_HELP: &str = ""; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Send signal to processes or list information about signals."; static EXIT_OK: i32 = 0; static EXIT_ERR: i32 = 1; +pub mod options { + pub static PIDS_OR_SIGNALS: &str = "pids_of_signals"; + pub static LIST: &str = "list"; + pub static TABLE: &str = "table"; + pub static TABLE_OLD: &str = "table_old"; + pub static SIGNAL: &str = "signal"; +} + #[derive(Clone, Copy)] pub enum Mode { Kill, @@ -33,41 +41,70 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let (args, obs_signal) = handle_obsolete(args); - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optopt("s", "signal", "specify the to be sent", "SIGNAL") - .optflagopt( - "l", - "list", - "list all signal names, or convert one to a name", - "LIST", - ) - .optflag("L", "table", "list all signal names in a nice table") - .parse(args); - let mode = if matches.opt_present("table") { + let usage = format!("{} [OPTIONS]... PID...", executable!()); + let matches = App::new(executable!()) + .version(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 mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) { Mode::Table - } else if matches.opt_present("list") { + } else if matches.is_present(options::LIST) { Mode::List } else { Mode::Kill }; + let pids_or_signals: Vec = matches + .values_of(options::PIDS_OR_SIGNALS) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + match mode { Mode::Kill => { - return kill( - &matches - .opt_str("signal") - .unwrap_or_else(|| obs_signal.unwrap_or_else(|| "TERM".to_owned())), - matches.free, - ) + let sig = match (obs_signal, matches.value_of(options::SIGNAL)) { + (Some(s), Some(_)) => s, // -s takes precedence + (Some(s), None) => s, + (None, Some(s)) => s.to_owned(), + (None, None) => "TERM".to_owned(), + }; + return kill(&sig, &pids_or_signals); } Mode::Table => table(), - Mode::List => list(matches.opt_str("list")), + Mode::List => list(pids_or_signals.get(0).cloned()), } - 0 + EXIT_OK } fn handle_obsolete(mut args: Vec) -> (Vec, Option) { @@ -148,14 +185,14 @@ fn list(arg: Option) { }; } -fn kill(signalname: &str, pids: std::vec::Vec) -> i32 { +fn kill(signalname: &str, pids: &[String]) -> i32 { let mut status = 0; let optional_signal_value = uucore::signals::signal_by_name_or_value(signalname); let signal_value = match optional_signal_value { Some(x) => x, None => crash!(EXIT_ERR, "unknown signal name {}", signalname), }; - for pid in &pids { + for pid in pids { match pid.parse::() { Ok(x) => { if unsafe { libc::kill(x as pid_t, signal_value as c_int) } != 0 { From 0e6b63b47bdf95c8458c717144ba1df9b1a66215 Mon Sep 17 00:00:00 2001 From: Dean Li Date: Sun, 2 May 2021 18:32:34 +0800 Subject: [PATCH 46/71] Add tests to check link fails with 1 or 3 argument(s) --- tests/by-util/test_link.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/by-util/test_link.rs b/tests/by-util/test_link.rs index 381ea168a..99559a7fe 100644 --- a/tests/by-util/test_link.rs +++ b/tests/by-util/test_link.rs @@ -39,3 +39,25 @@ fn test_link_nonexistent_file() { assert!(!at.file_exists(file)); assert!(!at.file_exists(link)); } + +#[test] +fn test_link_one_argument() { + let (_, mut ucmd) = at_and_ucmd!(); + let file = "test_link_argument"; + ucmd.args(&[file]).fails().stderr_contains( + "error: The argument '...' requires at least 2 values, but only 1 was provide", + ); +} + +#[test] +fn test_link_three_arguments() { + let (_, mut ucmd) = at_and_ucmd!(); + let arguments = vec![ + "test_link_argument1", + "test_link_argument2", + "test_link_argument3", + ]; + ucmd.args(&arguments[..]).fails().stderr_contains( + format!("error: The value '{}' was provided to '...', but it wasn't expecting any more values", arguments[2]), + ); +} From 000bd73edceae756be09693d4f7db9ed5899210a Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 2 May 2021 12:39:25 +0200 Subject: [PATCH 47/71] tr: fix merge conflict --- src/uu/tr/src/tr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 5e6c41a86..dcb64a127 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -182,7 +182,7 @@ impl TranslateAndSqueezeOperation { complement: bool, ) -> TranslateAndSqueezeOperation { TranslateAndSqueezeOperation { - translate: TranslateOperation::new(set1, set2, truncate), + translate: TranslateOperation::new(set1, set2, truncate, complement), squeeze: SqueezeOperation::new(set2_, complement), } } From acd30526a26ccfaf0a6860c913e552b1f7363729 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 2 May 2021 13:53:11 +0200 Subject: [PATCH 48/71] tr: fix clippy warning --- src/uu/tr/src/tr.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index dcb64a127..a44f2733c 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -217,8 +217,8 @@ fn translate_input( // Set `prev_c` to the post-translate character. This // allows the squeeze operation to correctly function // after the translate operation. - if res.is_some() { - prev_c = res.unwrap(); + if let Some(rc) = res { + prev_c = rc; } res }); From fba245b176d47b9e9d25975dd87f5d4babfaf5dd Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 2 May 2021 14:26:23 +0200 Subject: [PATCH 49/71] ls: add -1 to tests to separate names with \n on windows --- tests/by-util/test_ls.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 4be24a99a..65dd51c8b 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -57,6 +57,7 @@ fn test_ls_a() { // Using the present working directory scene .ucmd() + .arg("-1") .succeeds() .stdout_does_not_contain(".test-1") .stdout_does_not_contain("..") @@ -65,6 +66,7 @@ fn test_ls_a() { scene .ucmd() .arg("-a") + .arg("-1") .succeeds() .stdout_contains(&".test-1") .stdout_contains(&"..") @@ -73,6 +75,7 @@ fn test_ls_a() { scene .ucmd() .arg("-A") + .arg("-1") .succeeds() .stdout_contains(".test-1") .stdout_does_not_contain("..") @@ -81,6 +84,7 @@ fn test_ls_a() { // Using a subdirectory scene .ucmd() + .arg("-1") .arg("some-dir") .succeeds() .stdout_does_not_contain(".test-2") @@ -90,6 +94,7 @@ fn test_ls_a() { scene .ucmd() .arg("-a") + .arg("-1") .arg("some-dir") .succeeds() .stdout_contains(&".test-2") @@ -100,6 +105,7 @@ fn test_ls_a() { scene .ucmd() .arg("-A") + .arg("-1") .arg("some-dir") .succeeds() .stdout_contains(".test-2") From dc5bd9f0bed39fa659edd43ac7c670c71ebf53bc Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 2 May 2021 17:27:44 +0200 Subject: [PATCH 50/71] improve memory usage estimation --- src/uu/sort/src/sort.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index be7944a0f..7436b9fda 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -281,7 +281,9 @@ pub struct Line { impl Line { pub fn estimate_size(&self) -> usize { - self.line.capacity() + self.selections.capacity() * std::mem::size_of::() + self.line.capacity() + + self.selections.capacity() * std::mem::size_of::() + + std::mem::size_of::() } pub fn new(line: String, settings: &GlobalSettings) -> Self { From 8b35dd974141218987749cf3b60ba8f5a190c1a0 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 2 May 2021 17:27:52 +0200 Subject: [PATCH 51/71] add requested tests --- tests/by-util/test_sort.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index eac9490a5..4465e861f 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -37,7 +37,29 @@ fn test_larger_than_specified_segment() { .arg("50K") .arg("ext_sort.txt") .succeeds() - .stdout_is_fixture(format!("{}", "ext_sort.expected")); + .stdout_is_fixture("ext_sort.expected"); +} + +#[test] +fn test_smaller_than_specified_segment() { + new_ucmd!() + .arg("-n") + .arg("-S") + .arg("100M") + .arg("ext_sort.txt") + .succeeds() + .stdout_is_fixture("ext_sort.expected"); +} + +#[test] +fn test_extsort_zero_terminated() { + new_ucmd!() + .arg("-z") + .arg("-S") + .arg("10K") + .arg("zero-terminated.txt") + .succeeds() + .stdout_is_fixture("zero-terminated.expected"); } #[test] From 3fcac152f8ef1e57de96bc999b2104da1a94ca91 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 2 May 2021 18:35:52 +0200 Subject: [PATCH 52/71] tr: add test --- tests/by-util/test_tr.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index a035faae7..29712b44a 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -95,15 +95,18 @@ fn test_complement4() { .pipe_in("0x1y2z3") .run() .stdout_is("0~1~2~3"); +} - // TODO: fix this +#[test] +#[ignore = "fixme: GNU tr returns '0a1b2c3' instead of '0~1~2~3', see #2158"] +fn test_complement5() { // $ echo '0x1y2z3' | tr -c '\0-@' '*-~' // 0a1b2c3 - // new_ucmd!() - // .args(&["-c", "\\0-@", "*-~"]) - // .pipe_in("0x1y2z3") - // .run() - // .stdout_is("0a1b2c3"); + new_ucmd!() + .args(&["-c", "\\0-@", "*-~"]) + .pipe_in("0x1y2z3") + .run() + .stdout_is("0a1b2c3"); } #[test] From 0a3e2216d72b925d6e47237c37f5e233a656c358 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 2 May 2021 16:29:34 -0400 Subject: [PATCH 53/71] wc: add lines() method for iterating over lines Add the `WordCountable::lines()` method that returns an iterator over lines of a file-like object. This mirrors the `std::io::BufRead::lines()` method, with some minor differences due to the particular use case of `wc`. This commit also creates a new module, `countable.rs`, to contain the `WordCountable` trait and the new `Lines` struct returned by `lines()`. --- src/uu/wc/src/countable.rs | 72 ++++++++++++++++++++++++++++++++++++++ src/uu/wc/src/wc.rs | 55 +++++------------------------ 2 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 src/uu/wc/src/countable.rs diff --git a/src/uu/wc/src/countable.rs b/src/uu/wc/src/countable.rs new file mode 100644 index 000000000..3da910a03 --- /dev/null +++ b/src/uu/wc/src/countable.rs @@ -0,0 +1,72 @@ +//! Traits and implementations for iterating over lines in a file-like object. +//! +//! This module provides a [`WordCountable`] trait and implementations +//! for some common file-like objects. Use the [`WordCountable::lines`] +//! method to get an iterator over lines of a file-like object. +use std::fs::File; +use std::io::{self, BufRead, BufReader, Read, StdinLock}; + +#[cfg(unix)] +use std::os::unix::io::AsRawFd; + +#[cfg(unix)] +pub trait WordCountable: AsRawFd + Read { + type Buffered: BufRead; + fn lines(self) -> Lines; +} + +#[cfg(not(unix))] +pub trait WordCountable: Read { + type Buffered: BufRead; + fn lines(self) -> Lines; +} + +impl WordCountable for StdinLock<'_> { + type Buffered = Self; + + fn lines(self) -> Lines + where + Self: Sized, + { + Lines { buf: self } + } +} +impl WordCountable for File { + type Buffered = BufReader; + + fn lines(self) -> Lines + where + Self: Sized, + { + Lines { + buf: BufReader::new(self), + } + } +} + +/// An iterator over the lines of an instance of `BufRead`. +/// +/// Similar to [`io::Lines`] but yields each line as a `Vec` and +/// includes the newline character (`\n`, the `0xA` byte) that +/// terminates the line. +/// +/// [`io::Lines`]:: io::Lines +pub struct Lines { + buf: B, +} + +impl Iterator for Lines { + type Item = io::Result>; + + fn next(&mut self) -> Option { + let mut line = Vec::new(); + + // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. + // hence the option wrapped in a result here + match self.buf.read_until(b'\n', &mut line) { + Ok(0) => None, + Ok(_n) => Some(Ok(line)), + Err(e) => Some(Err(e)), + } + } +} diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 59ca10141..3b70856fa 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -11,17 +11,17 @@ extern crate uucore; mod count_bytes; +mod countable; use count_bytes::count_bytes_fast; +use countable::WordCountable; use clap::{App, Arg, ArgMatches}; use thiserror::Error; use std::cmp::max; use std::fs::File; -use std::io::{self, BufRead, BufReader, Read, StdinLock, Write}; +use std::io::{self, Write}; use std::ops::{Add, AddAssign}; -#[cfg(unix)] -use std::os::unix::io::AsRawFd; use std::path::Path; use std::str::from_utf8; @@ -82,32 +82,6 @@ impl Settings { } } -#[cfg(unix)] -trait WordCountable: AsRawFd + Read { - type Buffered: BufRead; - fn get_buffered(self) -> Self::Buffered; -} -#[cfg(not(unix))] -trait WordCountable: Read { - type Buffered: BufRead; - fn get_buffered(self) -> Self::Buffered; -} - -impl WordCountable for StdinLock<'_> { - type Buffered = Self; - - fn get_buffered(self) -> Self::Buffered { - self - } -} -impl WordCountable for File { - type Buffered = BufReader; - - fn get_buffered(self) -> Self::Buffered { - BufReader::new(self) - } -} - #[derive(Debug, Default, Copy, Clone)] struct WordCount { bytes: usize, @@ -270,25 +244,16 @@ fn word_count_from_reader( let mut byte_count: usize = 0; let mut char_count: usize = 0; let mut longest_line_length: usize = 0; - let mut raw_line = Vec::new(); let mut ends_lf: bool; // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. // hence the option wrapped in a result here - let mut buffered_reader = reader.get_buffered(); - loop { - match buffered_reader.read_until(LF, &mut raw_line) { - Ok(n) => { - if n == 0 { - break; - } - } - Err(ref e) => { - if !raw_line.is_empty() { - show_warning!("Error while reading {}: {}", path, e); - } else { - break; - } + for line_result in reader.lines() { + let raw_line = match line_result { + Ok(l) => l, + Err(e) => { + show_warning!("Error while reading {}: {}", path, e); + continue; } }; @@ -317,8 +282,6 @@ fn word_count_from_reader( longest_line_length = current_char_count - (ends_lf as usize); } } - - raw_line.truncate(0); } Ok(WordCount { From 219fc48487c2095958bf46f70adf6da0436a4fd4 Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Mon, 3 May 2021 08:42:23 +0100 Subject: [PATCH 54/71] compile_table: adding mac M1 to report. --- README.md | 1 + docs/compiles_table.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 12dfb5609..95dc036fd 100644 --- a/README.md +++ b/README.md @@ -429,6 +429,7 @@ This is an auto-generated table showing which binaries compile for each target-t |windows-msvc|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y| |windows-gnu|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y| |windows-msvc|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y| +|apple MacOS|aarch64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |apple MacOS|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |freebsd|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |netbsd|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y| diff --git a/docs/compiles_table.py b/docs/compiles_table.py index 0cbfdf0e9..a051287c9 100644 --- a/docs/compiles_table.py +++ b/docs/compiles_table.py @@ -26,6 +26,7 @@ TARGETS = [ "x86_64-pc-windows-gnu", "x86_64-pc-windows-msvc", # Apple + "aarch64-apple-darwin", "x86_64-apple-darwin", "aarch64-apple-ios", "x86_64-apple-ios", @@ -231,4 +232,4 @@ if __name__ == "__main__": prev_table, _, _ = load_csv(CACHE_PATH) new_table = merge_tables(prev_table, table) - save_csv(CACHE_PATH, new_table) \ No newline at end of file + save_csv(CACHE_PATH, new_table) From 91c736bd95bc8eafdff1ff428bc68e3ed76fa34c Mon Sep 17 00:00:00 2001 From: bashi8128 Date: Mon, 3 May 2021 23:22:00 +0900 Subject: [PATCH 55/71] tests/basename: add tests for error messages --- tests/by-util/test_basename.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 6c7a3ecae..2a40ba4b9 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -104,11 +104,25 @@ fn test_no_args() { expect_error(vec![]); } +#[test] +fn test_no_args_output() { + new_ucmd!() + .fails() + .stderr_is("basename: error: missing operand\nTry 'basename --help' for more information."); +} + #[test] fn test_too_many_args() { expect_error(vec!["a", "b", "c"]); } +#[test] +fn test_too_many_args_output() { + new_ucmd!().args(&["a", "b", "c"]).fails().stderr_is( + "basename: error: extra operand 'c'\nTry 'basename --help' for more information.", + ); +} + fn test_invalid_utf8_args(os_str: &OsStr) { let test_vec = vec![os_str.to_os_string()]; new_ucmd!().args(&test_vec).succeeds().stdout_is("fo�o\n"); From 74802f9f0fca4579315ce6f7097ff131534d211f Mon Sep 17 00:00:00 2001 From: bashi8128 Date: Mon, 3 May 2021 23:26:46 +0900 Subject: [PATCH 56/71] basename: improve error messages Remove duplicated utility name from error messages --- src/uu/basename/src/basename.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index b54a69f44..d5512863f 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -71,7 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !matches.is_present(options::NAME) { crash!( 1, - "{0}: {1}\nTry '{0} --help' for more information.", + "{1}\nTry '{0} --help' for more information.", executable!(), "missing operand" ); @@ -85,7 +85,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !multiple_paths && matches.occurrences_of(options::NAME) > 2 { crash!( 1, - "{0}: extra operand '{1}'\nTry '{0} --help' for more information.", + "extra operand '{1}'\nTry '{0} --help' for more information.", executable!(), matches.values_of(options::NAME).unwrap().nth(2).unwrap() ); From 5a4bb610ffec547fdac3f749a17f46b572f62962 Mon Sep 17 00:00:00 2001 From: bashi8128 Date: Mon, 3 May 2021 23:32:01 +0900 Subject: [PATCH 57/71] basename: rename variable names Rename variable names to be more explicit ones --- src/uu/basename/src/basename.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index d5512863f..c20561b30 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -77,10 +77,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ); } - let opt_s = matches.is_present(options::SUFFIX); - let opt_a = matches.is_present(options::MULTIPLE); - let opt_z = matches.is_present(options::ZERO); - let multiple_paths = opt_s || opt_a; + let opt_suffix = matches.is_present(options::SUFFIX); + let opt_multiple = matches.is_present(options::MULTIPLE); + let opt_zero = matches.is_present(options::ZERO); + let multiple_paths = opt_suffix || opt_multiple; // too many arguments if !multiple_paths && matches.occurrences_of(options::NAME) > 2 { crash!( @@ -91,9 +91,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ); } - let suffix = if opt_s { + let suffix = if opt_suffix { matches.value_of(options::SUFFIX).unwrap() - } else if !opt_a && matches.occurrences_of(options::NAME) > 1 { + } else if !opt_multiple && matches.occurrences_of(options::NAME) > 1 { matches.values_of(options::NAME).unwrap().nth(1).unwrap() } else { "" @@ -109,7 +109,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { matches.values_of(options::NAME).unwrap().take(1).collect() }; - let line_ending = if opt_z { "\0" } else { "\n" }; + let line_ending = if opt_zero { "\0" } else { "\n" }; for path in paths { print!("{}{}", basename(&path, &suffix), line_ending); } From 224c8b3f9469e3f9030dc5d076b4e398f2df694b Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Mon, 3 May 2021 09:55:17 +0100 Subject: [PATCH 58/71] df output update (non inode mode) proposal specific for mac. on this platform, capacity column is also displayed. --- src/uu/df/src/df.rs | 8 ++++++++ tests/by-util/test_df.rs | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index e898b187c..c917eb2e8 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -916,6 +916,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { "Use%", ] }); + if cfg!(target_os = "macos") && !opt.show_inode_instead { + header.insert(header.len() - 1, "Capacity"); + } header.push("Mounted on"); for (idx, title) in header.iter().enumerate() { @@ -970,6 +973,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { "{0: >12} ", human_readable(free_size, opt.human_readable_base) ); + if cfg!(target_os = "macos") { + let used = fs.usage.blocks - fs.usage.bfree; + let blocks = used + fs.usage.bavail; + print!("{0: >12} ", use_size(used, blocks)); + } print!("{0: >5} ", use_size(free_size, total_size)); } print!("{0: <16}", fs.mountinfo.mount_dir); diff --git a/tests/by-util/test_df.rs b/tests/by-util/test_df.rs index 0ae8d2339..e3b7141d1 100644 --- a/tests/by-util/test_df.rs +++ b/tests/by-util/test_df.rs @@ -20,4 +20,16 @@ fn test_df_compatible_si() { new_ucmd!().arg("-aH").succeeds(); } +#[test] +fn test_df_output() { + if cfg!(target_os = "macos") { + new_ucmd!().arg("-H").arg("-total").succeeds(). + stdout_only("Filesystem Size Used Available Capacity Use% Mounted on \n"); + } else { + new_ucmd!().arg("-H").arg("-total").succeeds().stdout_only( + "Filesystem Size Used Available Use% Mounted on \n" + ); + } +} + // ToDO: more tests... From 5bcfa88f0ab75c32b5278150531b8affdce53f94 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 3 May 2021 23:09:45 +0200 Subject: [PATCH 59/71] stat: fix test to ignore selinux related output --- tests/by-util/test_stat.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 60d735c51..7b7e990f4 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -198,9 +198,16 @@ fn test_terse_normal_format() { let expect = expected_result(&args); println!("actual: {:?}", actual); println!("expect: {:?}", expect); - let v_actual: Vec<&str> = actual.split(' ').collect(); - let v_expect: Vec<&str> = expect.split(' ').collect(); + let v_actual: Vec<&str> = actual.trim().split(' ').collect(); + let mut v_expect: Vec<&str> = expect.trim().split(' ').collect(); assert!(!v_expect.is_empty()); + + // uu_stat does not support selinux + if v_actual.len() == v_expect.len() - 1 && v_expect[v_expect.len() - 1].contains(":") { + // assume last element contains: `SELinux security context string` + v_expect.pop(); + } + // * allow for inequality if `stat` (aka, expect) returns "0" (unknown value) assert!( expect == "0" From 0cafe2b70d463ec7c5a8ec2be6a42cdfd37e58f6 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Mon, 3 May 2021 20:52:32 -0400 Subject: [PATCH 60/71] wc: add tests for edge cases for wc on files --- tests/by-util/test_wc.rs | 57 ++++++++++++++ tests/fixtures/wc/emptyfile.txt | 0 tests/fixtures/wc/manyemptylines.txt | 100 ++++++++++++++++++++++++ tests/fixtures/wc/notrailingnewline.txt | 1 + tests/fixtures/wc/onelongemptyline.txt | 1 + tests/fixtures/wc/onelongword.txt | 1 + 6 files changed, 160 insertions(+) create mode 100644 tests/fixtures/wc/emptyfile.txt create mode 100644 tests/fixtures/wc/manyemptylines.txt create mode 100644 tests/fixtures/wc/notrailingnewline.txt create mode 100644 tests/fixtures/wc/onelongemptyline.txt create mode 100644 tests/fixtures/wc/onelongword.txt diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 075878470..a16f1854e 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -112,3 +112,60 @@ fn test_multiple_default() { alice_in_wonderland.txt\n 36 370 2189 total\n", ); } + +/// Test for an empty file. +#[test] +fn test_file_empty() { + // TODO There is a leading space in the output that should be + // removed; see issue #2173. + new_ucmd!() + .args(&["-clmwL", "emptyfile.txt"]) + .run() + .stdout_is(" 0 0 0 0 0 emptyfile.txt\n"); +} + +/// Test for an file containing a single non-whitespace character +/// *without* a trailing newline. +#[test] +fn test_file_single_line_no_trailing_newline() { + // TODO There is a leading space in the output that should be + // removed; see issue #2173. + new_ucmd!() + .args(&["-clmwL", "notrailingnewline.txt"]) + .run() + .stdout_is(" 1 1 2 2 1 notrailingnewline.txt\n"); +} + +/// Test for a file that has 100 empty lines (that is, the contents of +/// the file are the newline character repeated one hundred times). +#[test] +fn test_file_many_empty_lines() { + // TODO There is a leading space in the output that should be + // removed; see issue #2173. + new_ucmd!() + .args(&["-clmwL", "manyemptylines.txt"]) + .run() + .stdout_is(" 100 0 100 100 0 manyemptylines.txt\n"); +} + +/// Test for a file that has one long line comprising only spaces. +#[test] +fn test_file_one_long_line_only_spaces() { + // TODO There is a leading space in the output that should be + // removed; see issue #2173. + new_ucmd!() + .args(&["-clmwL", "onelongemptyline.txt"]) + .run() + .stdout_is(" 1 0 10001 10001 10000 onelongemptyline.txt\n"); +} + +/// Test for a file that has one long line comprising a single "word". +#[test] +fn test_file_one_long_word() { + // TODO There is a leading space in the output that should be + // removed; see issue #2173. + new_ucmd!() + .args(&["-clmwL", "onelongword.txt"]) + .run() + .stdout_is(" 1 1 10001 10001 10000 onelongword.txt\n"); +} diff --git a/tests/fixtures/wc/emptyfile.txt b/tests/fixtures/wc/emptyfile.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/wc/manyemptylines.txt b/tests/fixtures/wc/manyemptylines.txt new file mode 100644 index 000000000..716f02896 --- /dev/null +++ b/tests/fixtures/wc/manyemptylines.txt @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/wc/notrailingnewline.txt b/tests/fixtures/wc/notrailingnewline.txt new file mode 100644 index 000000000..789819226 --- /dev/null +++ b/tests/fixtures/wc/notrailingnewline.txt @@ -0,0 +1 @@ +a diff --git a/tests/fixtures/wc/onelongemptyline.txt b/tests/fixtures/wc/onelongemptyline.txt new file mode 100644 index 000000000..f93ac3c2c --- /dev/null +++ b/tests/fixtures/wc/onelongemptyline.txt @@ -0,0 +1 @@ + diff --git a/tests/fixtures/wc/onelongword.txt b/tests/fixtures/wc/onelongword.txt new file mode 100644 index 000000000..9d693a7ca --- /dev/null +++ b/tests/fixtures/wc/onelongword.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa From 6dff9f00a35d2893033330cb8f281357d6904fd9 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 4 May 2021 10:02:40 +0200 Subject: [PATCH 61/71] refresh cargo.lock with recent updates --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a33ab099..8d740bb9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1483,9 +1483,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad184cc9470f9117b2ac6817bfe297307418819ba40552f9b3846f05c33d5373" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ "proc-macro2", "quote 1.0.9", From 231bb7be93639576bdc553ae7a6fa2f7f5568ddc Mon Sep 17 00:00:00 2001 From: rethab Date: Wed, 5 May 2021 22:59:40 +0200 Subject: [PATCH 62/71] Migrate mknod to clap, closes #2051 (#2056) * mknod: add tests for fifo * mknod: add test for character device --- .gitignore | 1 + Cargo.lock | 2 +- src/uu/mknod/Cargo.toml | 2 +- src/uu/mknod/src/mknod.rs | 304 +++++++++++++++------------- src/uu/mknod/src/parsemode.rs | 54 +++++ src/uucore/src/lib/features/mode.rs | 33 ++- tests/by-util/test_mknod.rs | 125 +++++++++++- tests/common/util.rs | 16 +- 8 files changed, 371 insertions(+), 166 deletions(-) create mode 100644 src/uu/mknod/src/parsemode.rs diff --git a/.gitignore b/.gitignore index b1ac52506..11f46e13e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ target/ Cargo.lock lib*.a /docs/_build +*.iml diff --git a/Cargo.lock b/Cargo.lock index 6ff3cd5c1..62fa80c2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2116,7 +2116,7 @@ dependencies = [ name = "uu_mknod" version = "0.0.6" dependencies = [ - "getopts", + "clap", "libc", "uucore", "uucore_procs", diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 2c3ac8fb9..1320e3546 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] -getopts = "0.2.18" +clap = "2.33" 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/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index fc6fb0870..5b6c2fa8c 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -5,21 +5,41 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) parsemode makedev sysmacros makenod newmode perror IFBLK IFCHR IFIFO +// spell-checker:ignore (ToDO) parsemode makedev sysmacros perror IFBLK IFCHR IFIFO #[macro_use] extern crate uucore; +use std::ffi::CString; + +use clap::{App, Arg, ArgMatches}; use libc::{dev_t, mode_t}; use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; -use getopts::Options; - -use std::ffi::CString; use uucore::InvalidEncodingHandling; static NAME: &str = "mknod"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Create the special file NAME of the given TYPE."; +static USAGE: &str = "mknod [OPTION]... NAME TYPE [MAJOR MINOR]"; +static LONG_HELP: &str = "Mandatory arguments to long options are mandatory for short options too. +-m, --mode=MODE set file permission bits to MODE, not a=rw - umask +--help display this help and exit +--version output version information and exit + +Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they +must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X, +it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal; +otherwise, as decimal. TYPE may be: + +b create a block (buffered) special file +c, u create a character (unbuffered) special file +p create a FIFO + +NOTE: your shell may have its own version of mknod, which usually supersedes +the version described here. Please refer to your shell's documentation +for details about the options it supports. +"; const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; @@ -30,13 +50,35 @@ fn makedev(maj: u64, min: u64) -> dev_t { } #[cfg(windows)] -fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 { +fn _makenod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 { panic!("Unsupported for windows platform") } #[cfg(unix)] -fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 { - unsafe { libc::mknod(path.as_ptr(), mode, dev) } +fn _makenod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 { + let c_str = CString::new(file_name).expect("Failed to convert to CString"); + + // the user supplied a mode + let set_umask = mode & MODE_RW_UGO != MODE_RW_UGO; + + unsafe { + // store prev umask + let last_umask = if set_umask { libc::umask(0) } else { 0 }; + + let errno = libc::mknod(c_str.as_ptr(), mode, dev); + + // set umask back to original value + if set_umask { + libc::umask(last_umask); + } + + if errno == -1 { + let c_str = CString::new(NAME).expect("Failed to convert to CString"); + // shows the error from the mknod syscall + libc::perror(c_str.as_ptr()); + } + errno + } } #[allow(clippy::cognitive_complexity)] @@ -44,156 +86,136 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - - let mut opts = Options::new(); - // Linux-specific options, not implemented // 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"); - opts.optopt( - "m", - "mode", - "set file permission bits to MODE, not a=rw - umask", - "MODE", - ); - opts.optflag("", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); + let matches = App::new(executable!()) + .version(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 = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => crash!(1, "{}\nTry '{} --help' for more information.", f, NAME), + let mode = match get_mode(&matches) { + Ok(mode) => mode, + Err(err) => { + show_info!("{}", err); + return 1; + } }; - if matches.opt_present("help") { - println!( - "Usage: {0} [OPTION]... NAME TYPE [MAJOR MINOR] + let file_name = matches.value_of("name").expect("Missing argument 'NAME'"); -Mandatory arguments to long options are mandatory for short options too. - -m, --mode=MODE set file permission bits to MODE, not a=rw - umask - --help display this help and exit - --version output version information and exit + // Only check the first character, to allow mnemonic usage like + // 'mknod /dev/rst0 character 18 0'. + let ch = matches + .value_of("type") + .expect("Missing argument 'TYPE'") + .chars() + .next() + .expect("Failed to get the first char"); -Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they -must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X, -it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal; -otherwise, as decimal. TYPE may be: - - b create a block (buffered) special file - c, u create a character (unbuffered) special file - p create a FIFO - -NOTE: your shell may have its own version of mknod, which usually supersedes -the version described here. Please refer to your shell's documentation -for details about the options it supports.", - NAME - ); - return 0; - } - - if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - return 0; - } - - let mut last_umask: mode_t = 0; - let mut newmode: mode_t = MODE_RW_UGO; - if matches.opt_present("mode") { - match uucore::mode::parse_mode(matches.opt_str("mode")) { - Ok(parsed) => { - if parsed > 0o777 { - show_info!("mode must specify only file permission bits"); - return 1; - } - newmode = parsed; - } - Err(e) => { - show_info!("{}", e); - return 1; - } + if ch == 'p' { + if matches.is_present("major") || matches.is_present("minor") { + eprintln!("Fifos do not have major and minor device numbers."); + eprintln!("Try '{} --help' for more information.", NAME); + 1 + } else { + _makenod(file_name, S_IFIFO | mode, 0) } - unsafe { - last_umask = libc::umask(0); - } - } + } else { + match (matches.value_of("major"), matches.value_of("minor")) { + (None, None) | (_, None) | (None, _) => { + eprintln!("Special files require major and minor device numbers."); + eprintln!("Try '{} --help' for more information.", NAME); + 1 + } + (Some(major), Some(minor)) => { + let major = major.parse::().expect("validated by clap"); + let minor = minor.parse::().expect("validated by clap"); - let mut ret = 0i32; - match matches.free.len() { - 0 => show_usage_error!("missing operand"), - 1 => show_usage_error!("missing operand after ‘{}’", matches.free[0]), - _ => { - let args = &matches.free; - let c_str = CString::new(args[0].as_str()).expect("Failed to convert to CString"); - - // Only check the first character, to allow mnemonic usage like - // 'mknod /dev/rst0 character 18 0'. - let ch = args[1] - .chars() - .next() - .expect("Failed to get the first char"); - - if ch == 'p' { - if args.len() > 2 { - show_info!("{}: extra operand ‘{}’", NAME, args[2]); - if args.len() == 4 { - eprintln!("Fifos do not have major and minor device numbers."); - } - eprintln!("Try '{} --help' for more information.", NAME); - return 1; - } - - ret = _makenod(c_str, S_IFIFO | newmode, 0); - } else { - if args.len() < 4 { - show_info!("missing operand after ‘{}’", args[args.len() - 1]); - if args.len() == 2 { - eprintln!("Special files require major and minor device numbers."); - } - eprintln!("Try '{} --help' for more information.", NAME); - return 1; - } else if args.len() > 4 { - show_usage_error!("extra operand ‘{}’", args[4]); - return 1; - } else if !"bcu".contains(ch) { - show_usage_error!("invalid device type ‘{}’", args[1]); - return 1; - } - - let maj = args[2].parse::(); - let min = args[3].parse::(); - if maj.is_err() { - show_info!("invalid major device number ‘{}’", args[2]); - return 1; - } else if min.is_err() { - show_info!("invalid minor device number ‘{}’", args[3]); - return 1; - } - - let (maj, min) = (maj.unwrap(), min.unwrap()); - let dev = makedev(maj, min); + let dev = makedev(major, minor); if ch == 'b' { // block special file - ret = _makenod(c_str, S_IFBLK | newmode, dev); - } else { + _makenod(file_name, S_IFBLK | mode, dev) + } else if ch == 'c' || ch == 'u' { // char special file - ret = _makenod(c_str, S_IFCHR | newmode, dev); + _makenod(file_name, S_IFCHR | mode, dev) + } else { + unreachable!("{} was validated to be only b, c or u", ch); } } } } - - if last_umask != 0 { - unsafe { - libc::umask(last_umask); - } - } - if ret == -1 { - let c_str = CString::new(format!("{}: {}", NAME, matches.free[0]).as_str()) - .expect("Failed to convert to CString"); - unsafe { - libc::perror(c_str.as_ptr()); - } - } - - ret +} + +fn get_mode(matches: &ArgMatches) -> Result { + match matches.value_of("mode") { + None => Ok(MODE_RW_UGO), + Some(str_mode) => uucore::mode::parse_mode(str_mode) + .map_err(|e| format!("invalid mode ({})", e)) + .and_then(|mode| { + if mode > 0o777 { + Err("mode must specify only file permission bits".to_string()) + } else { + Ok(mode) + } + }), + } +} + +fn valid_type(tpe: String) -> Result<(), String> { + // Only check the first character, to allow mnemonic usage like + // 'mknod /dev/rst0 character 18 0'. + tpe.chars() + .next() + .ok_or_else(|| "missing device type".to_string()) + .and_then(|first_char| { + if vec!['b', 'c', 'u', 'p'].contains(&first_char) { + Ok(()) + } else { + Err(format!("invalid device type ‘{}’", tpe)) + } + }) +} + +fn valid_u64(num: String) -> Result<(), String> { + num.parse::().map(|_| ()).map_err(|_| num) } diff --git a/src/uu/mknod/src/parsemode.rs b/src/uu/mknod/src/parsemode.rs new file mode 100644 index 000000000..026fc4a56 --- /dev/null +++ b/src/uu/mknod/src/parsemode.rs @@ -0,0 +1,54 @@ +// spell-checker:ignore (ToDO) fperm + +use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; + +use uucore::mode; + +pub const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + +pub fn parse_mode(mode: &str) -> Result { + let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + let result = if mode.contains(arr) { + mode::parse_numeric(MODE_RW_UGO as u32, mode) + } else { + mode::parse_symbolic(MODE_RW_UGO as u32, mode, true) + }; + result.map(|mode| mode as mode_t) +} + +#[cfg(test)] +mod test { + /// Test if the program is running under WSL + // ref: @@ + // ToDO: test on WSL2 which likely doesn't need special handling; plan change to `is_wsl_1()` if WSL2 is less needy + pub fn is_wsl() -> bool { + #[cfg(target_os = "linux")] + { + if let Ok(b) = std::fs::read("/proc/sys/kernel/osrelease") { + if let Ok(s) = std::str::from_utf8(&b) { + let a = s.to_ascii_lowercase(); + return a.contains("microsoft") || a.contains("wsl"); + } + } + } + false + } + + #[test] + fn symbolic_modes() { + assert_eq!(super::parse_mode("u+x").unwrap(), 0o766); + assert_eq!( + super::parse_mode("+x").unwrap(), + if !is_wsl() { 0o777 } else { 0o776 } + ); + assert_eq!(super::parse_mode("a-w").unwrap(), 0o444); + assert_eq!(super::parse_mode("g-r").unwrap(), 0o626); + } + + #[test] + fn numeric_modes() { + assert_eq!(super::parse_mode("644").unwrap(), 0o644); + assert_eq!(super::parse_mode("+100").unwrap(), 0o766); + assert_eq!(super::parse_mode("-4").unwrap(), 0o662); + } +} diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index 1bb79ac03..4fb5a6509 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -132,19 +132,15 @@ fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) { (srwx, pos) } -pub fn parse_mode(mode: Option) -> Result { +pub fn parse_mode(mode: &str) -> Result { let fperm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - if let Some(mode) = mode { - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { - parse_numeric(fperm as u32, mode.as_str()) - } else { - parse_symbolic(fperm as u32, mode.as_str(), true) - }; - result.map(|mode| mode as mode_t) + let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + let result = if mode.contains(arr) { + parse_numeric(fperm as u32, mode) } else { - Ok(fperm) - } + parse_symbolic(fperm as u32, mode, true) + }; + result.map(|mode| mode as mode_t) } #[cfg(test)] @@ -152,20 +148,19 @@ mod test { #[test] fn symbolic_modes() { - assert_eq!(super::parse_mode(Some("u+x".to_owned())).unwrap(), 0o766); + assert_eq!(super::parse_mode("u+x").unwrap(), 0o766); assert_eq!( - super::parse_mode(Some("+x".to_owned())).unwrap(), + super::parse_mode("+x").unwrap(), if !crate::os::is_wsl_1() { 0o777 } else { 0o776 } ); - assert_eq!(super::parse_mode(Some("a-w".to_owned())).unwrap(), 0o444); - assert_eq!(super::parse_mode(Some("g-r".to_owned())).unwrap(), 0o626); + assert_eq!(super::parse_mode("a-w").unwrap(), 0o444); + assert_eq!(super::parse_mode("g-r").unwrap(), 0o626); } #[test] fn numeric_modes() { - assert_eq!(super::parse_mode(Some("644".to_owned())).unwrap(), 0o644); - assert_eq!(super::parse_mode(Some("+100".to_owned())).unwrap(), 0o766); - assert_eq!(super::parse_mode(Some("-4".to_owned())).unwrap(), 0o662); - assert_eq!(super::parse_mode(None).unwrap(), 0o666); + assert_eq!(super::parse_mode("644").unwrap(), 0o644); + assert_eq!(super::parse_mode("+100").unwrap(), 0o766); + assert_eq!(super::parse_mode("-4").unwrap(), 0o662); } } diff --git a/tests/by-util/test_mknod.rs b/tests/by-util/test_mknod.rs index 651491045..1d39372ac 100644 --- a/tests/by-util/test_mknod.rs +++ b/tests/by-util/test_mknod.rs @@ -1 +1,124 @@ -// ToDO: add tests +use crate::common::util::*; + +#[cfg(not(windows))] +#[test] +fn test_mknod_help() { + new_ucmd!() + .arg("--help") + .succeeds() + .no_stderr() + .stdout_contains("USAGE:"); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_version() { + assert!(new_ucmd!() + .arg("--version") + .succeeds() + .no_stderr() + .stdout_str() + .starts_with("mknod")); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_fifo_default_writable() { + let ts = TestScenario::new(util_name!()); + ts.ucmd().arg("test_file").arg("p").succeeds(); + assert!(ts.fixtures.is_fifo("test_file")); + assert!(!ts.fixtures.metadata("test_file").permissions().readonly()); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_fifo_mnemonic_usage() { + let ts = TestScenario::new(util_name!()); + ts.ucmd().arg("test_file").arg("pipe").succeeds(); + assert!(ts.fixtures.is_fifo("test_file")); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_fifo_read_only() { + let ts = TestScenario::new(util_name!()); + ts.ucmd() + .arg("-m") + .arg("a=r") + .arg("test_file") + .arg("p") + .succeeds(); + assert!(ts.fixtures.is_fifo("test_file")); + assert!(ts.fixtures.metadata("test_file").permissions().readonly()); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_fifo_invalid_extra_operand() { + new_ucmd!() + .arg("test_file") + .arg("p") + .arg("1") + .arg("2") + .fails() + .stderr_contains(&"Fifos do not have major and minor device numbers"); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_character_device_requires_major_and_minor() { + new_ucmd!() + .arg("test_file") + .arg("c") + .fails() + .status_code(1) + .stderr_contains(&"Special files require major and minor device numbers."); + new_ucmd!() + .arg("test_file") + .arg("c") + .arg("1") + .fails() + .status_code(1) + .stderr_contains(&"Special files require major and minor device numbers."); + new_ucmd!() + .arg("test_file") + .arg("c") + .arg("1") + .arg("c") + .fails() + .status_code(1) + .stderr_contains(&"Invalid value for ''"); + new_ucmd!() + .arg("test_file") + .arg("c") + .arg("c") + .arg("1") + .fails() + .status_code(1) + .stderr_contains(&"Invalid value for ''"); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_invalid_arg() { + new_ucmd!() + .arg("--foo") + .fails() + .status_code(1) + .no_stdout() + .stderr_contains(&"Found argument '--foo' which wasn't expected"); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_invalid_mode() { + new_ucmd!() + .arg("--mode") + .arg("rw") + .arg("test_file") + .arg("p") + .fails() + .no_stdout() + .status_code(1) + .stderr_contains(&"invalid mode"); +} diff --git a/tests/common/util.rs b/tests/common/util.rs index 1ade70127..719849afc 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -163,7 +163,7 @@ impl CmdResult { /// asserts that the command's exit code is the same as the given one pub fn status_code(&self, code: i32) -> &CmdResult { - assert!(self.code == Some(code)); + assert_eq!(self.code, Some(code)); self } @@ -295,12 +295,22 @@ impl CmdResult { } pub fn stdout_contains>(&self, cmp: T) -> &CmdResult { - assert!(self.stdout_str().contains(cmp.as_ref())); + assert!( + self.stdout_str().contains(cmp.as_ref()), + "'{}' does not contain '{}'", + self.stdout_str(), + cmp.as_ref() + ); self } pub fn stderr_contains>(&self, cmp: T) -> &CmdResult { - assert!(self.stderr_str().contains(cmp.as_ref())); + assert!( + self.stderr_str().contains(cmp.as_ref()), + "'{}' does not contain '{}'", + self.stderr_str(), + cmp.as_ref() + ); self } From 7d2b051866f77000d253de324ad69e3fbe31ce9d Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Thu, 6 May 2021 02:33:25 +0530 Subject: [PATCH 63/71] Implement Total size feature (#2170) * ls: Implement total size feature - Implement total size reporting that was missing - Fix minor formatting / readability nits * tests: Add tests for ls total sizes feature * ls: Fix MSRV build errors due to unsupported attributes for if blocks * ls: Add windows support for total sizes feature - Add windows support (defaults to file size as block sizes related infromation is not avialable on windows) - Renamed some functions --- Cargo.lock | 2 ++ src/uu/ls/src/ls.rs | 74 ++++++++++++++++++++++++++++------------ tests/by-util/test_ls.rs | 45 ++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62fa80c2d..3cd0c7cda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1692,6 +1692,7 @@ dependencies = [ name = "uu_basename" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] @@ -2645,6 +2646,7 @@ dependencies = [ name = "uu_who" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 0e2754f07..f24bf513e 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1179,31 +1179,32 @@ impl PathData { } fn list(locs: Vec, config: Config) -> i32 { - let number_of_locs = locs.len(); - let mut files = Vec::::new(); let mut dirs = Vec::::new(); let mut has_failed = false; let mut out = BufWriter::new(stdout()); - for loc in locs { + for loc in &locs { let p = PathBuf::from(&loc); if !p.exists() { 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 + /* + We found an error, the return code of ls should not be 0 + And no need to continue the execution + */ has_failed = true; continue; } let path_data = PathData::new(p, None, None, &config, true); - let show_dir_contents = if let Some(ft) = path_data.file_type() { - !config.directory && ft.is_dir() - } else { - has_failed = true; - false + let show_dir_contents = match path_data.file_type() { + Some(ft) => !config.directory && ft.is_dir(), + None => { + has_failed = true; + false + } }; if show_dir_contents { @@ -1217,7 +1218,7 @@ fn list(locs: Vec, config: Config) -> i32 { sort_entries(&mut dirs, &config); for dir in dirs { - if number_of_locs > 1 { + if locs.len() > 1 { let _ = writeln!(out, "\n{}:", dir.p_buf.display()); } enter_directory(&dir, &config, &mut out); @@ -1331,7 +1332,7 @@ fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) { if let Some(md) = entry.md() { ( display_symlink_count(&md).len(), - display_file_size(&md, config).len(), + display_size(md.len(), config).len(), ) } else { (0, 0) @@ -1344,14 +1345,22 @@ fn pad_left(string: String, count: usize) -> String { fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter) { if config.format == Format::Long { - let (mut max_links, mut max_size) = (1, 1); + let (mut max_links, mut max_width) = (1, 1); + let mut total_size = 0; + for item in items { - let (links, size) = display_dir_entry_size(item, config); + let (links, width) = display_dir_entry_size(item, config); max_links = links.max(max_links); - max_size = size.max(max_size); + max_width = width.max(max_width); + total_size += item.md().map_or(0, |md| get_block_size(md, config)); } + + if total_size > 0 { + let _ = writeln!(out, "total {}", display_size(total_size, config)); + } + for item in items { - display_item_long(item, max_links, max_size, config, out); + display_item_long(item, max_links, max_width, config, out); } } else { let names = items.iter().filter_map(|i| display_file_name(&i, config)); @@ -1396,6 +1405,29 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter u64 { + /* GNU ls will display sizes in terms of block size + md.len() will differ from this value when the file has some holes + */ + #[cfg(unix)] + { + // hard-coded for now - enabling setting this remains a TODO + let ls_block_size = 1024; + return match config.size_format { + SizeFormat::Binary => md.blocks() * 512, + SizeFormat::Decimal => md.blocks() * 512, + SizeFormat::Bytes => md.blocks() * 512 / ls_block_size, + }; + } + + #[cfg(not(unix))] + { + let _ = config; + // no way to get block size for windows, fall-back to file size + md.len() + } +} + fn display_grid( names: impl Iterator, width: u16, @@ -1471,7 +1503,7 @@ fn display_item_long( let _ = writeln!( out, " {} {} {}", - pad_left(display_file_size(&md, config), max_size), + pad_left(display_size(md.len(), config), max_size), 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 @@ -1626,13 +1658,13 @@ fn format_prefixed(prefixed: NumberPrefix) -> String { } } -fn display_file_size(metadata: &Metadata, config: &Config) -> String { +fn display_size(len: u64, config: &Config) -> String { // NOTE: The human-readable behaviour deviates from the GNU ls. // The GNU ls uses binary prefixes by default. match config.size_format { - SizeFormat::Binary => format_prefixed(NumberPrefix::binary(metadata.len() as f64)), - SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(metadata.len() as f64)), - SizeFormat::Bytes => metadata.len().to_string(), + SizeFormat::Binary => format_prefixed(NumberPrefix::binary(len as f64)), + SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(len as f64)), + SizeFormat::Bytes => len.to_string(), } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 110764aa5..0985ba719 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -5,6 +5,7 @@ use crate::common::util::*; extern crate regex; use self::regex::Regex; +use std::collections::HashMap; use std::path::Path; use std::thread::sleep; use std::time::Duration; @@ -308,6 +309,50 @@ fn test_ls_long() { } } +#[test] +fn test_ls_long_total_size() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch(&at.plus_as_string("test-long")); + at.append("test-long", "1"); + at.touch(&at.plus_as_string("test-long2")); + at.append("test-long2", "2"); + + let expected_prints: HashMap<_, _> = if cfg!(unix) { + [ + ("long_vanilla", "total 8"), + ("long_human_readable", "total 8.0K"), + ("long_si", "total 8.2k"), + ] + .iter() + .cloned() + .collect() + } else { + [ + ("long_vanilla", "total 2"), + ("long_human_readable", "total 2"), + ("long_si", "total 2"), + ] + .iter() + .cloned() + .collect() + }; + + for arg in &["-l", "--long", "--format=long", "--format=verbose"] { + let result = scene.ucmd().arg(arg).succeeds(); + result.stdout_contains(expected_prints["long_vanilla"]); + + for arg2 in &["-h", "--human-readable", "--si"] { + let result = scene.ucmd().arg(arg).arg(arg2).succeeds(); + result.stdout_contains(if *arg2 == "--si" { + expected_prints["long_si"] + } else { + expected_prints["long_human_readable"] + }); + } + } +} + #[test] fn test_ls_long_formats() { let scene = TestScenario::new(util_name!()); From a2658250fc12f7b1d55978afa3774dd8263955c9 Mon Sep 17 00:00:00 2001 From: jaggededgedjustice Date: Wed, 5 May 2021 22:12:17 +0100 Subject: [PATCH 64/71] Fix fmt crashing on subtracting unsigned numbers (#2178) --- src/uu/fmt/src/linebreak.rs | 2 +- tests/by-util/test_fmt.rs | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/uu/fmt/src/linebreak.rs b/src/uu/fmt/src/linebreak.rs index 50cb6f77f..fe9f8568e 100644 --- a/src/uu/fmt/src/linebreak.rs +++ b/src/uu/fmt/src/linebreak.rs @@ -296,7 +296,7 @@ fn find_kp_breakpoints<'a, T: Iterator>>( (0, 0.0) } else { compute_demerits( - (args.opts.goal - tlen) as isize, + args.opts.goal as isize - tlen as isize, stretch, w.word_nchars as isize, active.prev_rat, diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index 21a5f3396..a83fae58e 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -33,18 +33,16 @@ fn test_fmt_w_too_big() { "fmt: error: invalid width: '2501': Numerical result out of range" ); } -/* #[test] - Fails for now, see https://github.com/uutils/coreutils/issues/1501 +#[test] fn test_fmt_w() { let result = new_ucmd!() .arg("-w") .arg("10") .arg("one-word-per-line.txt") .run(); - //.stdout_is_fixture("call_graph.expected"); - assert_eq!(result.stdout_str().trim(), "this is a file with one word per line"); + //.stdout_is_fixture("call_graph.expected"); + assert_eq!( + result.stdout_str().trim(), + "this is\na file\nwith one\nword per\nline" + ); } - - -fmt is pretty broken in general, needs more works to have more tests - */ From 9f9735694db35ac47b4ad1b01b146c3d80f377d7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 5 May 2021 22:52:07 +0200 Subject: [PATCH 65/71] refresh cargo.lock with recent updates --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3cd0c7cda..a0169b412 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -618,7 +618,7 @@ checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.7", + "redox_syscall 0.2.8", "winapi 0.3.9", ] @@ -1259,9 +1259,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85dd92e586f7355c633911e11f77f3d12f04b1b1bd76a198bd34ae3af8341ef2" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" dependencies = [ "bitflags", ] @@ -1272,7 +1272,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" dependencies = [ - "redox_syscall 0.2.7", + "redox_syscall 0.2.8", ] [[package]] @@ -1537,7 +1537,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ "libc", "numtoa", - "redox_syscall 0.2.7", + "redox_syscall 0.2.8", "redox_termios", ] From 928fc59845d9854fd16ae324d4ed82882bec99bd Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 6 May 2021 10:43:48 +0200 Subject: [PATCH 66/71] Ignore test_lookup until issue #2181 is fixed --- tests/by-util/test_who.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index a5637f23a..8aeecfb55 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -162,6 +162,7 @@ fn test_users() { #[cfg(target_os = "linux")] #[test] +#[ignore] fn test_lookup() { for opt in vec!["--lookup"] { new_ucmd!() From cdd3998a445ea31a14320d27b97f1f8d74d3af4d Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 6 May 2021 14:10:16 +0200 Subject: [PATCH 67/71] gitignore: add ds_store files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 11f46e13e..77e8f717e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ Cargo.lock lib*.a /docs/_build *.iml +### macOS ### +.DS_Store From b24b9d501bfc84702f635669d703a7a7aa0156fc Mon Sep 17 00:00:00 2001 From: Idan Attias Date: Thu, 6 May 2021 10:52:35 +0300 Subject: [PATCH 68/71] logname: replace getopts with clap --- Cargo.lock | 1 + src/uu/logname/Cargo.toml | 1 + src/uu/logname/src/logname.rs | 16 ++++++++-------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0169b412..24c008040 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2068,6 +2068,7 @@ dependencies = [ name = "uu_logname" version = "0.0.6" dependencies = [ + "clap", "libc", "uucore", "uucore_procs", diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index 416f817d7..4aa4d68f4 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -16,6 +16,7 @@ path = "src/logname.rs" [dependencies] libc = "0.2.42" +clap = "2.33" 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/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 8c6a946f5..9f9319e65 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -13,7 +13,8 @@ extern crate uucore; use std::ffi::CStr; -use uucore::InvalidEncodingHandling; + +use clap::App; extern "C" { // POSIX requires using getlogin (or equivalent code) @@ -31,15 +32,14 @@ fn get_userlogin() -> Option { } } -static SYNTAX: &str = ""; static SUMMARY: &str = "Print user's login name"; -static LONG_HELP: &str = ""; +static VERSION: &str = env!("CARGO_PKG_VERSION"); -pub fn uumain(args: impl uucore::Args) -> i32 { - app!(SYNTAX, SUMMARY, LONG_HELP).parse( - args.collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(), - ); +pub fn uumain(_: impl uucore::Args) -> i32 { + let _ = App::new(executable!()) + .version(VERSION) + .about(SUMMARY) + .get_matches(); match get_userlogin() { Some(userlogin) => println!("{}", userlogin), From 41eb930292ba994b11e03dd700a0180f008c5d9b Mon Sep 17 00:00:00 2001 From: Idan Attias Date: Thu, 6 May 2021 11:06:38 +0300 Subject: [PATCH 69/71] logname: align profile --- src/uu/logname/src/logname.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 9f9319e65..ae0f93533 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -13,6 +13,7 @@ extern crate uucore; use std::ffi::CStr; +use uucore::InvalidEncodingHandling; use clap::App; @@ -35,10 +36,21 @@ fn get_userlogin() -> Option { static SUMMARY: &str = "Print user's login name"; static VERSION: &str = env!("CARGO_PKG_VERSION"); -pub fn uumain(_: impl uucore::Args) -> i32 { +fn get_usage() -> String { + format!("{0}", executable!()) +} + +pub fn uumain(args: impl uucore::Args) -> i32 { + let _ = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); + + let usage = get_usage(); + let _ = App::new(executable!()) .version(VERSION) .about(SUMMARY) + .usage(&usage[..]) .get_matches(); match get_userlogin() { From 34b9809223ac5d260fa9931da6206597bf49865a Mon Sep 17 00:00:00 2001 From: Idan Attias Date: Thu, 6 May 2021 11:59:58 +0300 Subject: [PATCH 70/71] logname: fix test & style warning --- src/uu/logname/src/logname.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index ae0f93533..14bf7ef3b 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -37,21 +37,20 @@ static SUMMARY: &str = "Print user's login name"; static VERSION: &str = env!("CARGO_PKG_VERSION"); fn get_usage() -> String { - format!("{0}", executable!()) + String::from(executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let _ = args + let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); let usage = get_usage(); - let _ = App::new(executable!()) .version(VERSION) .about(SUMMARY) .usage(&usage[..]) - .get_matches(); + .get_matches_from(args); match get_userlogin() { Some(userlogin) => println!("{}", userlogin), From 704c6865b1d3f5a3523b354b60ee30f8ee6c59f2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 7 May 2021 09:57:31 +0200 Subject: [PATCH 71/71] refresh cargo.lock with recent updates --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24c008040..2362342d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1277,9 +1277,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.3" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5f1ceb7f74abbce32601642fcf8e8508a8a8991e0621c7d750295b9095702b" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr 2.4.0", @@ -1312,9 +1312,9 @@ dependencies = [ [[package]] name = "retain_mut" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53552c6c49e1e13f1a203ef0080ab3bbef0beb570a528993e83df057a9d9bba1" +checksum = "e9c17925a9027d298a4603d286befe3f9dc0e8ed02523141914eb628798d6e5b" [[package]] name = "rust-ini"