diff --git a/GNUmakefile b/GNUmakefile index 89a4dca80..bb82925ec 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,4 +1,4 @@ -# spell-checker:ignore (misc) testsuite runtest (targets) busytest distclean manpages pkgs ; (vars/env) BINDIR BUILDDIR CARGOFLAGS DESTDIR DOCSDIR INSTALLDIR INSTALLEES MANDIR MULTICALL +# spell-checker:ignore (misc) testsuite runtest findstring (targets) busytest distclean manpages pkgs ; (vars/env) BINDIR BUILDDIR CARGOFLAGS DESTDIR DOCSDIR INSTALLDIR INSTALLEES MANDIR MULTICALL # Config options PROFILE ?= debug @@ -307,10 +307,12 @@ ifeq (${MULTICALL}, y) $(INSTALL) $(BUILDDIR)/coreutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)coreutils cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out coreutils, $(INSTALLEES)), \ ln -fs $(PROG_PREFIX)coreutils $(PROG_PREFIX)$(prog) &&) : + $(if $(findstring test,$(INSTALLEES)), ln -fs $(PROG_PREFIX)coreutils $(PROG_PREFIX)[) cat $(DOCSDIR)/_build/man/coreutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)coreutils.1.gz else $(foreach prog, $(INSTALLEES), \ $(INSTALL) $(BUILDDIR)/$(prog) $(INSTALLDIR_BIN)/$(PROG_PREFIX)$(prog);) + $(if $(findstring test,$(INSTALLEES)), $(INSTALL) $(BUILDDIR)/test $(INSTALLDIR_BIN)/$(PROG_PREFIX)[) endif $(foreach man, $(filter $(INSTALLEES), $(basename $(notdir $(wildcard $(DOCSDIR)/_build/man/*)))), \ cat $(DOCSDIR)/_build/man/$(man).1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)$(man).1.gz &&) : @@ -326,6 +328,7 @@ ifeq (${MULTICALL}, y) endif rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz) rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS)) + rm -f $(INSTALLDIR_BIN)/$(PROG_PREFIX)[ rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(PROG_PREFIX),$(PROGS)) rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(PROG_PREFIX),$(PROGS)) rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/$(PROG_PREFIX),$(addsuffix .fish,$(PROGS))) diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 63ee57272..8d85dc85e 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -5,6 +5,9 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[macro_use] extern crate uucore; diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 05167853c..437668947 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -31,6 +31,8 @@ use std::path::Path; use std::path::PathBuf; use std::str::FromStr; use std::time::{Duration, UNIX_EPOCH}; +use std::{error::Error, fmt::Display}; +use uucore::error::{UCustomError, UResult}; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::InvalidEncodingHandling; #[cfg(windows)] @@ -399,8 +401,61 @@ fn get_usage() -> String { ) } +#[derive(Debug)] +enum DuError { + InvalidMaxDepthArg(String), + SummarizeDepthConflict(String), + InvalidTimeStyleArg(String), + InvalidTimeArg(String), +} + +impl Display for DuError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DuError::InvalidMaxDepthArg(s) => write!(f, "invalid maximum depth '{}'", s), + DuError::SummarizeDepthConflict(s) => { + write!(f, "summarizing conflicts with --max-depth={}", s) + } + DuError::InvalidTimeStyleArg(s) => { + write!( + f, + "invalid argument '{}' for 'time style' +Valid arguments are: +- 'full-iso' +- 'long-iso' +- 'iso' +Try '{} --help' for more information.", + s, NAME + ) + } + DuError::InvalidTimeArg(s) => { + write!( + f, + "Invalid argument '{}' for --time. +'birth' and 'creation' arguments are not supported on this platform.", + s + ) + } + } + } +} + +impl Error for DuError {} + +impl UCustomError for DuError { + fn code(&self) -> i32 { + match self { + Self::InvalidMaxDepthArg(_) => 1, + Self::SummarizeDepthConflict(_) => 1, + Self::InvalidTimeStyleArg(_) => 1, + Self::InvalidTimeArg(_) => 1, + } + } +} + +#[uucore_procs::gen_uumain] #[allow(clippy::cognitive_complexity)] -pub fn uumain(args: impl uucore::Args) -> i32 { +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -411,19 +466,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let summarize = matches.is_present(options::SUMMARIZE); - let max_depth_str = matches.value_of(options::MAX_DEPTH); - let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::().ok()); - match (max_depth_str, max_depth) { - (Some(s), _) if summarize => { - show_error!("summarizing conflicts with --max-depth={}", s); - return 1; - } - (Some(s), None) => { - show_error!("invalid maximum depth '{}'", s); - return 1; - } - (Some(_), Some(_)) | (None, _) => { /* valid */ } - } + let max_depth = parse_depth(matches.value_of(options::MAX_DEPTH), summarize)?; let options = Options { all: matches.is_present(options::ALL), @@ -480,27 +523,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } }; - let time_format_str = match matches.value_of("time-style") { - Some(s) => match s { - "full-iso" => "%Y-%m-%d %H:%M:%S.%f %z", - "long-iso" => "%Y-%m-%d %H:%M", - "iso" => "%Y-%m-%d", - _ => { - show_error!( - "invalid argument '{}' for 'time style' -Valid arguments are: -- 'full-iso' -- 'long-iso' -- 'iso' -Try '{} --help' for more information.", - s, - NAME - ); - return 1; - } - }, - None => "%Y-%m-%d %H:%M", - }; + let time_format_str = parse_time_style(matches.value_of("time-style"))?; let line_separator = if matches.is_present(options::NULL) { "\0" @@ -534,18 +557,9 @@ Try '{} --help' for more information.", Some(s) => match s { "ctime" | "status" => stat.modified, "access" | "atime" | "use" => stat.accessed, - "birth" | "creation" => { - if let Some(time) = stat.created { - time - } else { - show_error!( - "Invalid argument '{}' for --time. -'birth' and 'creation' arguments are not supported on this platform.", - s - ); - return 1; - } - } + "birth" | "creation" => stat + .created + .ok_or_else(|| DuError::InvalidTimeArg(s.into()))?, // below should never happen as clap already restricts the values. _ => unreachable!("Invalid field for --time"), }, @@ -590,7 +604,28 @@ Try '{} --help' for more information.", print!("{}", line_separator); } - 0 + Ok(()) +} + +fn parse_time_style(s: Option<&str>) -> UResult<&str> { + match s { + Some(s) => match s { + "full-iso" => Ok("%Y-%m-%d %H:%M:%S.%f %z"), + "long-iso" => Ok("%Y-%m-%d %H:%M"), + "iso" => Ok("%Y-%m-%d"), + _ => Err(DuError::InvalidTimeStyleArg(s.into()).into()), + }, + None => Ok("%Y-%m-%d %H:%M"), + } +} + +fn parse_depth(max_depth_str: Option<&str>, summarize: bool) -> UResult> { + let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::().ok()); + match (max_depth_str, max_depth) { + (Some(s), _) if summarize => Err(DuError::SummarizeDepthConflict(s.into()).into()), + (Some(s), None) => Err(DuError::InvalidMaxDepthArg(s.into()).into()), + (Some(_), Some(_)) | (None, _) => Ok(max_depth), + } } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index b0f68968d..180c4d2e5 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -7,6 +7,9 @@ // spell-checker:ignore (ToDO) gethostid +// Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[macro_use] extern crate uucore; diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 69602fc0a..14f8b9df2 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -7,6 +7,9 @@ // spell-checker:ignore (ToDO) MAKEWORD addrs hashset +// Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[macro_use] extern crate uucore; @@ -14,6 +17,8 @@ use clap::{crate_version, App, Arg, ArgMatches}; use std::collections::hash_set::HashSet; use std::net::ToSocketAddrs; use std::str; +#[cfg(windows)] +use uucore::error::UUsageError; use uucore::error::{UResult, USimpleError}; #[cfg(windows)] @@ -37,7 +42,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { #[allow(deprecated)] let mut data = std::mem::uninitialized(); if WSAStartup(MAKEWORD(2, 2), &mut data as *mut _) != 0 { - return Err(USimpleError::new(1, format!("Failed to start Winsock 2.2"))); + return Err(UUsageError::new( + 1, + "Failed to start Winsock 2.2".to_string(), + )); } } let result = execute(args); diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 1ba5ee0b5..55bcdb77b 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1267,6 +1267,7 @@ pub fn uu_app() -> App<'static, 'static> { .help("sort by a key") .long_help(LONG_HELP_KEYS) .multiple(true) + .number_of_values(1) .takes_value(true), ) .arg( diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index dba840d3c..bed1472e2 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -10,12 +10,82 @@ mod parser; -use clap::{App, AppSettings}; +use clap::{crate_version, App, AppSettings}; use parser::{parse, Symbol}; use std::ffi::{OsStr, OsString}; use std::path::Path; use uucore::executable; +const USAGE: &str = "test EXPRESSION +or: test +or: [ EXPRESSION ] +or: [ ] +or: [ OPTION"; + +// We use after_help so that this comes after the usage string (it would come before if we used about) +const AFTER_HELP: &str = " +Exit with the status determined by EXPRESSION. + +An omitted EXPRESSION defaults to false. Otherwise, +EXPRESSION is true or false and sets exit status. It is one of: + + ( EXPRESSION ) EXPRESSION is true + ! EXPRESSION EXPRESSION is false + EXPRESSION1 -a EXPRESSION2 both EXPRESSION1 and EXPRESSION2 are true + EXPRESSION1 -o EXPRESSION2 either EXPRESSION1 or EXPRESSION2 is true + + -n STRING the length of STRING is nonzero + STRING equivalent to -n STRING + -z STRING the length of STRING is zero + STRING1 = STRING2 the strings are equal + STRING1 != STRING2 the strings are not equal + + INTEGER1 -eq INTEGER2 INTEGER1 is equal to INTEGER2 + INTEGER1 -ge INTEGER2 INTEGER1 is greater than or equal to INTEGER2 + INTEGER1 -gt INTEGER2 INTEGER1 is greater than INTEGER2 + INTEGER1 -le INTEGER2 INTEGER1 is less than or equal to INTEGER2 + INTEGER1 -lt INTEGER2 INTEGER1 is less than INTEGER2 + INTEGER1 -ne INTEGER2 INTEGER1 is not equal to INTEGER2 + + FILE1 -ef FILE2 FILE1 and FILE2 have the same device and inode numbers + FILE1 -nt FILE2 FILE1 is newer (modification date) than FILE2 + FILE1 -ot FILE2 FILE1 is older than FILE2 + + -b FILE FILE exists and is block special + -c FILE FILE exists and is character special + -d FILE FILE exists and is a directory + -e FILE FILE exists + -f FILE FILE exists and is a regular file + -g FILE FILE exists and is set-group-ID + -G FILE FILE exists and is owned by the effective group ID + -h FILE FILE exists and is a symbolic link (same as -L) + -k FILE FILE exists and has its sticky bit set + -L FILE FILE exists and is a symbolic link (same as -h) + -N FILE FILE exists and has been modified since it was last read + -O FILE FILE exists and is owned by the effective user ID + -p FILE FILE exists and is a named pipe + -r FILE FILE exists and read permission is granted + -s FILE FILE exists and has a size greater than zero + -S FILE FILE exists and is a socket + -t FD file descriptor FD is opened on a terminal + -u FILE FILE exists and its set-user-ID bit is set + -w FILE FILE exists and write permission is granted + -x FILE FILE exists and execute (or search) permission is granted + +Except for -h and -L, all FILE-related tests dereference symbolic links. +Beware that parentheses need to be escaped (e.g., by backslashes) for shells. +INTEGER may also be -l STRING, which evaluates to the length of STRING. + +NOTE: Binary -a and -o are inherently ambiguous. Use 'test EXPR1 && test +EXPR2' or 'test EXPR1 || test EXPR2' instead. + +NOTE: [ honors the --help and --version options, but test does not. +test treats each of those as it treats any other nonempty STRING. + +NOTE: your shell may have its own version of test and/or [, which usually supersedes +the version described here. Please refer to your shell's documentation +for details about the options it supports."; + pub fn uu_app() -> App<'static, 'static> { App::new(executable!()) .setting(AppSettings::DisableHelpFlags) @@ -30,8 +100,22 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { .to_string_lossy(); let mut args: Vec<_> = args.collect(); - // If invoked via name '[', matching ']' must be in the last arg - if binary_name == "[" { + if binary_name.ends_with('[') { + // If invoked as [ we should recognize --help and --version (but not -h or -v) + if args.len() == 1 && (args[0] == "--help" || args[0] == "--version") { + // Let clap pretty-print help and version + App::new(binary_name) + .version(crate_version!()) + .usage(USAGE) + .after_help(AFTER_HELP) + // Disable printing of -h and -v as valid alternatives for --help and --version, + // since we don't recognize -h and -v as help/version flags. + .setting(AppSettings::NeedsLongHelp) + .setting(AppSettings::NeedsLongVersion) + .get_matches_from(std::iter::once(program).chain(args.into_iter())); + return 0; + } + // If invoked via name '[', matching ']' must be in the last arg let last = args.pop(); if last != Some(OsString::from("]")) { eprintln!("[: missing ']'"); diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index dda859722..abd50d1b8 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -30,12 +30,16 @@ pub mod options { pub static OS: &str = "operating-system"; } -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "")))] const HOST_OS: &str = "GNU/Linux"; +#[cfg(all(target_os = "linux", not(any(target_env = "gnu", target_env = ""))))] +const HOST_OS: &str = "Linux"; #[cfg(target_os = "windows")] const HOST_OS: &str = "Windows NT"; #[cfg(target_os = "freebsd")] const HOST_OS: &str = "FreeBSD"; +#[cfg(target_os = "netbsd")] +const HOST_OS: &str = "NetBSD"; #[cfg(target_os = "openbsd")] const HOST_OS: &str = "OpenBSD"; #[cfg(target_vendor = "apple")] diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 1d41ddac5..4a1cc3fa9 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -951,3 +951,11 @@ fn test_conflict_check_out() { ); } } + +#[test] +fn test_key_takes_one_arg() { + new_ucmd!() + .args(&["-k", "2.3", "keys_open_ended.txt"]) + .succeeds() + .stdout_is_fixture("keys_open_ended.expected"); +} diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 1867927da..79c24651a 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -718,3 +718,21 @@ fn test_bracket_syntax_missing_right_bracket() { .status_code(2) .stderr_is("[: missing ']'"); } + +#[test] +fn test_bracket_syntax_help() { + let scenario = TestScenario::new("["); + let mut ucmd = scenario.ucmd(); + + ucmd.arg("--help").succeeds().stdout_contains("USAGE:"); +} + +#[test] +fn test_bracket_syntax_version() { + let scenario = TestScenario::new("["); + let mut ucmd = scenario.ucmd(); + + ucmd.arg("--version") + .succeeds() + .stdout_matches(&r"\[ \d+\.\d+\.\d+".parse().unwrap()); +} diff --git a/tests/by-util/test_uname.rs b/tests/by-util/test_uname.rs index de3f42a6b..adcaa1072 100644 --- a/tests/by-util/test_uname.rs +++ b/tests/by-util/test_uname.rs @@ -1,5 +1,10 @@ use crate::common::util::*; +#[test] +fn test_uname() { + new_ucmd!().succeeds(); +} + #[test] fn test_uname_compatible() { new_ucmd!().arg("-a").succeeds(); @@ -45,3 +50,60 @@ fn test_uname_kernel() { #[cfg(not(target_os = "linux"))] ucmd.arg("-o").succeeds(); } + +#[test] +fn test_uname_operating_system() { + #[cfg(target_vendor = "apple")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("Darwin\n"); + #[cfg(target_os = "freebsd")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("FreeBSD\n"); + #[cfg(target_os = "fuchsia")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("Fuchsia\n"); + #[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "")))] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("GNU/Linux\n"); + #[cfg(all(target_os = "linux", not(any(target_env = "gnu", target_env = ""))))] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("Linux\n"); + #[cfg(target_os = "netbsd")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("NetBSD\n"); + #[cfg(target_os = "openbsd")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("OpenBSD\n"); + #[cfg(target_os = "redox")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("Redox\n"); + #[cfg(target_os = "windows")] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("Windows NT\n"); +} + +#[test] +fn test_uname_help() { + new_ucmd!() + .arg("--help") + .succeeds() + .stdout_contains("system information"); +}