diff --git a/Cargo.lock b/Cargo.lock index 089d30554bf..06b23a0662a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,6 +218,7 @@ version = "0.0.6" dependencies = [ "atty", "chrono", + "clap", "conv", "filetime", "glob 0.3.0", @@ -484,25 +485,25 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c" +checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" dependencies = [ "bitflags", "crossterm_winapi", - "lazy_static", "libc", "mio", "parking_lot", "signal-hook", + "signal-hook-mio", "winapi 0.3.9", ] [[package]] name = "crossterm_winapi" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9" +checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" dependencies = [ "winapi 0.3.9", ] @@ -558,9 +559,9 @@ dependencies = [ [[package]] name = "dunce" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2641c4a7c0c4101df53ea572bffdc561c146f6c2eb09e4df02bc4811e3feeb4" +checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541" [[package]] name = "either" @@ -675,9 +676,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" +checksum = "f0fc1b9fa0e64ffb1aa5b95daa0f0f167734fd528b7c02eabc581d9d843649b1" dependencies = [ "aho-corasick", "bstr", @@ -759,9 +760,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" dependencies = [ "either", ] @@ -982,9 +983,9 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "once_cell" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "onig" @@ -1230,7 +1231,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", - "rand_chacha 0.3.0", + "rand_chacha 0.3.1", "rand_core 0.6.2", "rand_hc 0.3.0", ] @@ -1247,9 +1248,9 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core 0.6.2", @@ -1481,15 +1482,25 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.1.17" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" +checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" dependencies = [ "libc", - "mio", "signal-hook-registry", ] +[[package]] +name = "signal-hook-mio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1557,9 +1568,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" dependencies = [ "proc-macro2", "quote 1.0.9", @@ -1959,6 +1970,7 @@ dependencies = [ name = "uu_expr" version = "0.0.6" dependencies = [ + "clap", "libc", "num-bigint", "num-traits", @@ -1986,6 +1998,7 @@ dependencies = [ name = "uu_false" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] @@ -2051,6 +2064,7 @@ dependencies = [ name = "uu_hostid" version = "0.0.6" dependencies = [ + "clap", "libc", "uucore", "uucore_procs", @@ -2327,8 +2341,9 @@ name = "uu_pr" version = "0.0.6" dependencies = [ "chrono", + "clap", "getopts", - "itertools 0.10.0", + "itertools 0.10.1", "quick-error 2.0.1", "regex", "time", @@ -2349,6 +2364,7 @@ dependencies = [ name = "uu_printf" version = "0.0.6" dependencies = [ + "clap", "itertools 0.8.2", "uucore", "uucore_procs", @@ -2477,7 +2493,7 @@ dependencies = [ "clap", "compare", "fnv", - "itertools 0.10.0", + "itertools 0.10.1", "memchr 2.4.0", "ouroboros", "rand 0.7.3", @@ -2585,6 +2601,7 @@ dependencies = [ name = "uu_test" version = "0.0.6" dependencies = [ + "clap", "libc", "redox_syscall 0.1.57", "uucore", @@ -2627,6 +2644,7 @@ dependencies = [ name = "uu_true" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] diff --git a/Cargo.toml b/Cargo.toml index 804c5f97821..94d9c4aec7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -225,6 +225,7 @@ test = [ "uu_test" ] [workspace] [dependencies] +clap = "2.33.3" lazy_static = { version="1.3" } textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review uucore = { version=">=0.0.8", package="uucore", path="src/uucore" } diff --git a/GNUmakefile b/GNUmakefile index e5ad01340cb..ea9c7254a6b 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -314,6 +314,11 @@ else endif $(foreach man, $(filter $(INSTALLEES), $(basename $(notdir $(wildcard $(DOCSDIR)/_build/man/*)))), \ cat $(DOCSDIR)/_build/man/$(man).1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)$(man).1.gz &&) : + $(foreach prog, $(INSTALLEES), \ + $(BUILDDIR)/coreutils completion $(prog) zsh > $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(PROG_PREFIX)$(prog); \ + $(BUILDDIR)/coreutils completion $(prog) bash > $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(PROG_PREFIX)$(prog); \ + $(BUILDDIR)/coreutils completion $(prog) fish > $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/$(PROG_PREFIX)$(prog).fish; \ + ) uninstall: ifeq (${MULTICALL}, y) diff --git a/build.rs b/build.rs index ae38177b049..0ec901497dd 100644 --- a/build.rs +++ b/build.rs @@ -41,6 +41,7 @@ pub fn main() { let mut mf = File::create(Path::new(&out_dir).join("uutils_map.rs")).unwrap(); let mut tf = File::create(Path::new(&out_dir).join("test_modules.rs")).unwrap(); + let mut af = File::create(Path::new(&out_dir).join("apps_map.rs")).unwrap(); mf.write_all( "type UtilityMap = HashMap<&'static str, fn(T) -> i32>;\n\ @@ -52,6 +53,16 @@ pub fn main() { ) .unwrap(); + af.write_all( + "type AppMap = HashMap<&'static str, fn(&str) -> App>;\n\ + \n\ + fn app_map() -> AppMap {\n\ + \tlet mut map = AppMap::new();\n\ + " + .as_bytes(), + ) + .unwrap(); + for krate in crates { match krate.as_ref() { k if k.starts_with(override_prefix) => { @@ -64,6 +75,15 @@ pub fn main() { .as_bytes(), ) .unwrap(); + af.write_all( + format!( + "\tmap.insert(\"{k}\", {krate}::app::get_app);\n", + k = krate[override_prefix.len()..].to_string(), + krate = krate + ) + .as_bytes(), + ) + .unwrap(); tf.write_all( format!( "#[path=\"{dir}/test_{k}.rs\"]\nmod test_{k};\n", @@ -83,6 +103,14 @@ pub fn main() { .as_bytes(), ) .unwrap(); + af.write_all( + format!( + "\tmap.insert(\"{krate}\", r#{krate}::app::get_app);\n", + krate = krate + ) + .as_bytes(), + ) + .unwrap(); tf.write_all( format!( "#[path=\"{dir}/test_{krate}.rs\"]\nmod test_{krate};\n", @@ -117,6 +145,29 @@ pub fn main() { .as_bytes(), ) .unwrap(); + af.write_all( + format!( + "\ + \tmap.insert(\"{krate}\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"md5sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"sha1sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"sha224sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"sha256sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"sha384sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"sha512sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"sha3sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"sha3-224sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"sha3-256sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"sha3-384sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"sha3-512sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"shake128sum\", {krate}::app::get_app);\n\ + \t\tmap.insert(\"shake256sum\", {krate}::app::get_app);\n\ + ", + krate = krate + ) + .as_bytes(), + ) + .unwrap(); tf.write_all( format!( "#[path=\"{dir}/test_{krate}.rs\"]\nmod test_{krate};\n", @@ -136,6 +187,14 @@ pub fn main() { .as_bytes(), ) .unwrap(); + af.write_all( + format!( + "\tmap.insert(\"{krate}\", {krate}::app::get_app);\n", + krate = krate + ) + .as_bytes(), + ) + .unwrap(); tf.write_all( format!( "#[path=\"{dir}/test_{krate}.rs\"]\nmod test_{krate};\n", @@ -150,7 +209,9 @@ pub fn main() { } mf.write_all(b"map\n}\n").unwrap(); + af.write_all(b"map\n}\n").unwrap(); mf.flush().unwrap(); + af.flush().unwrap(); tf.flush().unwrap(); } diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 2e703b68243..b3de86af0d0 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -5,6 +5,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use clap::{App, Shell}; use std::cmp; use std::collections::hash_map::HashMap; use std::ffi::OsString; @@ -15,6 +16,7 @@ use std::process; const VERSION: &str = env!("CARGO_PKG_VERSION"); include!(concat!(env!("OUT_DIR"), "/uutils_map.rs")); +include!(concat!(env!("OUT_DIR"), "/apps_map.rs")); fn usage(utils: &UtilityMap, name: &str) { println!("{} {} (multi-call binary)\n", name, VERSION); @@ -74,6 +76,10 @@ fn main() { if let Some(util_os) = util_name { let util = util_os.as_os_str().to_string_lossy(); + if util == "completion" { + gen_completions(args); + } + match utils.get(&util[..]) { Some(&uumain) => { process::exit(uumain((vec![util_os].into_iter()).chain(args))); @@ -113,3 +119,41 @@ fn main() { process::exit(0); } } + +/// Prints completions for the utility in the first parameter for the shell in the second parameter to stdout +fn gen_completions(mut args: impl Iterator) -> ! { + let app_map = app_map(); + let utility = args + .next() + .expect("expected utility as the first parameter") + .to_str() + .expect("utility name was not valid utf-8") + .to_owned(); + let shell = args + .next() + .expect("expected shell as the second parameter") + .to_str() + .expect("shell name was not valid utf-8") + .to_owned(); + let mut app = if utility == "coreutils" { + gen_coreutils_app() + } else if let Some(app) = app_map.get(utility.as_str()) { + app(&utility) + } else { + eprintln!("{} is not a valid utility", utility); + process::exit(1) + }; + let shell: Shell = shell.parse().unwrap(); + let bin_name = std::env::var("PROG_PREFIX").unwrap_or_default() + &utility; + app.gen_completions_to(bin_name, shell, &mut io::stdout()); + io::stdout().flush().unwrap(); + process::exit(0); +} + +fn gen_coreutils_app<'a, 'b>() -> App<'a, 'b> { + let mut app = App::new("coreutils"); + for (name, sub_app) in app_map() { + app = app.subcommand(sub_app(name)); + } + app +} diff --git a/src/uu/arch/src/app.rs b/src/uu/arch/src/app.rs new file mode 100644 index 00000000000..209285fbbca --- /dev/null +++ b/src/uu/arch/src/app.rs @@ -0,0 +1,11 @@ +use clap::{crate_version, App}; + +const ABOUT: &str = "Display machine architecture"; +const SUMMARY: &str = "Determine architecture name for current machine."; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .after_help(SUMMARY) +} diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index eddd24502f2..6815d1deebb 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -6,22 +6,17 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +pub mod app; + #[macro_use] extern crate uucore; use platform_info::*; -use clap::{crate_version, App}; - -static ABOUT: &str = "Display machine architecture"; -static SUMMARY: &str = "Determine architecture name for current machine."; +use crate::app::get_app; pub fn uumain(args: impl uucore::Args) -> i32 { - App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .after_help(SUMMARY) - .get_matches_from(args); + get_app(executable!()).get_matches_from(args); let uts = return_if_err!(1, PlatformInfo::new()); println!("{}", uts.machine().trim()); diff --git a/src/uu/base32/src/app.rs b/src/uu/base32/src/app.rs new file mode 100644 index 00000000000..88ed49501ff --- /dev/null +++ b/src/uu/base32/src/app.rs @@ -0,0 +1,36 @@ +use clap::{App, Arg}; + +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"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .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)) +} diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index e6a01cb3457..3a7a5a391a1 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -12,6 +12,7 @@ use std::io::{stdin, Read}; use uucore::encoding::Format; +pub mod app; pub mod base_common; static ABOUT: &str = " diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 256b674e26c..5c4cad00040 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -16,7 +16,8 @@ use std::fs::File; use std::io::{BufReader, Stdin}; use std::path::Path; -use clap::{App, Arg}; +pub use crate::app::get_app; +use crate::app::options; // Config. pub struct Config { @@ -26,13 +27,6 @@ pub struct Config { 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) { @@ -78,35 +72,7 @@ pub fn parse_base_cmd_args( 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 app = get_app(name).version(version).about(about).usage(usage); let arg_list = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 0dd83102778..a89522af466 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -15,6 +15,8 @@ use uucore::encoding::Format; use std::io::{stdin, Read}; +pub use uu_base32::app; + static ABOUT: &str = " With no FILE, or when FILE is -, read standard input. diff --git a/src/uu/basename/src/app.rs b/src/uu/basename/src/app.rs new file mode 100644 index 00000000000..e9c458625ed --- /dev/null +++ b/src/uu/basename/src/app.rs @@ -0,0 +1,37 @@ +use clap::{crate_version, App, Arg}; + +const SUMMARY: &str = "Print NAME with any leading directory components removed +If specified, also remove a trailing SUFFIX"; + +pub mod options { + pub const MULTIPLE: &str = "multiple"; + pub const NAME: &str = "name"; + pub const SUFFIX: &str = "suffix"; + pub const ZERO: &str = "zero"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(SUMMARY) + .arg( + Arg::with_name(options::MULTIPLE) + .short("a") + .long(options::MULTIPLE) + .help("support multiple arguments and treat each as a NAME"), + ) + .arg(Arg::with_name(options::NAME).multiple(true).hidden(true)) + .arg( + Arg::with_name(options::SUFFIX) + .short("s") + .long(options::SUFFIX) + .value_name("SUFFIX") + .help("remove a trailing SUFFIX; implies -a"), + ) + .arg( + Arg::with_name(options::ZERO) + .short("z") + .long(options::ZERO) + .help("end each output line with NUL, not newline"), + ) +} diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 098a3e2b235..21dfb3ad42c 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -10,12 +10,12 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; +pub mod app; + use std::path::{is_separator, PathBuf}; use uucore::InvalidEncodingHandling; -static SUMMARY: &str = "Print NAME with any leading directory components removed -If specified, also remove a trailing SUFFIX"; +use crate::app::get_app; fn get_usage() -> String { format!( @@ -40,30 +40,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // // Argument parsing // - let matches = App::new(executable!()) - .version(crate_version!()) - .about(SUMMARY) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(options::MULTIPLE) - .short("a") - .long(options::MULTIPLE) - .help("support multiple arguments and treat each as a NAME"), - ) - .arg(Arg::with_name(options::NAME).multiple(true).hidden(true)) - .arg( - Arg::with_name(options::SUFFIX) - .short("s") - .long(options::SUFFIX) - .value_name("SUFFIX") - .help("remove a trailing SUFFIX; implies -a"), - ) - .arg( - Arg::with_name(options::ZERO) - .short("z") - .long(options::ZERO) - .help("end each output line with NUL, not newline"), - ) .get_matches_from(args); // too few arguments diff --git a/src/uu/cat/src/app.rs b/src/uu/cat/src/app.rs new file mode 100644 index 00000000000..3331331aff4 --- /dev/null +++ b/src/uu/cat/src/app.rs @@ -0,0 +1,83 @@ +use clap::{crate_version, App, Arg}; + +// spell-checker:ignore (ToDO) nonprint nonblank nonprinting + +const NAME: &str = "cat"; +const SYNTAX: &str = "[OPTION]... [FILE]..."; +const SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output + With no FILE, or when FILE is -, read standard input."; + +pub mod options { + pub const FILE: &str = "file"; + pub const SHOW_ALL: &str = "show-all"; + pub const NUMBER_NONBLANK: &str = "number-nonblank"; + pub const SHOW_NONPRINTING_ENDS: &str = "e"; + pub const SHOW_ENDS: &str = "show-ends"; + pub const NUMBER: &str = "number"; + pub const SQUEEZE_BLANK: &str = "squeeze-blank"; + pub const SHOW_NONPRINTING_TABS: &str = "t"; + pub const SHOW_TABS: &str = "show-tabs"; + pub const SHOW_NONPRINTING: &str = "show-nonprinting"; +} +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .name(NAME) + .version(crate_version!()) + .usage(SYNTAX) + .about(SUMMARY) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .arg( + Arg::with_name(options::SHOW_ALL) + .short("A") + .long(options::SHOW_ALL) + .help("equivalent to -vET"), + ) + .arg( + Arg::with_name(options::NUMBER_NONBLANK) + .short("b") + .long(options::NUMBER_NONBLANK) + .help("number nonempty output lines, overrides -n") + .overrides_with(options::NUMBER), + ) + .arg( + Arg::with_name(options::SHOW_NONPRINTING_ENDS) + .short("e") + .help("equivalent to -vE"), + ) + .arg( + Arg::with_name(options::SHOW_ENDS) + .short("E") + .long(options::SHOW_ENDS) + .help("display $ at end of each line"), + ) + .arg( + Arg::with_name(options::NUMBER) + .short("n") + .long(options::NUMBER) + .help("number all output lines"), + ) + .arg( + Arg::with_name(options::SQUEEZE_BLANK) + .short("s") + .long(options::SQUEEZE_BLANK) + .help("suppress repeated empty output lines"), + ) + .arg( + Arg::with_name(options::SHOW_NONPRINTING_TABS) + .short("t") + .long(options::SHOW_NONPRINTING_TABS) + .help("equivalent to -vT"), + ) + .arg( + Arg::with_name(options::SHOW_TABS) + .short("T") + .long(options::SHOW_TABS) + .help("display TAB characters at ^I"), + ) + .arg( + Arg::with_name(options::SHOW_NONPRINTING) + .short("v") + .long(options::SHOW_NONPRINTING) + .help("use ^ and M- notation, except for LF (\\n) and TAB (\\t)"), + ) +} diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 889ba424a0e..32499c915c5 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -16,11 +16,12 @@ extern crate unix_socket; extern crate uucore; // last synced with: cat (GNU coreutils) 8.13 -use clap::{crate_version, App, Arg}; use std::fs::{metadata, File}; use std::io::{self, Read, Write}; use thiserror::Error; +pub mod app; + /// Linux splice support #[cfg(any(target_os = "linux", target_os = "android"))] mod splice; @@ -36,10 +37,7 @@ use std::os::unix::fs::FileTypeExt; use unix_socket::UnixStream; use uucore::InvalidEncodingHandling; -static NAME: &str = "cat"; -static SYNTAX: &str = "[OPTION]... [FILE]..."; -static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output - With no FILE, or when FILE is -, read standard input."; +use crate::app::{get_app, options}; #[derive(Error, Debug)] enum CatError { @@ -151,85 +149,12 @@ enum InputType { Socket, } -mod options { - pub static FILE: &str = "file"; - pub static SHOW_ALL: &str = "show-all"; - pub static NUMBER_NONBLANK: &str = "number-nonblank"; - pub static SHOW_NONPRINTING_ENDS: &str = "e"; - pub static SHOW_ENDS: &str = "show-ends"; - pub static NUMBER: &str = "number"; - pub static SQUEEZE_BLANK: &str = "squeeze-blank"; - pub static SHOW_NONPRINTING_TABS: &str = "t"; - pub static SHOW_TABS: &str = "show-tabs"; - pub static SHOW_NONPRINTING: &str = "show-nonprinting"; -} - pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(SYNTAX) - .about(SUMMARY) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .arg( - Arg::with_name(options::SHOW_ALL) - .short("A") - .long(options::SHOW_ALL) - .help("equivalent to -vET"), - ) - .arg( - Arg::with_name(options::NUMBER_NONBLANK) - .short("b") - .long(options::NUMBER_NONBLANK) - .help("number nonempty output lines, overrides -n") - .overrides_with(options::NUMBER), - ) - .arg( - Arg::with_name(options::SHOW_NONPRINTING_ENDS) - .short("e") - .help("equivalent to -vE"), - ) - .arg( - Arg::with_name(options::SHOW_ENDS) - .short("E") - .long(options::SHOW_ENDS) - .help("display $ at end of each line"), - ) - .arg( - Arg::with_name(options::NUMBER) - .short("n") - .long(options::NUMBER) - .help("number all output lines"), - ) - .arg( - Arg::with_name(options::SQUEEZE_BLANK) - .short("s") - .long(options::SQUEEZE_BLANK) - .help("suppress repeated empty output lines"), - ) - .arg( - Arg::with_name(options::SHOW_NONPRINTING_TABS) - .short("t") - .long(options::SHOW_NONPRINTING_TABS) - .help("equivalent to -vT"), - ) - .arg( - Arg::with_name(options::SHOW_TABS) - .short("T") - .long(options::SHOW_TABS) - .help("display TAB characters at ^I"), - ) - .arg( - Arg::with_name(options::SHOW_NONPRINTING) - .short("v") - .long(options::SHOW_NONPRINTING) - .help("use ^ and M- notation, except for LF (\\n) and TAB (\\t)"), - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let number_mode = if matches.is_present(options::NUMBER_NONBLANK) { NumberingMode::NonEmpty diff --git a/src/uu/chgrp/src/app.rs b/src/uu/chgrp/src/app.rs new file mode 100644 index 00000000000..e63ede2e4f0 --- /dev/null +++ b/src/uu/chgrp/src/app.rs @@ -0,0 +1,111 @@ +// spell-checker:ignore (ToDO) RFILE RFILE's + +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Change the group of each FILE to GROUP."; + +pub mod options { + pub mod verbosity { + pub static CHANGES: &str = "changes"; + pub static QUIET: &str = "quiet"; + pub static SILENT: &str = "silent"; + pub static VERBOSE: &str = "verbose"; + } + pub mod preserve_root { + pub static PRESERVE: &str = "preserve-root"; + pub static NO_PRESERVE: &str = "no-preserve-root"; + } + pub mod dereference { + pub static DEREFERENCE: &str = "dereference"; + pub static NO_DEREFERENCE: &str = "no-dereference"; + } + pub static RECURSIVE: &str = "recursive"; + pub mod traverse { + pub static TRAVERSE: &str = "H"; + pub static NO_TRAVERSE: &str = "P"; + pub static EVERY: &str = "L"; + } + pub static REFERENCE: &str = "reference"; + pub static ARG_GROUP: &str = "GROUP"; + pub static ARG_FILES: &str = "FILE"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::verbosity::CHANGES) + .short("c") + .long(options::verbosity::CHANGES) + .help("like verbose but report only when a change is made"), + ) + .arg( + Arg::with_name(options::verbosity::SILENT) + .short("f") + .long(options::verbosity::SILENT), + ) + .arg( + Arg::with_name(options::verbosity::QUIET) + .long(options::verbosity::QUIET) + .help("suppress most error messages"), + ) + .arg( + Arg::with_name(options::verbosity::VERBOSE) + .short("v") + .long(options::verbosity::VERBOSE) + .help("output a diagnostic for every file processed"), + ) + .arg( + Arg::with_name(options::dereference::DEREFERENCE) + .long(options::dereference::DEREFERENCE), + ) + .arg( + Arg::with_name(options::dereference::NO_DEREFERENCE) + .short("h") + .long(options::dereference::NO_DEREFERENCE) + .help( + "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", + ), + ) + .arg( + Arg::with_name(options::preserve_root::PRESERVE) + .long(options::preserve_root::PRESERVE) + .help("fail to operate recursively on '/'"), + ) + .arg( + Arg::with_name(options::preserve_root::NO_PRESERVE) + .long(options::preserve_root::NO_PRESERVE) + .help("do not treat '/' specially (the default)"), + ) + .arg( + Arg::with_name(options::REFERENCE) + .long(options::REFERENCE) + .value_name("RFILE") + .help("use RFILE's group rather than specifying GROUP values") + .takes_value(true) + .multiple(false), + ) + .arg( + Arg::with_name(options::RECURSIVE) + .short("R") + .long(options::RECURSIVE) + .help("operate on files and directories recursively"), + ) + .arg( + Arg::with_name(options::traverse::TRAVERSE) + .short(options::traverse::TRAVERSE) + .help("if a command line argument is a symbolic link to a directory, traverse it"), + ) + .arg( + Arg::with_name(options::traverse::NO_TRAVERSE) + .short(options::traverse::NO_TRAVERSE) + .help("do not traverse any symbolic links (default)") + .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]), + ) + .arg( + Arg::with_name(options::traverse::EVERY) + .short(options::traverse::EVERY) + .help("traverse every symbolic link to a directory encountered"), + ) +} diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 454a0386c7e..05e5e8fdca3 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -7,14 +7,17 @@ // spell-checker:ignore (ToDO) COMFOLLOW Chgrper RFILE RFILE's derefer dgid nonblank nonprint nonprinting +pub mod app; + #[macro_use] extern crate uucore; +use app::get_app; pub use uucore::entries; use uucore::fs::resolve_relative_path; use uucore::libc::gid_t; use uucore::perms::{wrap_chgrp, Verbosity}; -use clap::{App, Arg}; +use clap::Arg; extern crate walkdir; use walkdir::WalkDir; @@ -26,34 +29,7 @@ use std::os::unix::fs::MetadataExt; use std::path::Path; use uucore::InvalidEncodingHandling; -static ABOUT: &str = "Change the group of each FILE to GROUP."; -static VERSION: &str = env!("CARGO_PKG_VERSION"); - -pub mod options { - pub mod verbosity { - pub static CHANGES: &str = "changes"; - pub static QUIET: &str = "quiet"; - pub static SILENT: &str = "silent"; - pub static VERBOSE: &str = "verbose"; - } - pub mod preserve_root { - pub static PRESERVE: &str = "preserve-root"; - pub static NO_PRESERVE: &str = "no-preserve-root"; - } - pub mod dereference { - pub static DEREFERENCE: &str = "dereference"; - pub static NO_DEREFERENCE: &str = "no-dereference"; - } - pub static RECURSIVE: &str = "recursive"; - pub mod traverse { - pub static TRAVERSE: &str = "H"; - pub static NO_TRAVERSE: &str = "P"; - pub static EVERY: &str = "L"; - } - pub static REFERENCE: &str = "reference"; - pub static ARG_GROUP: &str = "GROUP"; - pub static ARG_FILES: &str = "FILE"; -} +use crate::app::options; const FTS_COMFOLLOW: u8 = 1; const FTS_PHYSICAL: u8 = 1 << 1; @@ -73,84 +49,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let mut app = App::new(executable!()) - .version(VERSION) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::verbosity::CHANGES) - .short("c") - .long(options::verbosity::CHANGES) - .help("like verbose but report only when a change is made"), - ) - .arg( - Arg::with_name(options::verbosity::SILENT) - .short("f") - .long(options::verbosity::SILENT), - ) - .arg( - Arg::with_name(options::verbosity::QUIET) - .long(options::verbosity::QUIET) - .help("suppress most error messages"), - ) - .arg( - Arg::with_name(options::verbosity::VERBOSE) - .short("v") - .long(options::verbosity::VERBOSE) - .help("output a diagnostic for every file processed"), - ) - .arg( - Arg::with_name(options::dereference::DEREFERENCE) - .long(options::dereference::DEREFERENCE), - ) - .arg( - Arg::with_name(options::dereference::NO_DEREFERENCE) - .short("h") - .long(options::dereference::NO_DEREFERENCE) - .help( - "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", - ), - ) - .arg( - Arg::with_name(options::preserve_root::PRESERVE) - .long(options::preserve_root::PRESERVE) - .help("fail to operate recursively on '/'"), - ) - .arg( - Arg::with_name(options::preserve_root::NO_PRESERVE) - .long(options::preserve_root::NO_PRESERVE) - .help("do not treat '/' specially (the default)"), - ) - .arg( - Arg::with_name(options::REFERENCE) - .long(options::REFERENCE) - .value_name("RFILE") - .help("use RFILE's group rather than specifying GROUP values") - .takes_value(true) - .multiple(false), - ) - .arg( - Arg::with_name(options::RECURSIVE) - .short("R") - .long(options::RECURSIVE) - .help("operate on files and directories recursively"), - ) - .arg( - Arg::with_name(options::traverse::TRAVERSE) - .short(options::traverse::TRAVERSE) - .help("if a command line argument is a symbolic link to a directory, traverse it"), - ) - .arg( - Arg::with_name(options::traverse::NO_TRAVERSE) - .short(options::traverse::NO_TRAVERSE) - .help("do not traverse any symbolic links (default)") - .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]), - ) - .arg( - Arg::with_name(options::traverse::EVERY) - .short(options::traverse::EVERY) - .help("traverse every symbolic link to a directory encountered"), - ); + let mut app = get_app(executable!()).usage(&usage[..]); // we change the positional args based on whether // --reference was used. diff --git a/src/uu/chmod/src/app.rs b/src/uu/chmod/src/app.rs new file mode 100644 index 00000000000..b88eef2a25b --- /dev/null +++ b/src/uu/chmod/src/app.rs @@ -0,0 +1,82 @@ +// spell-checker:ignore (ToDO) ugoa RFILE RFILE's + +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Change the mode of each FILE to MODE. + With --reference, change the mode of each FILE to that of RFILE."; + +const LONG_USAGE: &str = + "Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'."; + +pub mod options { + pub const CHANGES: &str = "changes"; + pub const QUIET: &str = "quiet"; // visible_alias("silent") + pub const VERBOSE: &str = "verbose"; + pub const NO_PRESERVE_ROOT: &str = "no-preserve-root"; + pub const PRESERVE_ROOT: &str = "preserve-root"; + pub const REFERENCE: &str = "RFILE"; + pub const RECURSIVE: &str = "recursive"; + pub const MODE: &str = "MODE"; + pub const FILE: &str = "FILE"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .after_help(LONG_USAGE) + .arg( + Arg::with_name(options::CHANGES) + .long(options::CHANGES) + .short("c") + .help("like verbose but report only when a change is made"), + ) + .arg( + Arg::with_name(options::QUIET) + .long(options::QUIET) + .visible_alias("silent") + .short("f") + .help("suppress most error messages"), + ) + .arg( + Arg::with_name(options::VERBOSE) + .long(options::VERBOSE) + .short("v") + .help("output a diagnostic for every file processed"), + ) + .arg( + Arg::with_name(options::NO_PRESERVE_ROOT) + .long(options::NO_PRESERVE_ROOT) + .help("do not treat '/' specially (the default)"), + ) + .arg( + Arg::with_name(options::PRESERVE_ROOT) + .long(options::PRESERVE_ROOT) + .help("fail to operate recursively on '/'"), + ) + .arg( + Arg::with_name(options::RECURSIVE) + .long(options::RECURSIVE) + .short("R") + .help("change files and directories recursively"), + ) + .arg( + Arg::with_name(options::REFERENCE) + .long("reference") + .takes_value(true) + .help("use RFILE's mode instead of MODE values"), + ) + .arg( + Arg::with_name(options::MODE) + .required_unless(options::REFERENCE) + .takes_value(true), + // It would be nice if clap could parse with delimiter, e.g. "g-x,u+x", + // however .multiple(true) cannot be used here because FILE already needs that. + // Only one positional argument with .multiple(true) set is allowed per command + ) + .arg( + Arg::with_name(options::FILE) + .required_unless(options::MODE) + .multiple(true), + ) +} diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 2d5787099e4..c5349ed5b63 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -10,7 +10,8 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; +pub mod app; + use std::fs; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::Path; @@ -21,20 +22,7 @@ use uucore::mode; use uucore::InvalidEncodingHandling; use walkdir::WalkDir; -static ABOUT: &str = "Change the mode of each FILE to MODE. - With --reference, change the mode of each FILE to that of RFILE."; - -mod options { - pub const CHANGES: &str = "changes"; - pub const QUIET: &str = "quiet"; // visible_alias("silent") - pub const VERBOSE: &str = "verbose"; - pub const NO_PRESERVE_ROOT: &str = "no-preserve-root"; - pub const PRESERVE_ROOT: &str = "preserve-root"; - pub const REFERENCE: &str = "RFILE"; - pub const RECURSIVE: &str = "recursive"; - pub const MODE: &str = "MODE"; - pub const FILE: &str = "FILE"; -} +use crate::app::{get_app, options}; fn get_usage() -> String { format!( @@ -45,10 +33,6 @@ or: {0} [OPTION]... --reference=RFILE FILE...", ) } -fn get_long_usage() -> String { - String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.") -} - pub fn uumain(args: impl uucore::Args) -> i32 { let mut args = args .collect_str(InvalidEncodingHandling::ConvertLossy) @@ -59,67 +43,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mode_had_minus_prefix = strip_minus_from_mode(&mut args); let usage = get_usage(); - let after_help = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .after_help(&after_help[..]) - .arg( - Arg::with_name(options::CHANGES) - .long(options::CHANGES) - .short("c") - .help("like verbose but report only when a change is made"), - ) - .arg( - Arg::with_name(options::QUIET) - .long(options::QUIET) - .visible_alias("silent") - .short("f") - .help("suppress most error messages"), - ) - .arg( - Arg::with_name(options::VERBOSE) - .long(options::VERBOSE) - .short("v") - .help("output a diagnostic for every file processed"), - ) - .arg( - Arg::with_name(options::NO_PRESERVE_ROOT) - .long(options::NO_PRESERVE_ROOT) - .help("do not treat '/' specially (the default)"), - ) - .arg( - Arg::with_name(options::PRESERVE_ROOT) - .long(options::PRESERVE_ROOT) - .help("fail to operate recursively on '/'"), - ) - .arg( - Arg::with_name(options::RECURSIVE) - .long(options::RECURSIVE) - .short("R") - .help("change files and directories recursively"), - ) - .arg( - Arg::with_name(options::REFERENCE) - .long("reference") - .takes_value(true) - .help("use RFILE's mode instead of MODE values"), - ) - .arg( - Arg::with_name(options::MODE) - .required_unless(options::REFERENCE) - .takes_value(true), - // It would be nice if clap could parse with delimiter, e.g. "g-x,u+x", - // however .multiple(true) cannot be used here because FILE already needs that. - // Only one positional argument with .multiple(true) set is allowed per command - ) - .arg( - Arg::with_name(options::FILE) - .required_unless(options::MODE) - .multiple(true), - ) .get_matches_from(args); let changes = matches.is_present(options::CHANGES); diff --git a/src/uu/chown/src/app.rs b/src/uu/chown/src/app.rs new file mode 100644 index 00000000000..8e803006171 --- /dev/null +++ b/src/uu/chown/src/app.rs @@ -0,0 +1,129 @@ +// spell-checker:ignore (ToDO) RFILE RFILE's + +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "change file owner and group"; + +pub mod options { + pub mod verbosity { + pub const CHANGES: &str = "changes"; + pub const QUIET: &str = "quiet"; + pub const SILENT: &str = "silent"; + pub const VERBOSE: &str = "verbose"; + } + pub mod preserve_root { + pub const PRESERVE: &str = "preserve-root"; + pub const NO_PRESERVE: &str = "no-preserve-root"; + } + pub mod dereference { + pub const DEREFERENCE: &str = "dereference"; + pub const NO_DEREFERENCE: &str = "no-dereference"; + } + pub const FROM: &str = "from"; + pub const RECURSIVE: &str = "recursive"; + pub mod traverse { + pub const TRAVERSE: &str = "H"; + pub const NO_TRAVERSE: &str = "P"; + pub const EVERY: &str = "L"; + } + pub const REFERENCE: &str = "reference"; +} + +pub const ARG_OWNER: &str = "owner"; +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::verbosity::CHANGES) + .short("c") + .long(options::verbosity::CHANGES) + .help("like verbose but report only when a change is made"), + ) + .arg(Arg::with_name(options::dereference::DEREFERENCE).long(options::dereference::DEREFERENCE).help( + "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself", + )) + .arg( + Arg::with_name(options::dereference::NO_DEREFERENCE) + .short("h") + .long(options::dereference::NO_DEREFERENCE) + .help( + "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", + ), + ) + .arg( + Arg::with_name(options::FROM) + .long(options::FROM) + .help( + "change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute", + ) + .value_name("CURRENT_OWNER:CURRENT_GROUP"), + ) + .arg( + Arg::with_name(options::preserve_root::PRESERVE) + .long(options::preserve_root::PRESERVE) + .help("fail to operate recursively on '/'"), + ) + .arg( + Arg::with_name(options::preserve_root::NO_PRESERVE) + .long(options::preserve_root::NO_PRESERVE) + .help("do not treat '/' specially (the default)"), + ) + .arg( + Arg::with_name(options::verbosity::QUIET) + .long(options::verbosity::QUIET) + .help("suppress most error messages"), + ) + .arg( + Arg::with_name(options::RECURSIVE) + .short("R") + .long(options::RECURSIVE) + .help("operate on files and directories recursively"), + ) + .arg( + Arg::with_name(options::REFERENCE) + .long(options::REFERENCE) + .help("use RFILE's owner and group rather than specifying OWNER:GROUP values") + .value_name("RFILE") + .min_values(1), + ) + .arg(Arg::with_name(options::verbosity::SILENT).short("f").long(options::verbosity::SILENT)) + .arg( + Arg::with_name(options::traverse::TRAVERSE) + .short(options::traverse::TRAVERSE) + .help("if a command line argument is a symbolic link to a directory, traverse it") + .overrides_with_all(&[options::traverse::EVERY, options::traverse::NO_TRAVERSE]), + ) + .arg( + Arg::with_name(options::traverse::EVERY) + .short(options::traverse::EVERY) + .help("traverse every symbolic link to a directory encountered") + .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::NO_TRAVERSE]), + ) + .arg( + Arg::with_name(options::traverse::NO_TRAVERSE) + .short(options::traverse::NO_TRAVERSE) + .help("do not traverse any symbolic links (default)") + .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]), + ) + .arg( + Arg::with_name(options::verbosity::VERBOSE) + .long(options::verbosity::VERBOSE) + .help("output a diagnostic for every file processed"), + ) + .arg( + Arg::with_name(ARG_OWNER) + .multiple(false) + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name(ARG_FILES) + .multiple(true) + .takes_value(true) + .required(true) + .min_values(1), + ) +} diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index ab9f10dba3b..1149cc03456 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -7,6 +7,8 @@ // spell-checker:ignore (ToDO) COMFOLLOW Chowner Passwd RFILE RFILE's derefer dgid duid +pub mod app; + #[macro_use] extern crate uucore; pub use uucore::entries::{self, Group, Locate, Passwd}; @@ -14,8 +16,6 @@ use uucore::fs::resolve_relative_path; use uucore::libc::{gid_t, uid_t}; use uucore::perms::{wrap_chown, Verbosity}; -use clap::{crate_version, App, Arg}; - use walkdir::WalkDir; use std::fs::{self, Metadata}; @@ -25,7 +25,7 @@ use std::convert::AsRef; use std::path::Path; use uucore::InvalidEncodingHandling; -static ABOUT: &str = "change file owner and group"; +use crate::app::{get_app, ARG_FILES, ARG_OWNER}; pub mod options { pub mod verbosity { @@ -52,9 +52,6 @@ pub mod options { pub static REFERENCE: &str = "reference"; } -static ARG_OWNER: &str = "owner"; -static ARG_FILES: &str = "files"; - const FTS_COMFOLLOW: u8 = 1; const FTS_PHYSICAL: u8 = 1 << 1; const FTS_LOGICAL: u8 = 1 << 2; @@ -73,100 +70,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(options::verbosity::CHANGES) - .short("c") - .long(options::verbosity::CHANGES) - .help("like verbose but report only when a change is made"), - ) - .arg(Arg::with_name(options::dereference::DEREFERENCE).long(options::dereference::DEREFERENCE).help( - "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself", - )) - .arg( - Arg::with_name(options::dereference::NO_DEREFERENCE) - .short("h") - .long(options::dereference::NO_DEREFERENCE) - .help( - "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", - ), - ) - .arg( - Arg::with_name(options::FROM) - .long(options::FROM) - .help( - "change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute", - ) - .value_name("CURRENT_OWNER:CURRENT_GROUP"), - ) - .arg( - Arg::with_name(options::preserve_root::PRESERVE) - .long(options::preserve_root::PRESERVE) - .help("fail to operate recursively on '/'"), - ) - .arg( - Arg::with_name(options::preserve_root::NO_PRESERVE) - .long(options::preserve_root::NO_PRESERVE) - .help("do not treat '/' specially (the default)"), - ) - .arg( - Arg::with_name(options::verbosity::QUIET) - .long(options::verbosity::QUIET) - .help("suppress most error messages"), - ) - .arg( - Arg::with_name(options::RECURSIVE) - .short("R") - .long(options::RECURSIVE) - .help("operate on files and directories recursively"), - ) - .arg( - Arg::with_name(options::REFERENCE) - .long(options::REFERENCE) - .help("use RFILE's owner and group rather than specifying OWNER:GROUP values") - .value_name("RFILE") - .min_values(1), - ) - .arg(Arg::with_name(options::verbosity::SILENT).short("f").long(options::verbosity::SILENT)) - .arg( - Arg::with_name(options::traverse::TRAVERSE) - .short(options::traverse::TRAVERSE) - .help("if a command line argument is a symbolic link to a directory, traverse it") - .overrides_with_all(&[options::traverse::EVERY, options::traverse::NO_TRAVERSE]), - ) - .arg( - Arg::with_name(options::traverse::EVERY) - .short(options::traverse::EVERY) - .help("traverse every symbolic link to a directory encountered") - .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::NO_TRAVERSE]), - ) - .arg( - Arg::with_name(options::traverse::NO_TRAVERSE) - .short(options::traverse::NO_TRAVERSE) - .help("do not traverse any symbolic links (default)") - .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]), - ) - .arg( - Arg::with_name(options::verbosity::VERBOSE) - .long(options::verbosity::VERBOSE) - .help("output a diagnostic for every file processed"), - ) - .arg( - Arg::with_name(ARG_OWNER) - .multiple(false) - .takes_value(true) - .required(true), - ) - .arg( - Arg::with_name(ARG_FILES) - .multiple(true) - .takes_value(true) - .required(true) - .min_values(1), - ) .get_matches_from(args); /* First arg is the owner/group */ diff --git a/src/uu/chroot/src/app.rs b/src/uu/chroot/src/app.rs new file mode 100644 index 00000000000..43842144a1a --- /dev/null +++ b/src/uu/chroot/src/app.rs @@ -0,0 +1,65 @@ +// spell-checker:ignore (ToDO) NEWROOT Userspec pstatus + +use clap::{crate_version, App, Arg}; + +mod options { + pub const NEWROOT: &str = "newroot"; + pub const USER: &str = "user"; + pub const GROUP: &str = "group"; + pub const GROUPS: &str = "groups"; + pub const USERSPEC: &str = "userspec"; + pub const COMMAND: &str = "command"; +} + +const ABOUT: &str = "Run COMMAND with root directory set to NEWROOT."; +const SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .usage(SYNTAX) + .arg( + Arg::with_name(options::NEWROOT) + .hidden(true) + .required(true) + .index(1), + ) + .arg( + Arg::with_name(options::USER) + .short("u") + .long(options::USER) + .help("User (ID or name) to switch before running the program") + .value_name("USER"), + ) + .arg( + Arg::with_name(options::GROUP) + .short("g") + .long(options::GROUP) + .help("Group (ID or name) to switch to") + .value_name("GROUP"), + ) + .arg( + Arg::with_name(options::GROUPS) + .short("G") + .long(options::GROUPS) + .help("Comma-separated list of groups to switch to") + .value_name("GROUP1,GROUP2..."), + ) + .arg( + Arg::with_name(options::USERSPEC) + .long(options::USERSPEC) + .help( + "Colon-separated user and group to switch to. \ + Same as -u USER -g GROUP. \ + Userspec has higher preference than -u and/or -g", + ) + .value_name("USER:GROUP"), + ) + .arg( + Arg::with_name(options::COMMAND) + .hidden(true) + .multiple(true) + .index(2), + ) +} diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 86d4a49007c..c18837eb0f2 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -8,9 +8,10 @@ // spell-checker:ignore (ToDO) NEWROOT Userspec pstatus +pub mod app; + #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::ffi::CString; use std::io::Error; use std::path::Path; @@ -18,10 +19,7 @@ use std::process::Command; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; use uucore::{entries, InvalidEncodingHandling}; -static NAME: &str = "chroot"; -static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT."; -static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]"; - +use crate::app::get_app; mod options { pub const NEWROOT: &str = "newroot"; pub const USER: &str = "user"; @@ -36,54 +34,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(SYNTAX) - .arg( - Arg::with_name(options::NEWROOT) - .hidden(true) - .required(true) - .index(1), - ) - .arg( - Arg::with_name(options::USER) - .short("u") - .long(options::USER) - .help("User (ID or name) to switch before running the program") - .value_name("USER"), - ) - .arg( - Arg::with_name(options::GROUP) - .short("g") - .long(options::GROUP) - .help("Group (ID or name) to switch to") - .value_name("GROUP"), - ) - .arg( - Arg::with_name(options::GROUPS) - .short("G") - .long(options::GROUPS) - .help("Comma-separated list of groups to switch to") - .value_name("GROUP1,GROUP2..."), - ) - .arg( - Arg::with_name(options::USERSPEC) - .long(options::USERSPEC) - .help( - "Colon-separated user and group to switch to. \ - Same as -u USER -g GROUP. \ - Userspec has higher preference than -u and/or -g", - ) - .value_name("USER:GROUP"), - ) - .arg( - Arg::with_name(options::COMMAND) - .hidden(true) - .multiple(true) - .index(2), - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let default_shell: &'static str = "/bin/sh"; let default_option: &'static str = "-i"; @@ -94,7 +45,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { None => crash!( 1, "Missing operand: NEWROOT\nTry '{} --help' for more information.", - NAME + executable!() ), }; diff --git a/src/uu/cksum/src/app.rs b/src/uu/cksum/src/app.rs new file mode 100644 index 00000000000..5230c21f6cf --- /dev/null +++ b/src/uu/cksum/src/app.rs @@ -0,0 +1,16 @@ +use clap::{crate_version, App, Arg}; + +const SYNTAX: &str = "[OPTIONS] [FILE]..."; +const SUMMARY: &str = "Print CRC and size for each file"; + +pub mod options { + pub static FILE: &str = "file"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(SUMMARY) + .usage(SYNTAX) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) +} diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 6a812c18636..db8de8a3903 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -7,23 +7,22 @@ // spell-checker:ignore (ToDO) fname +pub mod app; + #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{self, stdin, BufReader, Read}; use std::path::Path; use uucore::InvalidEncodingHandling; +use crate::app::{get_app, options}; + // NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 const CRC_TABLE_LEN: usize = 256; const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table(); -const NAME: &str = "cksum"; -const SYNTAX: &str = "[OPTIONS] [FILE]..."; -const SUMMARY: &str = "Print CRC and size for each file"; - // this is basically a hack to get "loops" to work on Rust 1.33. Once we update to Rust 1.46 or // greater, we can just use while loops macro_rules! unroll { @@ -171,22 +170,12 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> { } } -mod options { - pub static FILE: &str = "file"; -} - pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .about(SUMMARY) - .usage(SYNTAX) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let files: Vec = match matches.values_of(options::FILE) { Some(v) => v.clone().map(|v| v.to_owned()).collect(), diff --git a/src/uu/comm/src/app.rs b/src/uu/comm/src/app.rs new file mode 100644 index 00000000000..70759928b61 --- /dev/null +++ b/src/uu/comm/src/app.rs @@ -0,0 +1,44 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "compare two sorted files line by line"; + +pub mod options { + pub const COLUMN_1: &str = "1"; + pub const COLUMN_2: &str = "2"; + pub const COLUMN_3: &str = "3"; + pub const DELIMITER: &str = "output-delimiter"; + pub const DELIMITER_DEFAULT: &str = "\t"; + pub const FILE_1: &str = "FILE1"; + pub const FILE_2: &str = "FILE2"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::COLUMN_1) + .short(options::COLUMN_1) + .help("suppress column 1 (lines unique to FILE1)"), + ) + .arg( + Arg::with_name(options::COLUMN_2) + .short(options::COLUMN_2) + .help("suppress column 2 (lines unique to FILE2)"), + ) + .arg( + Arg::with_name(options::COLUMN_3) + .short(options::COLUMN_3) + .help("suppress column 3 (lines that appear in both files)"), + ) + .arg( + Arg::with_name(options::DELIMITER) + .long(options::DELIMITER) + .help("separate columns with STR") + .value_name("STR") + .default_value(options::DELIMITER_DEFAULT) + .hide_default_value(true), + ) + .arg(Arg::with_name(options::FILE_1).required(true)) + .arg(Arg::with_name(options::FILE_2).required(true)) +} diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 7a6086bb501..b96054d1768 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -7,29 +7,21 @@ // spell-checker:ignore (ToDO) delim mkdelim +pub mod app; + #[macro_use] extern crate uucore; +use app::options; use std::cmp::Ordering; use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::path::Path; use uucore::InvalidEncodingHandling; -use clap::{crate_version, App, Arg, ArgMatches}; - -static ABOUT: &str = "compare two sorted files line by line"; -static LONG_HELP: &str = ""; +use clap::ArgMatches; -mod options { - pub const COLUMN_1: &str = "1"; - pub const COLUMN_2: &str = "2"; - pub const COLUMN_3: &str = "3"; - pub const DELIMITER: &str = "output-delimiter"; - pub const DELIMITER_DEFAULT: &str = "\t"; - pub const FILE_1: &str = "FILE1"; - pub const FILE_2: &str = "FILE2"; -} +use crate::app::get_app; fn get_usage() -> String { format!("{} [OPTION]... FILE1 FILE2", executable!()) @@ -137,36 +129,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::COLUMN_1) - .short(options::COLUMN_1) - .help("suppress column 1 (lines unique to FILE1)"), - ) - .arg( - Arg::with_name(options::COLUMN_2) - .short(options::COLUMN_2) - .help("suppress column 2 (lines unique to FILE2)"), - ) - .arg( - Arg::with_name(options::COLUMN_3) - .short(options::COLUMN_3) - .help("suppress column 3 (lines that appear in both files)"), - ) - .arg( - Arg::with_name(options::DELIMITER) - .long(options::DELIMITER) - .help("separate columns with STR") - .value_name("STR") - .default_value(options::DELIMITER_DEFAULT) - .hide_default_value(true), - ) - .arg(Arg::with_name(options::FILE_1).required(true)) - .arg(Arg::with_name(options::FILE_2).required(true)) .get_matches_from(args); let mut f1 = open_file(matches.value_of(options::FILE_1).unwrap()).unwrap(); diff --git a/src/uu/cp/src/app.rs b/src/uu/cp/src/app.rs new file mode 100644 index 00000000000..1a5432efb41 --- /dev/null +++ b/src/uu/cp/src/app.rs @@ -0,0 +1,242 @@ +use clap::{crate_version, App, Arg}; +use uucore::backup_control; + +pub const OPT_ARCHIVE: &str = "archive"; +pub const OPT_ATTRIBUTES_ONLY: &str = "attributes-only"; +pub const OPT_BACKUP: &str = "backup"; +pub const OPT_BACKUP_NO_ARG: &str = "b"; +pub const OPT_CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links"; +pub const OPT_CONTEXT: &str = "context"; +pub const OPT_COPY_CONTENTS: &str = "copy-contents"; +pub const OPT_DEREFERENCE: &str = "dereference"; +pub const OPT_FORCE: &str = "force"; +pub const OPT_INTERACTIVE: &str = "interactive"; +pub const OPT_LINK: &str = "link"; +pub const OPT_NO_CLOBBER: &str = "no-clobber"; +pub const OPT_NO_DEREFERENCE: &str = "no-dereference"; +pub const OPT_NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-links"; +pub const OPT_NO_PRESERVE: &str = "no-preserve"; +pub const OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; +pub const OPT_ONE_FILE_SYSTEM: &str = "one-file-system"; +pub const OPT_PARENT: &str = "parent"; +pub const OPT_PARENTS: &str = "parents"; +pub const OPT_PATHS: &str = "paths"; +pub const OPT_PRESERVE: &str = "preserve"; +pub const OPT_PRESERVE_DEFAULT_ATTRIBUTES: &str = "preserve-default-attributes"; +pub const OPT_RECURSIVE: &str = "recursive"; +pub const OPT_RECURSIVE_ALIAS: &str = "recursive_alias"; +pub const OPT_REFLINK: &str = "reflink"; +pub const OPT_REMOVE_DESTINATION: &str = "remove-destination"; +pub const OPT_SPARSE: &str = "sparse"; +pub const OPT_STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; +pub const OPT_SUFFIX: &str = "suffix"; +pub const OPT_SYMBOLIC_LINK: &str = "symbolic-link"; +pub const OPT_TARGET_DIRECTORY: &str = "target-directory"; +pub const OPT_UPDATE: &str = "update"; +pub const OPT_VERBOSE: &str = "verbose"; + +#[derive(Clone, Eq, PartialEq)] +pub enum Attribute { + #[cfg(unix)] + Mode, + Ownership, + Timestamps, + Context, + Links, + Xattr, +} + +#[cfg(unix)] +pub const PRESERVABLE_ATTRIBUTES: &[&str] = &[ + "mode", + "ownership", + "timestamps", + "context", + "links", + "xattr", + "all", +]; + +#[cfg(not(unix))] +pub const PRESERVABLE_ATTRIBUTES: &[&str] = &[ + "ownership", + "timestamps", + "context", + "links", + "xattr", + "all", +]; + +const ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg(Arg::with_name(OPT_TARGET_DIRECTORY) + .short("t") + .conflicts_with(OPT_NO_TARGET_DIRECTORY) + .long(OPT_TARGET_DIRECTORY) + .value_name(OPT_TARGET_DIRECTORY) + .takes_value(true) + .help("copy all SOURCE arguments into target-directory")) + .arg(Arg::with_name(OPT_NO_TARGET_DIRECTORY) + .short("T") + .long(OPT_NO_TARGET_DIRECTORY) + .conflicts_with(OPT_TARGET_DIRECTORY) + .help("Treat DEST as a regular file and not a directory")) + .arg(Arg::with_name(OPT_INTERACTIVE) + .short("i") + .long(OPT_INTERACTIVE) + .conflicts_with(OPT_NO_CLOBBER) + .help("ask before overwriting files")) + .arg(Arg::with_name(OPT_LINK) + .short("l") + .long(OPT_LINK) + .overrides_with(OPT_REFLINK) + .help("hard-link files instead of copying")) + .arg(Arg::with_name(OPT_NO_CLOBBER) + .short("n") + .long(OPT_NO_CLOBBER) + .conflicts_with(OPT_INTERACTIVE) + .help("don't overwrite a file that already exists")) + .arg(Arg::with_name(OPT_RECURSIVE) + .short("r") + .long(OPT_RECURSIVE) + // --archive sets this option + .help("copy directories recursively")) + .arg(Arg::with_name(OPT_RECURSIVE_ALIAS) + .short("R") + .help("same as -r")) + .arg(Arg::with_name(OPT_STRIP_TRAILING_SLASHES) + .long(OPT_STRIP_TRAILING_SLASHES) + .help("remove any trailing slashes from each SOURCE argument")) + .arg(Arg::with_name(OPT_VERBOSE) + .short("v") + .long(OPT_VERBOSE) + .help("explicitly state what is being done")) + .arg(Arg::with_name(OPT_SYMBOLIC_LINK) + .short("s") + .long(OPT_SYMBOLIC_LINK) + .conflicts_with(OPT_LINK) + .overrides_with(OPT_REFLINK) + .help("make symbolic links instead of copying")) + .arg(Arg::with_name(OPT_FORCE) + .short("f") + .long(OPT_FORCE) + .help("if an existing destination file cannot be opened, remove it and \ + try again (this option is ignored when the -n option is also used). \ + Currently not implemented for Windows.")) + .arg(Arg::with_name(OPT_REMOVE_DESTINATION) + .long(OPT_REMOVE_DESTINATION) + .conflicts_with(OPT_FORCE) + .help("remove each existing destination file before attempting to open it \ + (contrast with --force). On Windows, current only works for writeable files.")) + .arg(Arg::with_name(OPT_BACKUP) + .long(OPT_BACKUP) + .help("make a backup of each existing destination file") + .takes_value(true) + .require_equals(true) + .min_values(0) + .possible_values(backup_control::BACKUP_CONTROL_VALUES) + .value_name("CONTROL") + ) + .arg(Arg::with_name(OPT_BACKUP_NO_ARG) + .short(OPT_BACKUP_NO_ARG) + .help("like --backup but does not accept an argument") + ) + .arg(Arg::with_name(OPT_SUFFIX) + .short("S") + .long(OPT_SUFFIX) + .takes_value(true) + .value_name("SUFFIX") + .help("override the usual backup suffix")) + .arg(Arg::with_name(OPT_UPDATE) + .short("u") + .long(OPT_UPDATE) + .help("copy only when the SOURCE file is newer than the destination file\ + or when the destination file is missing")) + .arg(Arg::with_name(OPT_REFLINK) + .long(OPT_REFLINK) + .takes_value(true) + .value_name("WHEN") + .help("control clone/CoW copies. See below")) + .arg(Arg::with_name(OPT_ATTRIBUTES_ONLY) + .long(OPT_ATTRIBUTES_ONLY) + .conflicts_with(OPT_COPY_CONTENTS) + .overrides_with(OPT_REFLINK) + .help("Don't copy the file data, just the attributes")) + .arg(Arg::with_name(OPT_PRESERVE) + .long(OPT_PRESERVE) + .takes_value(true) + .multiple(true) + .use_delimiter(true) + .possible_values(PRESERVABLE_ATTRIBUTES) + .value_name("ATTR_LIST") + .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE]) + // -d sets this option + // --archive sets this option + .help("Preserve the specified attributes (default: mode(unix only),ownership,timestamps),\ + if possible additional attributes: context, links, xattr, all")) + .arg(Arg::with_name(OPT_PRESERVE_DEFAULT_ATTRIBUTES) + .short("-p") + .long(OPT_PRESERVE_DEFAULT_ATTRIBUTES) + .conflicts_with_all(&[OPT_PRESERVE, OPT_NO_PRESERVE, OPT_ARCHIVE]) + .help("same as --preserve=mode(unix only),ownership,timestamps")) + .arg(Arg::with_name(OPT_NO_PRESERVE) + .long(OPT_NO_PRESERVE) + .takes_value(true) + .value_name("ATTR_LIST") + .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_ARCHIVE]) + .help("don't preserve the specified attributes")) + .arg(Arg::with_name(OPT_PARENTS) + .long(OPT_PARENTS) + .alias(OPT_PARENT) + .help("use full source file name under DIRECTORY")) + .arg(Arg::with_name(OPT_NO_DEREFERENCE) + .short("-P") + .long(OPT_NO_DEREFERENCE) + .conflicts_with(OPT_DEREFERENCE) + // -d sets this option + .help("never follow symbolic links in SOURCE")) + .arg(Arg::with_name(OPT_DEREFERENCE) + .short("L") + .long(OPT_DEREFERENCE) + .conflicts_with(OPT_NO_DEREFERENCE) + .help("always follow symbolic links in SOURCE")) + .arg(Arg::with_name(OPT_ARCHIVE) + .short("a") + .long(OPT_ARCHIVE) + .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE]) + .help("Same as -dR --preserve=all")) + .arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS) + .short("d") + .help("same as --no-dereference --preserve=links")) + .arg(Arg::with_name(OPT_ONE_FILE_SYSTEM) + .short("x") + .long(OPT_ONE_FILE_SYSTEM) + .help("stay on this file system")) + + // TODO: implement the following args + .arg(Arg::with_name(OPT_COPY_CONTENTS) + .long(OPT_COPY_CONTENTS) + .conflicts_with(OPT_ATTRIBUTES_ONLY) + .help("NotImplemented: copy contents of special files when recursive")) + .arg(Arg::with_name(OPT_SPARSE) + .long(OPT_SPARSE) + .takes_value(true) + .value_name("WHEN") + .help("NotImplemented: control creation of sparse files. See below")) + .arg(Arg::with_name(OPT_CONTEXT) + .long(OPT_CONTEXT) + .takes_value(true) + .value_name("CTX") + .help("NotImplemented: set SELinux security context of destination file to default type")) + .arg(Arg::with_name(OPT_CLI_SYMBOLIC_LINKS) + .short("H") + .help("NotImplemented: follow command-line symbolic links in SOURCE")) + // END TODO + + .arg(Arg::with_name(OPT_PATHS) + .multiple(true)) +} diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index a87e86b982c..467f96c0f08 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -10,6 +10,8 @@ // spell-checker:ignore (ToDO) ficlone linkgs lstat nlink nlinks pathbuf reflink strs xattrs +pub mod app; + #[cfg(target_os = "linux")] #[macro_use] extern crate ioctl_sys; @@ -18,6 +20,7 @@ extern crate quick_error; #[macro_use] extern crate uucore; +use app::*; #[cfg(windows)] use winapi::um::fileapi::CreateFileW; #[cfg(windows)] @@ -25,7 +28,7 @@ use winapi::um::fileapi::GetFileInformationByHandle; use std::borrow::Cow; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::ArgMatches; use filetime::FileTime; use quick_error::ResultExt; use std::collections::HashSet; @@ -179,17 +182,6 @@ pub enum CopyMode { AttrOnly, } -#[derive(Clone, Eq, PartialEq)] -pub enum Attribute { - #[cfg(unix)] - Mode, - Ownership, - Timestamps, - Context, - Links, - Xattr, -} - /// Re-usable, extensible copy options #[allow(dead_code)] pub struct Options { @@ -213,7 +205,6 @@ pub struct Options { verbose: bool, } -static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; static LONG_HELP: &str = ""; static EXIT_OK: i32 = 0; static EXIT_ERR: i32 = 1; @@ -227,62 +218,6 @@ fn get_usage() -> String { ) } -// Argument constants -static OPT_ARCHIVE: &str = "archive"; -static OPT_ATTRIBUTES_ONLY: &str = "attributes-only"; -static OPT_BACKUP: &str = "backup"; -static OPT_BACKUP_NO_ARG: &str = "b"; -static OPT_CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links"; -static OPT_CONTEXT: &str = "context"; -static OPT_COPY_CONTENTS: &str = "copy-contents"; -static OPT_DEREFERENCE: &str = "dereference"; -static OPT_FORCE: &str = "force"; -static OPT_INTERACTIVE: &str = "interactive"; -static OPT_LINK: &str = "link"; -static OPT_NO_CLOBBER: &str = "no-clobber"; -static OPT_NO_DEREFERENCE: &str = "no-dereference"; -static OPT_NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-linkgs"; -static OPT_NO_PRESERVE: &str = "no-preserve"; -static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; -static OPT_ONE_FILE_SYSTEM: &str = "one-file-system"; -static OPT_PARENT: &str = "parent"; -static OPT_PARENTS: &str = "parents"; -static OPT_PATHS: &str = "paths"; -static OPT_PRESERVE: &str = "preserve"; -static OPT_PRESERVE_DEFAULT_ATTRIBUTES: &str = "preserve-default-attributes"; -static OPT_RECURSIVE: &str = "recursive"; -static OPT_RECURSIVE_ALIAS: &str = "recursive_alias"; -static OPT_REFLINK: &str = "reflink"; -static OPT_REMOVE_DESTINATION: &str = "remove-destination"; -static OPT_SPARSE: &str = "sparse"; -static OPT_STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; -static OPT_SUFFIX: &str = "suffix"; -static OPT_SYMBOLIC_LINK: &str = "symbolic-link"; -static OPT_TARGET_DIRECTORY: &str = "target-directory"; -static OPT_UPDATE: &str = "update"; -static OPT_VERBOSE: &str = "verbose"; - -#[cfg(unix)] -static PRESERVABLE_ATTRIBUTES: &[&str] = &[ - "mode", - "ownership", - "timestamps", - "context", - "links", - "xattr", - "all", -]; - -#[cfg(not(unix))] -static PRESERVABLE_ATTRIBUTES: &[&str] = &[ - "ownership", - "timestamps", - "context", - "links", - "xattr", - "all", -]; - static DEFAULT_ATTRIBUTES: &[Attribute] = &[ #[cfg(unix)] Attribute::Mode, @@ -292,177 +227,13 @@ static DEFAULT_ATTRIBUTES: &[Attribute] = &[ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP)) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg(Arg::with_name(OPT_TARGET_DIRECTORY) - .short("t") - .conflicts_with(OPT_NO_TARGET_DIRECTORY) - .long(OPT_TARGET_DIRECTORY) - .value_name(OPT_TARGET_DIRECTORY) - .takes_value(true) - .help("copy all SOURCE arguments into target-directory")) - .arg(Arg::with_name(OPT_NO_TARGET_DIRECTORY) - .short("T") - .long(OPT_NO_TARGET_DIRECTORY) - .conflicts_with(OPT_TARGET_DIRECTORY) - .help("Treat DEST as a regular file and not a directory")) - .arg(Arg::with_name(OPT_INTERACTIVE) - .short("i") - .long(OPT_INTERACTIVE) - .conflicts_with(OPT_NO_CLOBBER) - .help("ask before overwriting files")) - .arg(Arg::with_name(OPT_LINK) - .short("l") - .long(OPT_LINK) - .overrides_with(OPT_REFLINK) - .help("hard-link files instead of copying")) - .arg(Arg::with_name(OPT_NO_CLOBBER) - .short("n") - .long(OPT_NO_CLOBBER) - .conflicts_with(OPT_INTERACTIVE) - .help("don't overwrite a file that already exists")) - .arg(Arg::with_name(OPT_RECURSIVE) - .short("r") - .long(OPT_RECURSIVE) - // --archive sets this option - .help("copy directories recursively")) - .arg(Arg::with_name(OPT_RECURSIVE_ALIAS) - .short("R") - .help("same as -r")) - .arg(Arg::with_name(OPT_STRIP_TRAILING_SLASHES) - .long(OPT_STRIP_TRAILING_SLASHES) - .help("remove any trailing slashes from each SOURCE argument")) - .arg(Arg::with_name(OPT_VERBOSE) - .short("v") - .long(OPT_VERBOSE) - .help("explicitly state what is being done")) - .arg(Arg::with_name(OPT_SYMBOLIC_LINK) - .short("s") - .long(OPT_SYMBOLIC_LINK) - .conflicts_with(OPT_LINK) - .overrides_with(OPT_REFLINK) - .help("make symbolic links instead of copying")) - .arg(Arg::with_name(OPT_FORCE) - .short("f") - .long(OPT_FORCE) - .help("if an existing destination file cannot be opened, remove it and \ - try again (this option is ignored when the -n option is also used). \ - Currently not implemented for Windows.")) - .arg(Arg::with_name(OPT_REMOVE_DESTINATION) - .long(OPT_REMOVE_DESTINATION) - .conflicts_with(OPT_FORCE) - .help("remove each existing destination file before attempting to open it \ - (contrast with --force). On Windows, current only works for writeable files.")) - .arg(Arg::with_name(OPT_BACKUP) - .long(OPT_BACKUP) - .help("make a backup of each existing destination file") - .takes_value(true) - .require_equals(true) - .min_values(0) - .possible_values(backup_control::BACKUP_CONTROL_VALUES) - .value_name("CONTROL") - ) - .arg(Arg::with_name(OPT_BACKUP_NO_ARG) - .short(OPT_BACKUP_NO_ARG) - .help("like --backup but does not accept an argument") - ) - .arg(Arg::with_name(OPT_SUFFIX) - .short("S") - .long(OPT_SUFFIX) - .takes_value(true) - .value_name("SUFFIX") - .help("override the usual backup suffix")) - .arg(Arg::with_name(OPT_UPDATE) - .short("u") - .long(OPT_UPDATE) - .help("copy only when the SOURCE file is newer than the destination file\ - or when the destination file is missing")) - .arg(Arg::with_name(OPT_REFLINK) - .long(OPT_REFLINK) - .takes_value(true) - .value_name("WHEN") - .help("control clone/CoW copies. See below")) - .arg(Arg::with_name(OPT_ATTRIBUTES_ONLY) - .long(OPT_ATTRIBUTES_ONLY) - .conflicts_with(OPT_COPY_CONTENTS) - .overrides_with(OPT_REFLINK) - .help("Don't copy the file data, just the attributes")) - .arg(Arg::with_name(OPT_PRESERVE) - .long(OPT_PRESERVE) - .takes_value(true) - .multiple(true) - .use_delimiter(true) - .possible_values(PRESERVABLE_ATTRIBUTES) - .value_name("ATTR_LIST") - .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE]) - // -d sets this option - // --archive sets this option - .help("Preserve the specified attributes (default: mode(unix only),ownership,timestamps),\ - if possible additional attributes: context, links, xattr, all")) - .arg(Arg::with_name(OPT_PRESERVE_DEFAULT_ATTRIBUTES) - .short("-p") - .long(OPT_PRESERVE_DEFAULT_ATTRIBUTES) - .conflicts_with_all(&[OPT_PRESERVE, OPT_NO_PRESERVE, OPT_ARCHIVE]) - .help("same as --preserve=mode(unix only),ownership,timestamps")) - .arg(Arg::with_name(OPT_NO_PRESERVE) - .long(OPT_NO_PRESERVE) - .takes_value(true) - .value_name("ATTR_LIST") - .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_ARCHIVE]) - .help("don't preserve the specified attributes")) - .arg(Arg::with_name(OPT_PARENTS) - .long(OPT_PARENTS) - .alias(OPT_PARENT) - .help("use full source file name under DIRECTORY")) - .arg(Arg::with_name(OPT_NO_DEREFERENCE) - .short("-P") - .long(OPT_NO_DEREFERENCE) - .conflicts_with(OPT_DEREFERENCE) - // -d sets this option - .help("never follow symbolic links in SOURCE")) - .arg(Arg::with_name(OPT_DEREFERENCE) - .short("L") - .long(OPT_DEREFERENCE) - .conflicts_with(OPT_NO_DEREFERENCE) - .help("always follow symbolic links in SOURCE")) - .arg(Arg::with_name(OPT_ARCHIVE) - .short("a") - .long(OPT_ARCHIVE) - .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE]) - .help("Same as -dR --preserve=all")) - .arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS) - .short("d") - .help("same as --no-dereference --preserve=links")) - .arg(Arg::with_name(OPT_ONE_FILE_SYSTEM) - .short("x") - .long(OPT_ONE_FILE_SYSTEM) - .help("stay on this file system")) - - // TODO: implement the following args - .arg(Arg::with_name(OPT_COPY_CONTENTS) - .long(OPT_COPY_CONTENTS) - .conflicts_with(OPT_ATTRIBUTES_ONLY) - .help("NotImplemented: copy contents of special files when recursive")) - .arg(Arg::with_name(OPT_SPARSE) - .long(OPT_SPARSE) - .takes_value(true) - .value_name("WHEN") - .help("NotImplemented: control creation of sparse files. See below")) - .arg(Arg::with_name(OPT_CONTEXT) - .long(OPT_CONTEXT) - .takes_value(true) - .value_name("CTX") - .help("NotImplemented: set SELinux security context of destination file to default type")) - .arg(Arg::with_name(OPT_CLI_SYMBOLIC_LINKS) - .short("H") - .help("NotImplemented: follow command-line symbolic links in SOURCE")) - // END TODO - - .arg(Arg::with_name(OPT_PATHS) - .multiple(true)) + .after_help(&*format!( + "{}\n{}", + LONG_HELP, + backup_control::BACKUP_CONTROL_LONG_HELP + )) .get_matches_from(args); let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches)); diff --git a/src/uu/csplit/src/app.rs b/src/uu/csplit/src/app.rs new file mode 100644 index 00000000000..351568e7025 --- /dev/null +++ b/src/uu/csplit/src/app.rs @@ -0,0 +1,75 @@ +use clap::{crate_version, App, Arg}; + +const SUMMARY: &str = "split a file into sections determined by context lines"; +const LONG_HELP: &str = "Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output."; + +pub mod options { + pub const SUFFIX_FORMAT: &str = "suffix-format"; + pub const SUPPRESS_MATCHED: &str = "suppress-matched"; + pub const DIGITS: &str = "digits"; + pub const PREFIX: &str = "prefix"; + pub const KEEP_FILES: &str = "keep-files"; + pub const QUIET: &str = "quiet"; + pub const ELIDE_EMPTY_FILES: &str = "elide-empty-files"; + pub const FILE: &str = "file"; + pub const PATTERN: &str = "pattern"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(SUMMARY) + .arg( + Arg::with_name(options::SUFFIX_FORMAT) + .short("b") + .long(options::SUFFIX_FORMAT) + .value_name("FORMAT") + .help("use sprintf FORMAT instead of %02d"), + ) + .arg( + Arg::with_name(options::PREFIX) + .short("f") + .long(options::PREFIX) + .value_name("PREFIX") + .help("use PREFIX instead of 'xx'"), + ) + .arg( + Arg::with_name(options::KEEP_FILES) + .short("k") + .long(options::KEEP_FILES) + .help("do not remove output files on errors"), + ) + .arg( + Arg::with_name(options::SUPPRESS_MATCHED) + .long(options::SUPPRESS_MATCHED) + .help("suppress the lines matching PATTERN"), + ) + .arg( + Arg::with_name(options::DIGITS) + .short("n") + .long(options::DIGITS) + .value_name("DIGITS") + .help("use specified number of digits instead of 2"), + ) + .arg( + Arg::with_name(options::QUIET) + .short("s") + .long(options::QUIET) + .visible_alias("silent") + .help("do not print counts of output file sizes"), + ) + .arg( + Arg::with_name(options::ELIDE_EMPTY_FILES) + .short("z") + .long(options::ELIDE_EMPTY_FILES) + .help("remove empty output files"), + ) + .arg(Arg::with_name(options::FILE).hidden(true).required(true)) + .arg( + Arg::with_name(options::PATTERN) + .hidden(true) + .multiple(true) + .required(true), + ) + .after_help(LONG_HELP) +} diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index d69254a3aca..a1ce63f2f4f 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -1,8 +1,8 @@ -#![crate_name = "uu_csplit"] +pub mod app; #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::ArgMatches; use regex::Regex; use std::cmp::Ordering; use std::io::{self, BufReader}; @@ -15,25 +15,11 @@ mod csplit_error; mod patterns; mod split_name; +use crate::app::{get_app, options}; use crate::csplit_error::CsplitError; use crate::split_name::SplitName; use uucore::InvalidEncodingHandling; -static SUMMARY: &str = "split a file into sections determined by context lines"; -static LONG_HELP: &str = "Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output."; - -mod options { - pub const SUFFIX_FORMAT: &str = "suffix-format"; - pub const SUPPRESS_MATCHED: &str = "suppress-matched"; - pub const DIGITS: &str = "digits"; - pub const PREFIX: &str = "prefix"; - pub const KEEP_FILES: &str = "keep-files"; - pub const QUIET: &str = "quiet"; - pub const ELIDE_EMPTY_FILES: &str = "elide-empty-files"; - pub const FILE: &str = "file"; - pub const PATTERN: &str = "pattern"; -} - fn get_usage() -> String { format!("{0} [OPTION]... FILE PATTERN...", executable!()) } @@ -711,63 +697,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(SUMMARY) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(options::SUFFIX_FORMAT) - .short("b") - .long(options::SUFFIX_FORMAT) - .value_name("FORMAT") - .help("use sprintf FORMAT instead of %02d"), - ) - .arg( - Arg::with_name(options::PREFIX) - .short("f") - .long(options::PREFIX) - .value_name("PREFIX") - .help("use PREFIX instead of 'xx'"), - ) - .arg( - Arg::with_name(options::KEEP_FILES) - .short("k") - .long(options::KEEP_FILES) - .help("do not remove output files on errors"), - ) - .arg( - Arg::with_name(options::SUPPRESS_MATCHED) - .long(options::SUPPRESS_MATCHED) - .help("suppress the lines matching PATTERN"), - ) - .arg( - Arg::with_name(options::DIGITS) - .short("n") - .long(options::DIGITS) - .value_name("DIGITS") - .help("use specified number of digits instead of 2"), - ) - .arg( - Arg::with_name(options::QUIET) - .short("s") - .long(options::QUIET) - .visible_alias("silent") - .help("do not print counts of output file sizes"), - ) - .arg( - Arg::with_name(options::ELIDE_EMPTY_FILES) - .short("z") - .long(options::ELIDE_EMPTY_FILES) - .help("remove empty output files"), - ) - .arg(Arg::with_name(options::FILE).hidden(true).required(true)) - .arg( - Arg::with_name(options::PATTERN) - .hidden(true) - .multiple(true) - .required(true), - ) - .after_help(LONG_HELP) .get_matches_from(args); // get the file to split diff --git a/src/uu/cut/src/app.rs b/src/uu/cut/src/app.rs new file mode 100644 index 00000000000..9be4c1fc422 --- /dev/null +++ b/src/uu/cut/src/app.rs @@ -0,0 +1,183 @@ +// spell-checker:ignore (ToDO) sourcefiles + +use clap::{crate_version, App, Arg}; + +const NAME: &str = "cut"; +const SYNTAX: &str = + "[-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+"; +const SUMMARY: &str = + "Prints specified byte or field columns from each line of stdin or the input files"; +const LONG_HELP: &str = " + Each call must specify a mode (what to use for columns), + a sequence (which columns to print), and provide a data source + + Specifying a mode + + Use --bytes (-b) or --characters (-c) to specify byte mode + + Use --fields (-f) to specify field mode, where each line is broken into + fields identified by a delimiter character. For example for a typical CSV + you could use this in combination with setting comma as the delimiter + + Specifying a sequence + + A sequence is a group of 1 or more numbers or inclusive ranges separated + by a commas. + + cut -f 2,5-7 some_file.txt + will display the 2nd, 5th, 6th, and 7th field for each source line + + Ranges can extend to the end of the row by excluding the the second number + + cut -f 3- some_file.txt + will display the 3rd field and all fields after for each source line + + The first number of a range can be excluded, and this is effectively the + same as using 1 as the first number: it causes the range to begin at the + first column. Ranges can also display a single column + + cut -f 1,3-5 some_file.txt + will display the 1st, 3rd, 4th, and 5th field for each source line + + The --complement option, when used, inverts the effect of the sequence + + cut --complement -f 4-6 some_file.txt + will display the every field but the 4th, 5th, and 6th + + Specifying a data source + + If no sourcefile arguments are specified, stdin is used as the source of + lines to print + + If sourcefile arguments are specified, stdin is ignored and all files are + read in consecutively if a sourcefile is not successfully read, a warning + will print to stderr, and the eventual status code will be 1, but cut + will continue to read through proceeding sourcefiles + + To print columns from both STDIN and a file argument, use - (dash) as a + sourcefile argument to represent stdin. + + Field Mode options + + The fields in each line are identified by a delimiter (separator) + + Set the delimiter + Set the delimiter which separates fields in the file using the + --delimiter (-d) option. Setting the delimiter is optional. + If not set, a default delimiter of Tab will be used. + + Optionally Filter based on delimiter + If the --only-delimited (-s) flag is provided, only lines which + contain the delimiter will be printed + + Replace the delimiter + If the --output-delimiter option is provided, the argument used for + it will replace the delimiter character in each line printed. This is + useful for transforming tabular data - e.g. to convert a CSV to a + TSV (tab-separated file) + + Line endings + + When the --zero-terminated (-z) option is used, cut sees \\0 (null) as the + 'line ending' character (both for the purposes of reading lines and + separating printed lines) instead of \\n (newline). This is useful for + tabular data where some of the cells may contain newlines + + echo 'ab\\0cd' | cut -z -c 1 + will result in 'a\\0c\\0' +"; + +pub mod options { + pub const BYTES: &str = "bytes"; + pub const CHARACTERS: &str = "characters"; + pub const DELIMITER: &str = "delimiter"; + pub const FIELDS: &str = "fields"; + pub const ZERO_TERMINATED: &str = "zero-terminated"; + pub const ONLY_DELIMITED: &str = "only-delimited"; + pub const OUTPUT_DELIMITER: &str = "output-delimiter"; + pub const COMPLEMENT: &str = "complement"; + pub const FILE: &str = "file"; +} +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .name(NAME) + .version(crate_version!()) + .usage(SYNTAX) + .about(SUMMARY) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::BYTES) + .short("b") + .long(options::BYTES) + .takes_value(true) + .help("filter byte columns from the input source") + .allow_hyphen_values(true) + .value_name("LIST") + .display_order(1), + ) + .arg( + Arg::with_name(options::CHARACTERS) + .short("c") + .long(options::CHARACTERS) + .help("alias for character mode") + .takes_value(true) + .allow_hyphen_values(true) + .value_name("LIST") + .display_order(2), + ) + .arg( + Arg::with_name(options::DELIMITER) + .short("d") + .long(options::DELIMITER) + .help("specify the delimiter character that separates fields in the input source. Defaults to Tab.") + .takes_value(true) + .value_name("DELIM") + .display_order(3), + ) + .arg( + Arg::with_name(options::FIELDS) + .short("f") + .long(options::FIELDS) + .help("filter field columns from the input source") + .takes_value(true) + .allow_hyphen_values(true) + .value_name("LIST") + .display_order(4), + ) + .arg( + Arg::with_name(options::COMPLEMENT) + .long(options::COMPLEMENT) + .help("invert the filter - instead of displaying only the filtered columns, display all but those columns") + .takes_value(false) + .display_order(5), + ) + .arg( + Arg::with_name(options::ONLY_DELIMITED) + .short("s") + .long(options::ONLY_DELIMITED) + .help("in field mode, only print lines which contain the delimiter") + .takes_value(false) + .display_order(6), + ) + .arg( + Arg::with_name(options::ZERO_TERMINATED) + .short("z") + .long(options::ZERO_TERMINATED) + .help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)") + .takes_value(false) + .display_order(8), + ) + .arg( + Arg::with_name(options::OUTPUT_DELIMITER) + .long(options::OUTPUT_DELIMITER) + .help("in field mode, replace the delimiter in output lines with this option's argument") + .takes_value(true) + .value_name("NEW_DELIM") + .display_order(7), + ) + .arg( + Arg::with_name(options::FILE) + .hidden(true) + .multiple(true) + ) +} diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index af4a27d8a0e..2cbc964ac01 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -11,102 +11,19 @@ extern crate uucore; use bstr::io::BufReadExt; -use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::path::Path; +use crate::app::{get_app, options}; + use self::searcher::Searcher; use uucore::ranges::Range; use uucore::InvalidEncodingHandling; +pub mod app; mod searcher; -static NAME: &str = "cut"; -static SYNTAX: &str = - "[-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+"; -static SUMMARY: &str = - "Prints specified byte or field columns from each line of stdin or the input files"; -static LONG_HELP: &str = " - Each call must specify a mode (what to use for columns), - a sequence (which columns to print), and provide a data source - - Specifying a mode - - Use --bytes (-b) or --characters (-c) to specify byte mode - - Use --fields (-f) to specify field mode, where each line is broken into - fields identified by a delimiter character. For example for a typical CSV - you could use this in combination with setting comma as the delimiter - - Specifying a sequence - - A sequence is a group of 1 or more numbers or inclusive ranges separated - by a commas. - - cut -f 2,5-7 some_file.txt - will display the 2nd, 5th, 6th, and 7th field for each source line - - Ranges can extend to the end of the row by excluding the the second number - - cut -f 3- some_file.txt - will display the 3rd field and all fields after for each source line - - The first number of a range can be excluded, and this is effectively the - same as using 1 as the first number: it causes the range to begin at the - first column. Ranges can also display a single column - - cut -f 1,3-5 some_file.txt - will display the 1st, 3rd, 4th, and 5th field for each source line - - The --complement option, when used, inverts the effect of the sequence - - cut --complement -f 4-6 some_file.txt - will display the every field but the 4th, 5th, and 6th - - Specifying a data source - - If no sourcefile arguments are specified, stdin is used as the source of - lines to print - - If sourcefile arguments are specified, stdin is ignored and all files are - read in consecutively if a sourcefile is not successfully read, a warning - will print to stderr, and the eventual status code will be 1, but cut - will continue to read through proceeding sourcefiles - - To print columns from both STDIN and a file argument, use - (dash) as a - sourcefile argument to represent stdin. - - Field Mode options - - The fields in each line are identified by a delimiter (separator) - - Set the delimiter - Set the delimiter which separates fields in the file using the - --delimiter (-d) option. Setting the delimiter is optional. - If not set, a default delimiter of Tab will be used. - - Optionally Filter based on delimiter - If the --only-delimited (-s) flag is provided, only lines which - contain the delimiter will be printed - - Replace the delimiter - If the --output-delimiter option is provided, the argument used for - it will replace the delimiter character in each line printed. This is - useful for transforming tabular data - e.g. to convert a CSV to a - TSV (tab-separated file) - - Line endings - - When the --zero-terminated (-z) option is used, cut sees \\0 (null) as the - 'line ending' character (both for the purposes of reading lines and - separating printed lines) instead of \\n (newline). This is useful for - tabular data where some of the cells may contain newlines - - echo 'ab\\0cd' | cut -z -c 1 - will result in 'a\\0c\\0' -"; - struct Options { out_delim: Option, zero_terminated: bool, @@ -379,105 +296,12 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { exit_code } -mod options { - pub const BYTES: &str = "bytes"; - pub const CHARACTERS: &str = "characters"; - pub const DELIMITER: &str = "delimiter"; - pub const FIELDS: &str = "fields"; - pub const ZERO_TERMINATED: &str = "zero-terminated"; - pub const ONLY_DELIMITED: &str = "only-delimited"; - pub const OUTPUT_DELIMITER: &str = "output-delimiter"; - pub const COMPLEMENT: &str = "complement"; - pub const FILE: &str = "file"; -} - pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(SYNTAX) - .about(SUMMARY) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::BYTES) - .short("b") - .long(options::BYTES) - .takes_value(true) - .help("filter byte columns from the input source") - .allow_hyphen_values(true) - .value_name("LIST") - .display_order(1), - ) - .arg( - Arg::with_name(options::CHARACTERS) - .short("c") - .long(options::CHARACTERS) - .help("alias for character mode") - .takes_value(true) - .allow_hyphen_values(true) - .value_name("LIST") - .display_order(2), - ) - .arg( - Arg::with_name(options::DELIMITER) - .short("d") - .long(options::DELIMITER) - .help("specify the delimiter character that separates fields in the input source. Defaults to Tab.") - .takes_value(true) - .value_name("DELIM") - .display_order(3), - ) - .arg( - Arg::with_name(options::FIELDS) - .short("f") - .long(options::FIELDS) - .help("filter field columns from the input source") - .takes_value(true) - .allow_hyphen_values(true) - .value_name("LIST") - .display_order(4), - ) - .arg( - Arg::with_name(options::COMPLEMENT) - .long(options::COMPLEMENT) - .help("invert the filter - instead of displaying only the filtered columns, display all but those columns") - .takes_value(false) - .display_order(5), - ) - .arg( - Arg::with_name(options::ONLY_DELIMITED) - .short("s") - .long(options::ONLY_DELIMITED) - .help("in field mode, only print lines which contain the delimiter") - .takes_value(false) - .display_order(6), - ) - .arg( - Arg::with_name(options::ZERO_TERMINATED) - .short("z") - .long(options::ZERO_TERMINATED) - .help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)") - .takes_value(false) - .display_order(8), - ) - .arg( - Arg::with_name(options::OUTPUT_DELIMITER) - .long(options::OUTPUT_DELIMITER) - .help("in field mode, replace the delimiter in output lines with this option's argument") - .takes_value(true) - .value_name("NEW_DELIM") - .display_order(7), - ) - .arg( - Arg::with_name(options::FILE) - .hidden(true) - .multiple(true) - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let complement = matches.is_present(options::COMPLEMENT); diff --git a/src/uu/date/src/app.rs b/src/uu/date/src/app.rs new file mode 100644 index 00000000000..1442b92d77a --- /dev/null +++ b/src/uu/date/src/app.rs @@ -0,0 +1,115 @@ +// spell-checker:ignore (format) DATEFILE + +use clap::{crate_version, App, Arg}; + +// Options +pub const DATE: &str = "date"; +pub const HOURS: &str = "hours"; +pub const MINUTES: &str = "minutes"; +pub const SECONDS: &str = "seconds"; +pub const HOUR: &str = "hour"; +pub const MINUTE: &str = "minute"; +pub const SECOND: &str = "second"; +pub const NS: &str = "ns"; + +pub const NAME: &str = "date"; +pub const ABOUT: &str = "print or set the system date and time"; + +pub const OPT_DATE: &str = "date"; +pub const OPT_FORMAT: &str = "format"; +pub const OPT_FILE: &str = "file"; +pub const OPT_DEBUG: &str = "debug"; +pub const OPT_ISO_8601: &str = "iso-8601"; +pub const OPT_RFC_EMAIL: &str = "rfc-email"; +pub const OPT_RFC_3339: &str = "rfc-3339"; +pub const OPT_SET: &str = "set"; +pub const OPT_REFERENCE: &str = "reference"; +pub const OPT_UNIVERSAL: &str = "universal"; +pub const OPT_UNIVERSAL_2: &str = "utc"; + +// Help strings + +const ISO_8601_HELP_STRING: &str = "output date/time in ISO 8601 format. + FMT='date' for date only (the default), + 'hours', 'minutes', 'seconds', or 'ns' + for date and time to the indicated precision. + Example: 2006-08-14T02:34:56-06:00"; + +const RFC_5322_HELP_STRING: &str = "output date and time in RFC 5322 format. + Example: Mon, 14 Aug 2006 02:34:56 -0600"; + +const RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format. + FMT='date', 'seconds', or 'ns' + for date and time to the indicated precision. + Example: 2006-08-14 02:34:56-06:00"; + +#[cfg(not(target_os = "macos"))] +const OPT_SET_HELP_STRING: &str = "set time described by STRING"; +#[cfg(target_os = "macos")] +const OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on mac yet)"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_DATE) + .short("d") + .long(OPT_DATE) + .takes_value(true) + .help("display time described by STRING, not 'now'"), + ) + .arg( + Arg::with_name(OPT_FILE) + .short("f") + .long(OPT_FILE) + .takes_value(true) + .help("like --date; once for each line of DATEFILE"), + ) + .arg( + Arg::with_name(OPT_ISO_8601) + .short("I") + .long(OPT_ISO_8601) + .takes_value(true) + .help(ISO_8601_HELP_STRING), + ) + .arg( + Arg::with_name(OPT_RFC_EMAIL) + .short("R") + .long(OPT_RFC_EMAIL) + .help(RFC_5322_HELP_STRING), + ) + .arg( + Arg::with_name(OPT_RFC_3339) + .long(OPT_RFC_3339) + .takes_value(true) + .help(RFC_3339_HELP_STRING), + ) + .arg( + Arg::with_name(OPT_DEBUG) + .long(OPT_DEBUG) + .help("annotate the parsed date, and warn about questionable usage to stderr"), + ) + .arg( + Arg::with_name(OPT_REFERENCE) + .short("r") + .long(OPT_REFERENCE) + .takes_value(true) + .help("display the last modification time of FILE"), + ) + .arg( + Arg::with_name(OPT_SET) + .short("s") + .long(OPT_SET) + .takes_value(true) + .help(OPT_SET_HELP_STRING), + ) + .arg( + Arg::with_name(OPT_UNIVERSAL) + .short("u") + .long(OPT_UNIVERSAL) + .alias(OPT_UNIVERSAL_2) + .help("print or set Coordinated Universal Time (UTC)"), + ) + .arg(Arg::with_name(OPT_FORMAT).multiple(false)) +} diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 8a0e3ef3a05..151e5dc5f99 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -8,13 +8,15 @@ // spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes +pub mod app; + #[macro_use] extern crate uucore; +use app::*; use chrono::{DateTime, FixedOffset, Local, Offset, Utc}; #[cfg(windows)] use chrono::{Datelike, Timelike}; -use clap::{crate_version, App, Arg}; #[cfg(all(unix, not(target_os = "macos")))] use libc::{clock_settime, timespec, CLOCK_REALTIME}; use std::fs::File; @@ -26,52 +28,6 @@ use winapi::{ um::{minwinbase::SYSTEMTIME, sysinfoapi::SetSystemTime}, }; -// Options -const DATE: &str = "date"; -const HOURS: &str = "hours"; -const MINUTES: &str = "minutes"; -const SECONDS: &str = "seconds"; -const HOUR: &str = "hour"; -const MINUTE: &str = "minute"; -const SECOND: &str = "second"; -const NS: &str = "ns"; - -const NAME: &str = "date"; -const ABOUT: &str = "print or set the system date and time"; - -const OPT_DATE: &str = "date"; -const OPT_FORMAT: &str = "format"; -const OPT_FILE: &str = "file"; -const OPT_DEBUG: &str = "debug"; -const OPT_ISO_8601: &str = "iso-8601"; -const OPT_RFC_EMAIL: &str = "rfc-email"; -const OPT_RFC_3339: &str = "rfc-3339"; -const OPT_SET: &str = "set"; -const OPT_REFERENCE: &str = "reference"; -const OPT_UNIVERSAL: &str = "universal"; -const OPT_UNIVERSAL_2: &str = "utc"; - -// Help strings - -static ISO_8601_HELP_STRING: &str = "output date/time in ISO 8601 format. - FMT='date' for date only (the default), - 'hours', 'minutes', 'seconds', or 'ns' - for date and time to the indicated precision. - Example: 2006-08-14T02:34:56-06:00"; - -static RFC_5322_HELP_STRING: &str = "output date and time in RFC 5322 format. - Example: Mon, 14 Aug 2006 02:34:56 -0600"; - -static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format. - FMT='date', 'seconds', or 'ns' - for date and time to the indicated precision. - Example: 2006-08-14 02:34:56-06:00"; - -#[cfg(not(target_os = "macos"))] -static OPT_SET_HELP_STRING: &str = "set time described by STRING"; -#[cfg(target_os = "macos")] -static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on mac yet)"; - /// Settings for this program, parsed from the command line struct Settings { utc: bool, @@ -142,70 +98,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { {0} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]", NAME ); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&syntax[..]) - .arg( - Arg::with_name(OPT_DATE) - .short("d") - .long(OPT_DATE) - .takes_value(true) - .help("display time described by STRING, not 'now'"), - ) - .arg( - Arg::with_name(OPT_FILE) - .short("f") - .long(OPT_FILE) - .takes_value(true) - .help("like --date; once for each line of DATEFILE"), - ) - .arg( - Arg::with_name(OPT_ISO_8601) - .short("I") - .long(OPT_ISO_8601) - .takes_value(true) - .help(ISO_8601_HELP_STRING), - ) - .arg( - Arg::with_name(OPT_RFC_EMAIL) - .short("R") - .long(OPT_RFC_EMAIL) - .help(RFC_5322_HELP_STRING), - ) - .arg( - Arg::with_name(OPT_RFC_3339) - .long(OPT_RFC_3339) - .takes_value(true) - .help(RFC_3339_HELP_STRING), - ) - .arg( - Arg::with_name(OPT_DEBUG) - .long(OPT_DEBUG) - .help("annotate the parsed date, and warn about questionable usage to stderr"), - ) - .arg( - Arg::with_name(OPT_REFERENCE) - .short("r") - .long(OPT_REFERENCE) - .takes_value(true) - .help("display the last modification time of FILE"), - ) - .arg( - Arg::with_name(OPT_SET) - .short("s") - .long(OPT_SET) - .takes_value(true) - .help(OPT_SET_HELP_STRING), - ) - .arg( - Arg::with_name(OPT_UNIVERSAL) - .short("u") - .long(OPT_UNIVERSAL) - .alias(OPT_UNIVERSAL_2) - .help("print or set Coordinated Universal Time (UTC)"), - ) - .arg(Arg::with_name(OPT_FORMAT).multiple(false)) .get_matches_from(args); let format = if let Some(form) = matches.value_of(OPT_FORMAT) { diff --git a/src/uu/df/src/app.rs b/src/uu/df/src/app.rs new file mode 100644 index 00000000000..38bca36a131 --- /dev/null +++ b/src/uu/df/src/app.rs @@ -0,0 +1,140 @@ +use clap::{crate_version, App, Arg}; + +pub const ABOUT: &str = "Show information about the file system on which each FILE resides,\n\ + or all file systems by default."; + +pub const EXIT_OK: i32 = 0; +pub const EXIT_ERR: i32 = 1; + +pub const OPT_ALL: &str = "all"; +pub const OPT_BLOCKSIZE: &str = "blocksize"; +pub const OPT_DIRECT: &str = "direct"; +pub const OPT_TOTAL: &str = "total"; +pub const OPT_HUMAN_READABLE: &str = "human-readable"; +pub const OPT_HUMAN_READABLE_2: &str = "human-readable-2"; +pub const OPT_INODES: &str = "inodes"; +pub const OPT_KILO: &str = "kilo"; +pub const OPT_LOCAL: &str = "local"; +pub const OPT_NO_SYNC: &str = "no-sync"; +pub const OPT_OUTPUT: &str = "output"; +pub const OPT_PATHS: &str = "paths"; +pub const OPT_PORTABILITY: &str = "portability"; +pub const OPT_SYNC: &str = "sync"; +pub const OPT_TYPE: &str = "type"; +pub const OPT_PRINT_TYPE: &str = "print-type"; +pub const OPT_EXCLUDE_TYPE: &str = "exclude-type"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_ALL) + .short("a") + .long("all") + .help("include dummy file systems"), + ) + .arg( + Arg::with_name(OPT_BLOCKSIZE) + .short("B") + .long("block-size") + .takes_value(true) + .help( + "scale sizes by SIZE before printing them; e.g.\ + '-BM' prints sizes in units of 1,048,576 bytes", + ), + ) + .arg( + Arg::with_name(OPT_DIRECT) + .long("direct") + .help("show statistics for a file instead of mount point"), + ) + .arg( + Arg::with_name(OPT_TOTAL) + .long("total") + .help("produce a grand total"), + ) + .arg( + Arg::with_name(OPT_HUMAN_READABLE) + .short("h") + .long("human-readable") + .conflicts_with(OPT_HUMAN_READABLE_2) + .help("print sizes in human readable format (e.g., 1K 234M 2G)"), + ) + .arg( + Arg::with_name(OPT_HUMAN_READABLE_2) + .short("H") + .long("si") + .conflicts_with(OPT_HUMAN_READABLE) + .help("likewise, but use powers of 1000 not 1024"), + ) + .arg( + Arg::with_name(OPT_INODES) + .short("i") + .long("inodes") + .help("list inode information instead of block usage"), + ) + .arg( + Arg::with_name(OPT_KILO) + .short("k") + .help("like --block-size=1K"), + ) + .arg( + Arg::with_name(OPT_LOCAL) + .short("l") + .long("local") + .help("limit listing to local file systems"), + ) + .arg( + Arg::with_name(OPT_NO_SYNC) + .long("no-sync") + .conflicts_with(OPT_SYNC) + .help("do not invoke sync before getting usage info (default)"), + ) + .arg( + Arg::with_name(OPT_OUTPUT) + .long("output") + .takes_value(true) + .use_delimiter(true) + .help( + "use the output format defined by FIELD_LIST,\ + or print all fields if FIELD_LIST is omitted.", + ), + ) + .arg( + Arg::with_name(OPT_PORTABILITY) + .short("P") + .long("portability") + .help("use the POSIX output format"), + ) + .arg( + Arg::with_name(OPT_SYNC) + .long("sync") + .conflicts_with(OPT_NO_SYNC) + .help("invoke sync before getting usage info"), + ) + .arg( + Arg::with_name(OPT_TYPE) + .short("t") + .long("type") + .takes_value(true) + .use_delimiter(true) + .help("limit listing to file systems of type TYPE"), + ) + .arg( + Arg::with_name(OPT_PRINT_TYPE) + .short("T") + .long("print-type") + .help("print file system type"), + ) + .arg( + Arg::with_name(OPT_EXCLUDE_TYPE) + .short("x") + .long("exclude-type") + .takes_value(true) + .use_delimiter(true) + .help("limit listing to file systems not of type TYPE"), + ) + .arg(Arg::with_name(OPT_PATHS).multiple(true)) + .help("Filesystem(s) to list") +} diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 0836aa43d8d..a698da63e1e 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -6,13 +6,15 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. +pub mod app; + #[macro_use] extern crate uucore; #[cfg(unix)] use uucore::fsext::statfs_fn; use uucore::fsext::{read_fs_list, FsUsage, MountInfo}; -use clap::{crate_version, App, Arg}; +use app::*; use number_prefix::NumberPrefix; use std::cell::Cell; @@ -30,30 +32,6 @@ use uucore::libc::{c_char, fsid_t, uid_t}; #[cfg(windows)] use std::path::Path; -static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\ - or all file systems by default."; - -static EXIT_OK: i32 = 0; -static EXIT_ERR: i32 = 1; - -static OPT_ALL: &str = "all"; -static OPT_BLOCKSIZE: &str = "blocksize"; -static OPT_DIRECT: &str = "direct"; -static OPT_TOTAL: &str = "total"; -static OPT_HUMAN_READABLE: &str = "human-readable"; -static OPT_HUMAN_READABLE_2: &str = "human-readable-2"; -static OPT_INODES: &str = "inodes"; -static OPT_KILO: &str = "kilo"; -static OPT_LOCAL: &str = "local"; -static OPT_NO_SYNC: &str = "no-sync"; -static OPT_OUTPUT: &str = "output"; -static OPT_PATHS: &str = "paths"; -static OPT_PORTABILITY: &str = "portability"; -static OPT_SYNC: &str = "sync"; -static OPT_TYPE: &str = "type"; -static OPT_PRINT_TYPE: &str = "print-type"; -static OPT_EXCLUDE_TYPE: &str = "exclude-type"; - /// Store names of file systems as a selector. /// Note: `exclude` takes priority over `include`. struct FsSelector { @@ -258,119 +236,8 @@ fn use_size(free_size: u64, total_size: u64) -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_ALL) - .short("a") - .long("all") - .help("include dummy file systems"), - ) - .arg( - Arg::with_name(OPT_BLOCKSIZE) - .short("B") - .long("block-size") - .takes_value(true) - .help( - "scale sizes by SIZE before printing them; e.g.\ - '-BM' prints sizes in units of 1,048,576 bytes", - ), - ) - .arg( - Arg::with_name(OPT_DIRECT) - .long("direct") - .help("show statistics for a file instead of mount point"), - ) - .arg( - Arg::with_name(OPT_TOTAL) - .long("total") - .help("produce a grand total"), - ) - .arg( - Arg::with_name(OPT_HUMAN_READABLE) - .short("h") - .long("human-readable") - .conflicts_with(OPT_HUMAN_READABLE_2) - .help("print sizes in human readable format (e.g., 1K 234M 2G)"), - ) - .arg( - Arg::with_name(OPT_HUMAN_READABLE_2) - .short("H") - .long("si") - .conflicts_with(OPT_HUMAN_READABLE) - .help("likewise, but use powers of 1000 not 1024"), - ) - .arg( - Arg::with_name(OPT_INODES) - .short("i") - .long("inodes") - .help("list inode information instead of block usage"), - ) - .arg( - Arg::with_name(OPT_KILO) - .short("k") - .help("like --block-size=1K"), - ) - .arg( - Arg::with_name(OPT_LOCAL) - .short("l") - .long("local") - .help("limit listing to local file systems"), - ) - .arg( - Arg::with_name(OPT_NO_SYNC) - .long("no-sync") - .conflicts_with(OPT_SYNC) - .help("do not invoke sync before getting usage info (default)"), - ) - .arg( - Arg::with_name(OPT_OUTPUT) - .long("output") - .takes_value(true) - .use_delimiter(true) - .help( - "use the output format defined by FIELD_LIST,\ - or print all fields if FIELD_LIST is omitted.", - ), - ) - .arg( - Arg::with_name(OPT_PORTABILITY) - .short("P") - .long("portability") - .help("use the POSIX output format"), - ) - .arg( - Arg::with_name(OPT_SYNC) - .long("sync") - .conflicts_with(OPT_NO_SYNC) - .help("invoke sync before getting usage info"), - ) - .arg( - Arg::with_name(OPT_TYPE) - .short("t") - .long("type") - .takes_value(true) - .use_delimiter(true) - .help("limit listing to file systems of type TYPE"), - ) - .arg( - Arg::with_name(OPT_PRINT_TYPE) - .short("T") - .long("print-type") - .help("print file system type"), - ) - .arg( - Arg::with_name(OPT_EXCLUDE_TYPE) - .short("x") - .long("exclude-type") - .takes_value(true) - .use_delimiter(true) - .help("limit listing to file systems not of type TYPE"), - ) - .arg(Arg::with_name(OPT_PATHS).multiple(true)) - .help("Filesystem(s) to list") .get_matches_from(args); let paths: Vec = matches diff --git a/src/uu/dircolors/src/app.rs b/src/uu/dircolors/src/app.rs new file mode 100644 index 00000000000..7b706f9a993 --- /dev/null +++ b/src/uu/dircolors/src/app.rs @@ -0,0 +1,46 @@ +use clap::{crate_version, App, Arg}; + +pub mod options { + pub const BOURNE_SHELL: &str = "bourne-shell"; + pub const C_SHELL: &str = "c-shell"; + pub const PRINT_DATABASE: &str = "print-database"; + pub const FILE: &str = "FILE"; +} + +const SUMMARY: &str = "Output commands to set the LS_COLORS environment variable."; +const LONG_HELP: &str = " + If FILE is specified, read it to determine which colors to use for which + file types and extensions. Otherwise, a precompiled database is used. + For details on the format of these files, run 'dircolors --print-database' +"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(SUMMARY) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::BOURNE_SHELL) + .long("sh") + .short("b") + .visible_alias("bourne-shell") + .help("output Bourne shell code to set LS_COLORS") + .display_order(1), + ) + .arg( + Arg::with_name(options::C_SHELL) + .long("csh") + .short("c") + .visible_alias("c-shell") + .help("output C shell code to set LS_COLORS") + .display_order(2), + ) + .arg( + Arg::with_name(options::PRINT_DATABASE) + .long("print-database") + .short("p") + .help("print the byte counts") + .display_order(3), + ) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) +} diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 2fa2e8b918c..ac3eb86fb35 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -16,26 +16,16 @@ use std::env; use std::fs::File; use std::io::{BufRead, BufReader}; -use clap::{crate_version, App, Arg}; - -mod options { - pub const BOURNE_SHELL: &str = "bourne-shell"; - pub const C_SHELL: &str = "c-shell"; - pub const PRINT_DATABASE: &str = "print-database"; - pub const FILE: &str = "FILE"; -} +pub mod app; +mod colors; -static SYNTAX: &str = "[OPTION]... [FILE]"; -static SUMMARY: &str = "Output commands to set the LS_COLORS environment variable."; -static LONG_HELP: &str = " - If FILE is specified, read it to determine which colors to use for which - file types and extensions. Otherwise, a precompiled database is used. - For details on the format of these files, run 'dircolors --print-database' -"; +use crate::app::get_app; +use crate::app::options; -mod colors; use self::colors::INTERNAL_DB; +const SYNTAX: &str = "[OPTION]... [FILE]"; + #[derive(PartialEq, Debug)] pub enum OutputFmt { Shell, @@ -73,35 +63,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(SUMMARY) + let matches = get_app(executable!()) .usage(&usage[..]) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::BOURNE_SHELL) - .long("sh") - .short("b") - .visible_alias("bourne-shell") - .help("output Bourne shell code to set LS_COLORS") - .display_order(1), - ) - .arg( - Arg::with_name(options::C_SHELL) - .long("csh") - .short("c") - .visible_alias("c-shell") - .help("output C shell code to set LS_COLORS") - .display_order(2), - ) - .arg( - Arg::with_name(options::PRINT_DATABASE) - .long("print-database") - .short("p") - .help("print the byte counts") - .display_order(3), - ) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) .get_matches_from(&args); let files = matches diff --git a/src/uu/dirname/src/app.rs b/src/uu/dirname/src/app.rs new file mode 100644 index 00000000000..79d7d988353 --- /dev/null +++ b/src/uu/dirname/src/app.rs @@ -0,0 +1,21 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "strip last component from file name"; + +pub mod options { + pub const ZERO: &str = "zero"; + pub const DIR: &str = "dir"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .about(ABOUT) + .version(crate_version!()) + .arg( + Arg::with_name(options::ZERO) + .long(options::ZERO) + .short("z") + .help("separate output with NUL rather than newline"), + ) + .arg(Arg::with_name(options::DIR).hidden(true).multiple(true)) +} diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index ad42517d4bc..fe28c9f1f16 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -5,19 +5,15 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +pub mod app; + #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::path::Path; use uucore::InvalidEncodingHandling; -static ABOUT: &str = "strip last component from file name"; - -mod options { - pub const ZERO: &str = "zero"; - pub const DIR: &str = "dir"; -} +use crate::app::{get_app, options}; fn get_usage() -> String { format!("{0} [OPTION] NAME...", executable!()) @@ -38,18 +34,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_long_usage(); - let matches = App::new(executable!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) .after_help(&after_help[..]) - .version(crate_version!()) - .arg( - Arg::with_name(options::ZERO) - .long(options::ZERO) - .short("z") - .help("separate output with NUL rather than newline"), - ) - .arg(Arg::with_name(options::DIR).hidden(true).multiple(true)) .get_matches_from(args); let separator = if matches.is_present(options::ZERO) { diff --git a/src/uu/du/src/app.rs b/src/uu/du/src/app.rs new file mode 100644 index 00000000000..c2ec9e88570 --- /dev/null +++ b/src/uu/du/src/app.rs @@ -0,0 +1,199 @@ +use clap::{crate_version, App, Arg}; + +pub mod options { + pub const NULL: &str = "0"; + pub const ALL: &str = "all"; + pub const APPARENT_SIZE: &str = "apparent-size"; + pub const BLOCK_SIZE: &str = "block-size"; + pub const BYTES: &str = "b"; + pub const TOTAL: &str = "c"; + pub const MAX_DEPTH: &str = "d"; + pub const HUMAN_READABLE: &str = "h"; + pub const BLOCK_SIZE_1K: &str = "k"; + pub const COUNT_LINKS: &str = "l"; + pub const BLOCK_SIZE_1M: &str = "m"; + pub const SEPARATE_DIRS: &str = "S"; + pub const SUMMARIZE: &str = "s"; + pub const SI: &str = "si"; + pub const TIME: &str = "time"; + pub const TIME_STYLE: &str = "time-style"; + pub const ONE_FILE_SYSTEM: &str = "one-file-system"; + pub const FILE: &str = "FILE"; +} + +const SUMMARY: &str = "estimate file space usage"; +const LONG_HELP: &str = " +Display values are in units of the first available SIZE from --block-size, +and the DU_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables. +Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set). + +SIZE is an integer and optional unit (example: 10M is 10*1024*1024). +Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers +of 1000). +"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(SUMMARY) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::ALL) + .short("a") + .long(options::ALL) + .help("write counts for all files, not just directories"), + ) + .arg( + Arg::with_name(options::APPARENT_SIZE) + .long(options::APPARENT_SIZE) + .help( + "print apparent sizes, rather than disk usage \ + although the apparent size is usually smaller, it may be larger due to holes \ + in ('sparse') files, internal fragmentation, indirect blocks, and the like" + ) + .alias("app") // The GNU test suite uses this alias + ) + .arg( + Arg::with_name(options::BLOCK_SIZE) + .short("B") + .long(options::BLOCK_SIZE) + .value_name("SIZE") + .help( + "scale sizes by SIZE before printing them. \ + E.g., '-BM' prints sizes in units of 1,048,576 bytes. See SIZE format below." + ) + ) + .arg( + Arg::with_name(options::BYTES) + .short("b") + .long("bytes") + .help("equivalent to '--apparent-size --block-size=1'") + ) + .arg( + Arg::with_name(options::TOTAL) + .long("total") + .short("c") + .help("produce a grand total") + ) + .arg( + Arg::with_name(options::MAX_DEPTH) + .short("d") + .long("max-depth") + .value_name("N") + .help( + "print the total for a directory (or file, with --all) \ + only if it is N or fewer levels below the command \ + line argument; --max-depth=0 is the same as --summarize" + ) + ) + .arg( + Arg::with_name(options::HUMAN_READABLE) + .long("human-readable") + .short("h") + .help("print sizes in human readable format (e.g., 1K 234M 2G)") + ) + .arg( + Arg::with_name("inodes") + .long("inodes") + .help( + "list inode usage information instead of block usage like --block-size=1K" + ) + ) + .arg( + Arg::with_name(options::BLOCK_SIZE_1K) + .short("k") + .help("like --block-size=1K") + ) + .arg( + Arg::with_name(options::COUNT_LINKS) + .short("l") + .long("count-links") + .help("count sizes many times if hard linked") + ) + // .arg( + // Arg::with_name("dereference") + // .short("L") + // .long("dereference") + // .help("dereference all symbolic links") + // ) + // .arg( + // Arg::with_name("no-dereference") + // .short("P") + // .long("no-dereference") + // .help("don't follow any symbolic links (this is the default)") + // ) + .arg( + Arg::with_name(options::BLOCK_SIZE_1M) + .short("m") + .help("like --block-size=1M") + ) + .arg( + Arg::with_name(options::NULL) + .short("0") + .long("null") + .help("end each output line with 0 byte rather than newline") + ) + .arg( + Arg::with_name(options::SEPARATE_DIRS) + .short("S") + .long("separate-dirs") + .help("do not include size of subdirectories") + ) + .arg( + Arg::with_name(options::SUMMARIZE) + .short("s") + .long("summarize") + .help("display only a total for each argument") + ) + .arg( + Arg::with_name(options::SI) + .long(options::SI) + .help("like -h, but use powers of 1000 not 1024") + ) + .arg( + Arg::with_name(options::ONE_FILE_SYSTEM) + .short("x") + .long(options::ONE_FILE_SYSTEM) + .help("skip directories on different file systems") + ) + // .arg( + // Arg::with_name("") + // .short("x") + // .long("exclude-from") + // .value_name("FILE") + // .help("exclude files that match any pattern in FILE") + // ) + // .arg( + // Arg::with_name("exclude") + // .long("exclude") + // .value_name("PATTERN") + // .help("exclude files that match PATTERN") + // ) + .arg( + Arg::with_name(options::TIME) + .long(options::TIME) + .value_name("WORD") + .require_equals(true) + .min_values(0) + .possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"]) + .help( + "show time of the last modification of any file in the \ + directory, or any of its subdirectories. If WORD is given, show time as WORD instead \ + of modification time: atime, access, use, ctime, status, birth or creation" + ) + ) + .arg( + Arg::with_name(options::TIME_STYLE) + .long(options::TIME_STYLE) + .value_name("STYLE") + .help( + "show times using style STYLE: \ + full-iso, long-iso, iso, +FORMAT FORMAT is interpreted like 'date'" + ) + ) + .arg( + Arg::with_name(options::FILE) + .hidden(true) + .multiple(true) + ) +} diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index b7c53eb72c2..947f72c231a 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -5,12 +5,13 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +pub mod app; + #[macro_use] extern crate uucore; use chrono::prelude::DateTime; use chrono::Local; -use clap::{crate_version, App, Arg}; use std::collections::HashSet; use std::convert::TryFrom; use std::env; @@ -42,38 +43,8 @@ use winapi::um::winbase::GetFileInformationByHandleEx; #[cfg(windows)] use winapi::um::winnt::{FILE_ID_128, ULONGLONG}; -mod options { - pub const NULL: &str = "0"; - pub const ALL: &str = "all"; - pub const APPARENT_SIZE: &str = "apparent-size"; - pub const BLOCK_SIZE: &str = "block-size"; - pub const BYTES: &str = "b"; - pub const TOTAL: &str = "c"; - pub const MAX_DEPTH: &str = "d"; - pub const HUMAN_READABLE: &str = "h"; - pub const BLOCK_SIZE_1K: &str = "k"; - pub const COUNT_LINKS: &str = "l"; - pub const BLOCK_SIZE_1M: &str = "m"; - pub const SEPARATE_DIRS: &str = "S"; - pub const SUMMARIZE: &str = "s"; - pub const SI: &str = "si"; - pub const TIME: &str = "time"; - pub const TIME_STYLE: &str = "time-style"; - pub const ONE_FILE_SYSTEM: &str = "one-file-system"; - pub const FILE: &str = "FILE"; -} - -const NAME: &str = "du"; -const SUMMARY: &str = "estimate file space usage"; -const LONG_HELP: &str = " -Display values are in units of the first available SIZE from --block-size, -and the DU_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables. -Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set). - -SIZE is an integer and optional unit (example: 10M is 10*1024*1024). -Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers -of 1000). -"; +use crate::app::get_app; +use crate::app::options; // TODO: Support Z & Y (currently limited by size of u64) const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)]; @@ -386,170 +357,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(SUMMARY) + let matches = get_app(executable!()) .usage(&usage[..]) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::ALL) - .short("a") - .long(options::ALL) - .help("write counts for all files, not just directories"), - ) - .arg( - Arg::with_name(options::APPARENT_SIZE) - .long(options::APPARENT_SIZE) - .help( - "print apparent sizes, rather than disk usage \ - although the apparent size is usually smaller, it may be larger due to holes \ - in ('sparse') files, internal fragmentation, indirect blocks, and the like" - ) - .alias("app") // The GNU test suite uses this alias - ) - .arg( - Arg::with_name(options::BLOCK_SIZE) - .short("B") - .long(options::BLOCK_SIZE) - .value_name("SIZE") - .help( - "scale sizes by SIZE before printing them. \ - E.g., '-BM' prints sizes in units of 1,048,576 bytes. See SIZE format below." - ) - ) - .arg( - Arg::with_name(options::BYTES) - .short("b") - .long("bytes") - .help("equivalent to '--apparent-size --block-size=1'") - ) - .arg( - Arg::with_name(options::TOTAL) - .long("total") - .short("c") - .help("produce a grand total") - ) - .arg( - Arg::with_name(options::MAX_DEPTH) - .short("d") - .long("max-depth") - .value_name("N") - .help( - "print the total for a directory (or file, with --all) \ - only if it is N or fewer levels below the command \ - line argument; --max-depth=0 is the same as --summarize" - ) - ) - .arg( - Arg::with_name(options::HUMAN_READABLE) - .long("human-readable") - .short("h") - .help("print sizes in human readable format (e.g., 1K 234M 2G)") - ) - .arg( - Arg::with_name("inodes") - .long("inodes") - .help( - "list inode usage information instead of block usage like --block-size=1K" - ) - ) - .arg( - Arg::with_name(options::BLOCK_SIZE_1K) - .short("k") - .help("like --block-size=1K") - ) - .arg( - Arg::with_name(options::COUNT_LINKS) - .short("l") - .long("count-links") - .help("count sizes many times if hard linked") - ) - // .arg( - // Arg::with_name("dereference") - // .short("L") - // .long("dereference") - // .help("dereference all symbolic links") - // ) - // .arg( - // Arg::with_name("no-dereference") - // .short("P") - // .long("no-dereference") - // .help("don't follow any symbolic links (this is the default)") - // ) - .arg( - Arg::with_name(options::BLOCK_SIZE_1M) - .short("m") - .help("like --block-size=1M") - ) - .arg( - Arg::with_name(options::NULL) - .short("0") - .long("null") - .help("end each output line with 0 byte rather than newline") - ) - .arg( - Arg::with_name(options::SEPARATE_DIRS) - .short("S") - .long("separate-dirs") - .help("do not include size of subdirectories") - ) - .arg( - Arg::with_name(options::SUMMARIZE) - .short("s") - .long("summarize") - .help("display only a total for each argument") - ) - .arg( - Arg::with_name(options::SI) - .long(options::SI) - .help("like -h, but use powers of 1000 not 1024") - ) - .arg( - Arg::with_name(options::ONE_FILE_SYSTEM) - .short("x") - .long(options::ONE_FILE_SYSTEM) - .help("skip directories on different file systems") - ) - // .arg( - // Arg::with_name("") - // .short("x") - // .long("exclude-from") - // .value_name("FILE") - // .help("exclude files that match any pattern in FILE") - // ) - // .arg( - // Arg::with_name("exclude") - // .long("exclude") - // .value_name("PATTERN") - // .help("exclude files that match PATTERN") - // ) - .arg( - Arg::with_name(options::TIME) - .long(options::TIME) - .value_name("WORD") - .require_equals(true) - .min_values(0) - .possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"]) - .help( - "show time of the last modification of any file in the \ - directory, or any of its subdirectories. If WORD is given, show time as WORD instead \ - of modification time: atime, access, use, ctime, status, birth or creation" - ) - ) - .arg( - Arg::with_name(options::TIME_STYLE) - .long(options::TIME_STYLE) - .value_name("STYLE") - .help( - "show times using style STYLE: \ - full-iso, long-iso, iso, +FORMAT FORMAT is interpreted like 'date'" - ) - ) - .arg( - Arg::with_name(options::FILE) - .hidden(true) - .multiple(true) - ) .get_matches_from(args); let summarize = matches.is_present(options::SUMMARIZE); @@ -570,7 +379,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let options = Options { all: matches.is_present(options::ALL), - program_name: NAME.to_owned(), + program_name: executable!().to_owned(), max_depth, total: matches.is_present(options::TOTAL), separate_dirs: matches.is_present(options::SEPARATE_DIRS), @@ -620,7 +429,7 @@ Valid arguments are: - 'iso' Try '{} --help' for more information.", s, - NAME + executable!() ); return 1; } diff --git a/src/uu/echo/src/app.rs b/src/uu/echo/src/app.rs new file mode 100644 index 00000000000..04b24d388ab --- /dev/null +++ b/src/uu/echo/src/app.rs @@ -0,0 +1,68 @@ +use clap::{crate_version, App, Arg}; + +const SUMMARY: &str = "display a line of text"; +const USAGE: &str = "[OPTIONS]... [STRING]..."; +const AFTER_HELP: &str = r#" + Echo the STRING(s) to standard output. + + If -e is in effect, the following sequences are recognized: + + \\\\ backslash + \\a alert (BEL) + \\b backspace + \\c produce no further output + \\e escape + \\f form feed + \\n new line + \\r carriage return + \\t horizontal tab + \\v vertical tab + \\0NNN byte with octal value NNN (1 to 3 digits) + \\xHH byte with hexadecimal value HH (1 to 2 digits) +"#; + +pub mod options { + pub const STRING: &str = "STRING"; + pub const NO_NEWLINE: &str = "no_newline"; + pub const ENABLE_BACKSLASH_ESCAPE: &str = "enable_backslash_escape"; + pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + // TrailingVarArg specifies the final positional argument is a VarArg + // and it doesn't attempts the parse any further args. + // Final argument must have multiple(true) or the usage string equivalent. + .setting(clap::AppSettings::TrailingVarArg) + .setting(clap::AppSettings::AllowLeadingHyphen) + .version(crate_version!()) + .about(SUMMARY) + .after_help(AFTER_HELP) + .usage(USAGE) + .arg( + Arg::with_name(options::NO_NEWLINE) + .short("n") + .help("do not output the trailing newline") + .takes_value(false) + .display_order(1), + ) + .arg( + Arg::with_name(options::ENABLE_BACKSLASH_ESCAPE) + .short("e") + .help("enable interpretation of backslash escapes") + .takes_value(false) + .display_order(2), + ) + .arg( + Arg::with_name(options::DISABLE_BACKSLASH_ESCAPE) + .short("E") + .help("disable interpretation of backslash escapes (default)") + .takes_value(false) + .display_order(3), + ) + .arg( + Arg::with_name(options::STRING) + .multiple(true) + .allow_hyphen_values(true), + ) +} diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index d83a4fe0606..46065c03a63 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -6,43 +6,17 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +pub mod app; + #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::io::{self, Write}; use std::iter::Peekable; use std::str::Chars; use uucore::InvalidEncodingHandling; -const NAME: &str = "echo"; -const SUMMARY: &str = "display a line of text"; -const USAGE: &str = "[OPTIONS]... [STRING]..."; -const AFTER_HELP: &str = r#" - Echo the STRING(s) to standard output. - - If -e is in effect, the following sequences are recognized: - - \\\\ backslash - \\a alert (BEL) - \\b backspace - \\c produce no further output - \\e escape - \\f form feed - \\n new line - \\r carriage return - \\t horizontal tab - \\v vertical tab - \\0NNN byte with octal value NNN (1 to 3 digits) - \\xHH byte with hexadecimal value HH (1 to 2 digits) -"#; - -mod options { - pub const STRING: &str = "STRING"; - pub const NO_NEWLINE: &str = "no_newline"; - pub const ENABLE_BACKSLASH_ESCAPE: &str = "enable_backslash_escape"; - pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape"; -} +use crate::app::{get_app, options}; fn parse_code( input: &mut Peekable, @@ -117,44 +91,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - // TrailingVarArg specifies the final positional argument is a VarArg - // and it doesn't attempts the parse any further args. - // Final argument must have multiple(true) or the usage string equivalent. - .setting(clap::AppSettings::TrailingVarArg) - .setting(clap::AppSettings::AllowLeadingHyphen) - .version(crate_version!()) - .about(SUMMARY) - .after_help(AFTER_HELP) - .usage(USAGE) - .arg( - Arg::with_name(options::NO_NEWLINE) - .short("n") - .help("do not output the trailing newline") - .takes_value(false) - .display_order(1), - ) - .arg( - Arg::with_name(options::ENABLE_BACKSLASH_ESCAPE) - .short("e") - .help("enable interpretation of backslash escapes") - .takes_value(false) - .display_order(2), - ) - .arg( - Arg::with_name(options::DISABLE_BACKSLASH_ESCAPE) - .short("E") - .help("disable interpretation of backslash escapes (default)") - .takes_value(false) - .display_order(3), - ) - .arg( - Arg::with_name(options::STRING) - .multiple(true) - .allow_hyphen_values(true), - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let no_newline = matches.is_present(options::NO_NEWLINE); let escaped = matches.is_present(options::ENABLE_BACKSLASH_ESCAPE); diff --git a/src/uu/env/src/app.rs b/src/uu/env/src/app.rs new file mode 100644 index 00000000000..9bf2d85c2cf --- /dev/null +++ b/src/uu/env/src/app.rs @@ -0,0 +1,50 @@ +use clap::{crate_description, crate_version, App, AppSettings, Arg}; + +// spell-checker:ignore (ToDO) chdir subcommands + +const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]"; +const AFTER_HELP: &str = "\ +A mere - implies -i. If no COMMAND, print the resulting environment. +"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(crate_description!()) + .usage(USAGE) + .after_help(AFTER_HELP) + .setting(AppSettings::AllowExternalSubcommands) + .arg(Arg::with_name("ignore-environment") + .short("i") + .long("ignore-environment") + .help("start with an empty environment")) + .arg(Arg::with_name("chdir") + .short("c") + .long("chdir") + .takes_value(true) + .number_of_values(1) + .value_name("DIR") + .help("change working directory to DIR")) + .arg(Arg::with_name("null") + .short("0") + .long("null") + .help("end each output line with a 0 byte rather than a newline (only valid when \ + printing the environment)")) + .arg(Arg::with_name("file") + .short("f") + .long("file") + .takes_value(true) + .number_of_values(1) + .value_name("PATH") + .multiple(true) + .help("read and set variables from a \".env\"-style configuration file (prior to any \ + unset and/or set)")) + .arg(Arg::with_name("unset") + .short("u") + .long("unset") + .takes_value(true) + .number_of_values(1) + .value_name("NAME") + .multiple(true) + .help("remove variable from the environment")) +} diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 0ea66d7e9c8..f04dfb1121c 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -9,21 +9,20 @@ // spell-checker:ignore (ToDO) chdir execvp progname subcommand subcommands unsets +pub mod app; + #[macro_use] extern crate clap; -use clap::{App, AppSettings, Arg}; use ini::Ini; use std::borrow::Cow; use std::env; use std::io::{self, Write}; use std::iter::Iterator; use std::process::Command; +use uucore::executable; -const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]"; -const AFTER_HELP: &str = "\ -A mere - implies -i. If no COMMAND, print the resulting environment. -"; +use crate::app::get_app; struct Options<'a> { ignore_env: bool, @@ -114,51 +113,8 @@ fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b (progname, &args[..]) } -fn create_app() -> App<'static, 'static> { - App::new(crate_name!()) - .version(crate_version!()) - .author(crate_authors!()) - .about(crate_description!()) - .usage(USAGE) - .after_help(AFTER_HELP) - .setting(AppSettings::AllowExternalSubcommands) - .arg(Arg::with_name("ignore-environment") - .short("i") - .long("ignore-environment") - .help("start with an empty environment")) - .arg(Arg::with_name("chdir") - .short("c") - .long("chdir") - .takes_value(true) - .number_of_values(1) - .value_name("DIR") - .help("change working directory to DIR")) - .arg(Arg::with_name("null") - .short("0") - .long("null") - .help("end each output line with a 0 byte rather than a newline (only valid when \ - printing the environment)")) - .arg(Arg::with_name("file") - .short("f") - .long("file") - .takes_value(true) - .number_of_values(1) - .value_name("PATH") - .multiple(true) - .help("read and set variables from a \".env\"-style configuration file (prior to any \ - unset and/or set)")) - .arg(Arg::with_name("unset") - .short("u") - .long("unset") - .takes_value(true) - .number_of_values(1) - .value_name("NAME") - .multiple(true) - .help("remove variable from the environment")) -} - fn run_env(args: impl uucore::Args) -> Result<(), i32> { - let app = create_app(); + let app = get_app(executable!()); let matches = app.get_matches_from(args); let ignore_env = matches.is_present("ignore-environment"); diff --git a/src/uu/expand/src/app.rs b/src/uu/expand/src/app.rs new file mode 100644 index 00000000000..42a5bbbde81 --- /dev/null +++ b/src/uu/expand/src/app.rs @@ -0,0 +1,45 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output. + With no FILE, or when FILE is -, read standard input."; + +const LONG_HELP: &str = ""; + +pub mod options { + pub static TABS: &str = "tabs"; + pub static INITIAL: &str = "initial"; + pub static NO_UTF8: &str = "no-utf8"; + pub static FILES: &str = "FILES"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::INITIAL) + .long(options::INITIAL) + .short("i") + .help("do not convert tabs after non blanks"), + ) + .arg( + Arg::with_name(options::TABS) + .long(options::TABS) + .short("t") + .value_name("N, LIST") + .takes_value(true) + .help("have tabs N characters apart, not 8 or use comma separated list of explicit tab positions"), + ) + .arg( + Arg::with_name(options::NO_UTF8) + .long(options::NO_UTF8) + .short("U") + .help("interpret input file as 8-bit ASCII rather than UTF-8"), + ).arg( + Arg::with_name(options::FILES) + .multiple(true) + .hidden(true) + .takes_value(true) + ) +} diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index d9d669e7ca9..f9632c2707b 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -9,26 +9,18 @@ // spell-checker:ignore (ToDO) ctype cwidth iflag nbytes nspaces nums tspaces uflag +pub mod app; + #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::ArgMatches; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::str::from_utf8; use unicode_width::UnicodeWidthChar; -static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output. - With no FILE, or when FILE is -, read standard input."; - -pub mod options { - pub static TABS: &str = "tabs"; - pub static INITIAL: &str = "initial"; - pub static NO_UTF8: &str = "no-utf8"; - pub static FILES: &str = "FILES"; -} - -static LONG_HELP: &str = ""; +use crate::app::{get_app, options}; static DEFAULT_TABSTOP: usize = 8; @@ -108,36 +100,8 @@ impl Options { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::INITIAL) - .long(options::INITIAL) - .short("i") - .help("do not convert tabs after non blanks"), - ) - .arg( - Arg::with_name(options::TABS) - .long(options::TABS) - .short("t") - .value_name("N, LIST") - .takes_value(true) - .help("have tabs N characters apart, not 8 or use comma separated list of explicit tab positions"), - ) - .arg( - Arg::with_name(options::NO_UTF8) - .long(options::NO_UTF8) - .short("U") - .help("interpret input file as 8-bit ASCII rather than UTF-8"), - ).arg( - Arg::with_name(options::FILES) - .multiple(true) - .hidden(true) - .takes_value(true) - ) .get_matches_from(args); expand(Options::new(&matches)); diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index ed992bf71a0..96b5f3cb8f5 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/expr.rs" [dependencies] +clap = "2.33" libc = "0.2.42" num-bigint = "0.4.0" num-traits = "0.2.14" diff --git a/src/uu/expr/src/app.rs b/src/uu/expr/src/app.rs new file mode 100644 index 00000000000..a469b2bf869 --- /dev/null +++ b/src/uu/expr/src/app.rs @@ -0,0 +1,10 @@ +use clap::{App, Arg}; + +const VERSION: &str = "version"; +const HELP: &str = "help"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .arg(Arg::with_name(VERSION).long(VERSION)) + .arg(Arg::with_name(HELP).long(HELP)) +} diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 8238917f74d..bc6c041fda4 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -10,6 +10,7 @@ extern crate uucore; use uucore::InvalidEncodingHandling; +pub mod app; mod syntax_tree; mod tokens; diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index eb977760f24..3c2d94f747c 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -13,6 +13,7 @@ edition = "2018" [build-dependencies] num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs +clap = "2.33" [dependencies] coz = { version = "0.1.3", optional = true } diff --git a/src/uu/factor/src/app.rs b/src/uu/factor/src/app.rs new file mode 100644 index 00000000000..d6eb7ab46a5 --- /dev/null +++ b/src/uu/factor/src/app.rs @@ -0,0 +1,15 @@ +use clap::{crate_version, App, Arg}; + +pub mod options { + pub const NUMBER: &str = "NUMBER"; +} + +const SUMMARY: &str = "Print the prime factors of the given NUMBER(s). +If none are specified, read from standard input."; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(SUMMARY) + .arg(Arg::with_name(options::NUMBER).multiple(true)) +} diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs index af5e3cdb022..7191d636403 100644 --- a/src/uu/factor/src/cli.rs +++ b/src/uu/factor/src/cli.rs @@ -12,22 +12,17 @@ extern crate uucore; use std::error::Error; use std::io::{self, stdin, stdout, BufRead, Write}; +pub mod app; mod factor; -use clap::{crate_version, App, Arg}; pub use factor::*; +use crate::app::{get_app, options}; + mod miller_rabin; pub mod numeric; mod rho; pub mod table; -static SUMMARY: &str = "Print the prime factors of the given NUMBER(s). -If none are specified, read from standard input."; - -mod options { - pub static NUMBER: &str = "NUMBER"; -} - fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box> { num_str .parse::() @@ -36,11 +31,7 @@ fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box i32 { - let matches = App::new(executable!()) - .version(crate_version!()) - .about(SUMMARY) - .arg(Arg::with_name(options::NUMBER).multiple(true)) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let stdout = stdout(); let mut w = io::BufWriter::new(stdout.lock()); diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index d7cbcd13aa0..644051d5957 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/false.rs" [dependencies] +clap = "2.33.3" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/false/src/app.rs b/src/uu/false/src/app.rs new file mode 100644 index 00000000000..ac7e0c2d7da --- /dev/null +++ b/src/uu/false/src/app.rs @@ -0,0 +1,7 @@ +use clap::{App, AppSettings}; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .setting(AppSettings::DisableHelpFlags) + .setting(AppSettings::DisableVersion) +} diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index 917c43fa0cd..177651c5aca 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -5,6 +5,8 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +pub mod app; + pub fn uumain(_: impl uucore::Args) -> i32 { 1 } diff --git a/src/uu/fmt/src/app.rs b/src/uu/fmt/src/app.rs new file mode 100644 index 00000000000..4d1b382362d --- /dev/null +++ b/src/uu/fmt/src/app.rs @@ -0,0 +1,145 @@ +use clap::{crate_version, App, Arg}; + +// spell-checker:ignore (ToDO) PSKIP tabwidth + +const ABOUT: &str = "Reformat paragraphs from input files (or stdin) to stdout."; + +pub const OPT_CROWN_MARGIN: &str = "crown-margin"; +pub const OPT_TAGGED_PARAGRAPH: &str = "tagged-paragraph"; +pub const OPT_PRESERVE_HEADERS: &str = "preserve-headers"; +pub const OPT_SPLIT_ONLY: &str = "split-only"; +pub const OPT_UNIFORM_SPACING: &str = "uniform-spacing"; +pub const OPT_PREFIX: &str = "prefix"; +pub const OPT_SKIP_PREFIX: &str = "skip-prefix"; +pub const OPT_EXACT_PREFIX: &str = "exact-prefix"; +pub const OPT_EXACT_SKIP_PREFIX: &str = "exact-skip-prefix"; +pub const OPT_WIDTH: &str = "width"; +pub const OPT_GOAL: &str = "goal"; +pub const OPT_QUICK: &str = "quick"; +pub const OPT_TAB_WIDTH: &str = "tab-width"; + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_CROWN_MARGIN) + .short("c") + .long(OPT_CROWN_MARGIN) + .help( + "First and second line of paragraph + may have different indentations, in which + case the first line's indentation is preserved, + and each subsequent line's indentation matches the second line.", + ), + ) + .arg( + Arg::with_name(OPT_TAGGED_PARAGRAPH) + .short("t") + .long("tagged-paragraph") + .help( + "Like -c, except that the first and second line of a paragraph *must* + have different indentation or they are treated as separate paragraphs.", + ), + ) + .arg( + Arg::with_name(OPT_PRESERVE_HEADERS) + .short("m") + .long("preserve-headers") + .help( + "Attempt to detect and preserve mail headers in the input. + Be careful when combining this flag with -p.", + ), + ) + .arg( + Arg::with_name(OPT_SPLIT_ONLY) + .short("s") + .long("split-only") + .help("Split lines only, do not reflow."), + ) + .arg( + Arg::with_name(OPT_UNIFORM_SPACING) + .short("u") + .long("uniform-spacing") + .help( + "Insert exactly one + space between words, and two between sentences. + Sentence breaks in the input are detected as [?!.] + followed by two spaces or a newline; other punctuation + is not interpreted as a sentence break.", + ), + ) + .arg( + Arg::with_name(OPT_PREFIX) + .short("p") + .long("prefix") + .help( + "Reformat only lines + beginning with PREFIX, reattaching PREFIX to reformatted lines. + Unless -x is specified, leading whitespace will be ignored + when matching PREFIX.", + ) + .value_name("PREFIX"), + ) + .arg( + Arg::with_name(OPT_SKIP_PREFIX) + .short("P") + .long("skip-prefix") + .help( + "Do not reformat lines + beginning with PSKIP. Unless -X is specified, leading whitespace + will be ignored when matching PSKIP", + ) + .value_name("PSKIP"), + ) + .arg( + Arg::with_name(OPT_EXACT_PREFIX) + .short("x") + .long("exact-prefix") + .help( + "PREFIX must match at the + beginning of the line with no preceding whitespace.", + ), + ) + .arg( + Arg::with_name(OPT_EXACT_SKIP_PREFIX) + .short("X") + .long("exact-skip-prefix") + .help( + "PSKIP must match at the + beginning of the line with no preceding whitespace.", + ), + ) + .arg( + Arg::with_name(OPT_WIDTH) + .short("w") + .long("width") + .help("Fill output lines up to a maximum of WIDTH columns, default 79.") + .value_name("WIDTH"), + ) + .arg( + Arg::with_name(OPT_GOAL) + .short("g") + .long("goal") + .help("Goal width, default ~0.94*WIDTH. Must be less than WIDTH.") + .value_name("GOAL"), + ) + .arg(Arg::with_name(OPT_QUICK).short("q").long("quick").help( + "Break lines more quickly at the + expense of a potentially more ragged appearance.", + )) + .arg( + Arg::with_name(OPT_TAB_WIDTH) + .short("T") + .long("tab-width") + .help( + "Treat tabs as TABWIDTH spaces for + determining line length, default 8. Note that this is used only for + calculating line lengths; tabs are preserved in the output.", + ) + .value_name("TABWIDTH"), + ) + .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) +} diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 91f59e0769d..745680a0b06 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -10,12 +10,13 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::cmp; use std::fs::File; use std::io::{stdin, stdout, Write}; use std::io::{BufReader, BufWriter, Read}; +use app::*; + use self::linebreak::break_lines; use self::parasplit::ParagraphStream; @@ -28,27 +29,11 @@ macro_rules! silent_unwrap( ) ); +pub mod app; mod linebreak; mod parasplit; -static ABOUT: &str = "Reformat paragraphs from input files (or stdin) to stdout."; -static MAX_WIDTH: usize = 2500; - -static OPT_CROWN_MARGIN: &str = "crown-margin"; -static OPT_TAGGED_PARAGRAPH: &str = "tagged-paragraph"; -static OPT_PRESERVE_HEADERS: &str = "preserve-headers"; -static OPT_SPLIT_ONLY: &str = "split-only"; -static OPT_UNIFORM_SPACING: &str = "uniform-spacing"; -static OPT_PREFIX: &str = "prefix"; -static OPT_SKIP_PREFIX: &str = "skip-prefix"; -static OPT_EXACT_PREFIX: &str = "exact-prefix"; -static OPT_EXACT_SKIP_PREFIX: &str = "exact-skip-prefix"; -static OPT_WIDTH: &str = "width"; -static OPT_GOAL: &str = "goal"; -static OPT_QUICK: &str = "quick"; -static OPT_TAB_WIDTH: &str = "tab-width"; - -static ARG_FILES: &str = "files"; +const MAX_WIDTH: usize = 2500; fn get_usage() -> String { format!("{} [OPTION]... [FILE]...", executable!()) @@ -77,128 +62,8 @@ pub struct FmtOptions { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(OPT_CROWN_MARGIN) - .short("c") - .long(OPT_CROWN_MARGIN) - .help( - "First and second line of paragraph - may have different indentations, in which - case the first line's indentation is preserved, - and each subsequent line's indentation matches the second line.", - ), - ) - .arg( - Arg::with_name(OPT_TAGGED_PARAGRAPH) - .short("t") - .long("tagged-paragraph") - .help( - "Like -c, except that the first and second line of a paragraph *must* - have different indentation or they are treated as separate paragraphs.", - ), - ) - .arg( - Arg::with_name(OPT_PRESERVE_HEADERS) - .short("m") - .long("preserve-headers") - .help( - "Attempt to detect and preserve mail headers in the input. - Be careful when combining this flag with -p.", - ), - ) - .arg( - Arg::with_name(OPT_SPLIT_ONLY) - .short("s") - .long("split-only") - .help("Split lines only, do not reflow."), - ) - .arg( - Arg::with_name(OPT_UNIFORM_SPACING) - .short("u") - .long("uniform-spacing") - .help( - "Insert exactly one - space between words, and two between sentences. - Sentence breaks in the input are detected as [?!.] - followed by two spaces or a newline; other punctuation - is not interpreted as a sentence break.", - ), - ) - .arg( - Arg::with_name(OPT_PREFIX) - .short("p") - .long("prefix") - .help( - "Reformat only lines - beginning with PREFIX, reattaching PREFIX to reformatted lines. - Unless -x is specified, leading whitespace will be ignored - when matching PREFIX.", - ) - .value_name("PREFIX"), - ) - .arg( - Arg::with_name(OPT_SKIP_PREFIX) - .short("P") - .long("skip-prefix") - .help( - "Do not reformat lines - beginning with PSKIP. Unless -X is specified, leading whitespace - will be ignored when matching PSKIP", - ) - .value_name("PSKIP"), - ) - .arg( - Arg::with_name(OPT_EXACT_PREFIX) - .short("x") - .long("exact-prefix") - .help( - "PREFIX must match at the - beginning of the line with no preceding whitespace.", - ), - ) - .arg( - Arg::with_name(OPT_EXACT_SKIP_PREFIX) - .short("X") - .long("exact-skip-prefix") - .help( - "PSKIP must match at the - beginning of the line with no preceding whitespace.", - ), - ) - .arg( - Arg::with_name(OPT_WIDTH) - .short("w") - .long("width") - .help("Fill output lines up to a maximum of WIDTH columns, default 79.") - .value_name("WIDTH"), - ) - .arg( - Arg::with_name(OPT_GOAL) - .short("g") - .long("goal") - .help("Goal width, default ~0.94*WIDTH. Must be less than WIDTH.") - .value_name("GOAL"), - ) - .arg(Arg::with_name(OPT_QUICK).short("q").long("quick").help( - "Break lines more quickly at the - expense of a potentially more ragged appearance.", - )) - .arg( - Arg::with_name(OPT_TAB_WIDTH) - .short("T") - .long("tab-width") - .help( - "Treat tabs as TABWIDTH spaces for - determining line length, default 8. Note that this is used only for - calculating line lengths; tabs are preserved in the output.", - ) - .value_name("TABWIDTH"), - ) - .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) + let matches = get_app(executable!()) + .usage(usage.as_str()) .get_matches_from(args); let mut files: Vec = matches diff --git a/src/uu/fold/src/app.rs b/src/uu/fold/src/app.rs new file mode 100644 index 00000000000..23e6abec586 --- /dev/null +++ b/src/uu/fold/src/app.rs @@ -0,0 +1,46 @@ +use clap::{crate_version, App, Arg}; + +static SYNTAX: &str = "[OPTION]... [FILE]..."; +static SUMMARY: &str = "Writes each file (or standard input if no files are given) + to standard output whilst breaking long lines"; + +pub mod options { + pub const BYTES: &str = "bytes"; + pub const SPACES: &str = "spaces"; + pub const WIDTH: &str = "width"; + pub const FILE: &str = "file"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .usage(SYNTAX) + .about(SUMMARY) + .arg( + Arg::with_name(options::BYTES) + .long(options::BYTES) + .short("b") + .help( + "count using bytes rather than columns (meaning control characters \ + such as newline are not treated specially)", + ) + .takes_value(false), + ) + .arg( + Arg::with_name(options::SPACES) + .long(options::SPACES) + .short("s") + .help("break lines at word boundaries rather than a hard cut-off") + .takes_value(false), + ) + .arg( + Arg::with_name(options::WIDTH) + .long(options::WIDTH) + .short("w") + .help("set WIDTH as the maximum line width rather than 80") + .value_name("WIDTH") + .allow_hyphen_values(true) + .takes_value(true), + ) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) +} diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 118f7f5f940..db4d2daae30 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -10,25 +10,16 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; use uucore::InvalidEncodingHandling; -const TAB_WIDTH: usize = 8; +use crate::app::{get_app, options}; -static NAME: &str = "fold"; -static SYNTAX: &str = "[OPTION]... [FILE]..."; -static SUMMARY: &str = "Writes each file (or standard input if no files are given) - to standard output whilst breaking long lines"; +const TAB_WIDTH: usize = 8; -mod options { - pub const BYTES: &str = "bytes"; - pub const SPACES: &str = "spaces"; - pub const WIDTH: &str = "width"; - pub const FILE: &str = "file"; -} +pub mod app; pub fn uumain(args: impl uucore::Args) -> i32 { let args = args @@ -36,39 +27,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .accept_any(); let (args, obs_width) = handle_obsolete(&args[..]); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(SYNTAX) - .about(SUMMARY) - .arg( - Arg::with_name(options::BYTES) - .long(options::BYTES) - .short("b") - .help( - "count using bytes rather than columns (meaning control characters \ - such as newline are not treated specially)", - ) - .takes_value(false), - ) - .arg( - Arg::with_name(options::SPACES) - .long(options::SPACES) - .short("s") - .help("break lines at word boundaries rather than a hard cut-off") - .takes_value(false), - ) - .arg( - Arg::with_name(options::WIDTH) - .long(options::WIDTH) - .short("w") - .help("set WIDTH as the maximum line width rather than 80") - .value_name("WIDTH") - .allow_hyphen_values(true) - .takes_value(true), - ) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let bytes = matches.is_present(options::BYTES); let spaces = matches.is_present(options::SPACES); diff --git a/src/uu/groups/src/app.rs b/src/uu/groups/src/app.rs new file mode 100644 index 00000000000..f207d9c6ee0 --- /dev/null +++ b/src/uu/groups/src/app.rs @@ -0,0 +1,11 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "display current group names"; +pub const OPT_USER: &str = "user"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg(Arg::with_name(OPT_USER)) +} diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 07c25cebb7a..36d9ad1be51 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -12,10 +12,9 @@ extern crate uucore; use uucore::entries::{get_groups_gnu, gid2grp, Locate, Passwd}; -use clap::{crate_version, App, Arg}; +use crate::app::{get_app, OPT_USER}; -static ABOUT: &str = "display current group names"; -static OPT_USER: &str = "user"; +pub mod app; fn get_usage() -> String { format!("{0} [USERNAME]", executable!()) @@ -24,11 +23,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg(Arg::with_name(OPT_USER)) .get_matches_from(args); match matches.value_of(OPT_USER) { diff --git a/src/uu/hashsum/src/app.rs b/src/uu/hashsum/src/app.rs new file mode 100644 index 00000000000..01c29ad5d29 --- /dev/null +++ b/src/uu/hashsum/src/app.rs @@ -0,0 +1,139 @@ +use std::num::ParseIntError; + +use clap::{crate_version, App, Arg}; + +fn is_valid_bit_num(arg: String) -> Result<(), String> { + parse_bit_num(&arg) + .map(|_| ()) + .map_err(|e| format!("{}", e)) +} +// TODO: return custom error type +fn parse_bit_num(arg: &str) -> Result { + arg.parse() +} + +fn is_custom_binary(program: &str) -> bool { + matches!( + program, + "md5sum" + | "sha1sum" + | "sha224sum" + | "sha256sum" + | "sha384sum" + | "sha512sum" + | "sha3sum" + | "sha3-224sum" + | "sha3-256sum" + | "sha3-384sum" + | "sha3-512sum" + | "shake128sum" + | "shake256sum" + | "b2sum" + ) +} + +pub fn get_app(app_name: &str) -> App { + #[cfg(windows)] + const BINARY_HELP: &str = "read in binary mode (default)"; + #[cfg(not(windows))] + const BINARY_HELP: &str = "read in binary mode"; + #[cfg(windows)] + const TEXT_HELP: &str = "read in text mode"; + #[cfg(not(windows))] + const TEXT_HELP: &str = "read in text mode (default)"; + let mut app = App::new(app_name) + .version(crate_version!()) + .about("Compute and check message digests.") + .arg( + Arg::with_name("binary") + .short("b") + .long("binary") + .help(BINARY_HELP), + ) + .arg( + Arg::with_name("check") + .short("c") + .long("check") + .help("read hashsums from the FILEs and check them"), + ) + .arg( + Arg::with_name("tag") + .long("tag") + .help("create a BSD-style checksum"), + ) + .arg( + Arg::with_name("text") + .short("t") + .long("text") + .help(TEXT_HELP) + .conflicts_with("binary"), + ) + .arg( + Arg::with_name("quiet") + .short("q") + .long("quiet") + .help("don't print OK for each successfully verified file"), + ) + .arg( + Arg::with_name("status") + .short("s") + .long("status") + .help("don't output anything, status code shows success"), + ) + .arg( + Arg::with_name("strict") + .long("strict") + .help("exit non-zero for improperly formatted checksum lines"), + ) + .arg( + Arg::with_name("warn") + .short("w") + .long("warn") + .help("warn about improperly formatted checksum lines"), + ) + // Needed for variable-length output sums (e.g. SHAKE) + .arg( + Arg::with_name("bits") + .long("bits") + .help("set the size of the output (only for SHAKE)") + .takes_value(true) + .value_name("BITS") + // XXX: should we actually use validators? they're not particularly efficient + .validator(is_valid_bit_num), + ) + .arg( + Arg::with_name("FILE") + .index(1) + .multiple(true) + .value_name("FILE"), + ); + if !is_custom_binary(app_name) { + let algorithms = &[ + ("md5", "work with MD5"), + ("sha1", "work with SHA1"), + ("sha224", "work with SHA224"), + ("sha256", "work with SHA256"), + ("sha384", "work with SHA384"), + ("sha512", "work with SHA512"), + ("sha3", "work with SHA3"), + ("sha3-224", "work with SHA3-224"), + ("sha3-256", "work with SHA3-256"), + ("sha3-384", "work with SHA3-384"), + ("sha3-512", "work with SHA3-512"), + ( + "shake128", + "work with SHAKE128 using BITS for the output size", + ), + ( + "shake256", + "work with SHAKE256 using BITS for the output size", + ), + ("b2sum", "work with BLAKE2"), + ]; + + for (name, desc) in algorithms { + app = app.arg(Arg::with_name(name).long(name).help(desc)); + } + } + app +} diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index a007473ab3d..82f5457c00c 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -9,17 +9,17 @@ // spell-checker:ignore (ToDO) algo, algoname, regexes, nread -#[macro_use] -extern crate clap; - #[macro_use] extern crate uucore; +pub mod app; mod digest; +use crate::app::get_app; + use self::digest::Digest; -use clap::{App, Arg, ArgMatches}; +use clap::ArgMatches; use hex::ToHex; use md5::Context as Md5; use regex::Regex; @@ -31,7 +31,6 @@ use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Read}; use std::iter; -use std::num::ParseIntError; use std::path::Path; const NAME: &str = "hashsum"; @@ -49,26 +48,6 @@ struct Options { output_bits: usize, } -fn is_custom_binary(program: &str) -> bool { - matches!( - program, - "md5sum" - | "sha1sum" - | "sha224sum" - | "sha256sum" - | "sha384sum" - | "sha512sum" - | "sha3sum" - | "sha3-224sum" - | "sha3-256sum" - | "sha3-384sum" - | "sha3-512sum" - | "shake128sum" - | "shake256sum" - | "b2sum" - ) -} - #[allow(clippy::cognitive_complexity)] fn detect_algo<'a>( program: &str, @@ -261,17 +240,6 @@ fn detect_algo<'a>( } } -// TODO: return custom error type -fn parse_bit_num(arg: &str) -> Result { - arg.parse() -} - -fn is_valid_bit_num(arg: String) -> Result<(), String> { - parse_bit_num(&arg) - .map(|_| ()) - .map_err(|e| format!("{}", e)) -} - pub fn uumain(mut args: impl uucore::Args) -> i32 { // if there is no program name for some reason, default to "hashsum" let program = args.next().unwrap_or_else(|| OsString::from(NAME)); @@ -285,119 +253,7 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { // Default binary in Windows, text mode otherwise let binary_flag_default = cfg!(windows); - let binary_help = format!( - "read in binary mode{}", - if binary_flag_default { - " (default)" - } else { - "" - } - ); - - let text_help = format!( - "read in text mode{}", - if binary_flag_default { - "" - } else { - " (default)" - } - ); - - let mut app = App::new(executable!()) - .version(crate_version!()) - .about("Compute and check message digests.") - .arg( - Arg::with_name("binary") - .short("b") - .long("binary") - .help(&binary_help), - ) - .arg( - Arg::with_name("check") - .short("c") - .long("check") - .help("read hashsums from the FILEs and check them"), - ) - .arg( - Arg::with_name("tag") - .long("tag") - .help("create a BSD-style checksum"), - ) - .arg( - Arg::with_name("text") - .short("t") - .long("text") - .help(&text_help) - .conflicts_with("binary"), - ) - .arg( - Arg::with_name("quiet") - .short("q") - .long("quiet") - .help("don't print OK for each successfully verified file"), - ) - .arg( - Arg::with_name("status") - .short("s") - .long("status") - .help("don't output anything, status code shows success"), - ) - .arg( - Arg::with_name("strict") - .long("strict") - .help("exit non-zero for improperly formatted checksum lines"), - ) - .arg( - Arg::with_name("warn") - .short("w") - .long("warn") - .help("warn about improperly formatted checksum lines"), - ) - // Needed for variable-length output sums (e.g. SHAKE) - .arg( - Arg::with_name("bits") - .long("bits") - .help("set the size of the output (only for SHAKE)") - .takes_value(true) - .value_name("BITS") - // XXX: should we actually use validators? they're not particularly efficient - .validator(is_valid_bit_num), - ) - .arg( - Arg::with_name("FILE") - .index(1) - .multiple(true) - .value_name("FILE"), - ); - - if !is_custom_binary(&binary_name) { - let algorithms = &[ - ("md5", "work with MD5"), - ("sha1", "work with SHA1"), - ("sha224", "work with SHA224"), - ("sha256", "work with SHA256"), - ("sha384", "work with SHA384"), - ("sha512", "work with SHA512"), - ("sha3", "work with SHA3"), - ("sha3-224", "work with SHA3-224"), - ("sha3-256", "work with SHA3-256"), - ("sha3-384", "work with SHA3-384"), - ("sha3-512", "work with SHA3-512"), - ( - "shake128", - "work with SHAKE128 using BITS for the output size", - ), - ( - "shake256", - "work with SHAKE256 using BITS for the output size", - ), - ("b2sum", "work with BLAKE2"), - ]; - - for (name, desc) in algorithms { - app = app.arg(Arg::with_name(name).long(name).help(desc)); - } - } + let app = get_app(&binary_name); // FIXME: this should use get_matches_from_safe() and crash!(), but at the moment that just // causes "error: " to be printed twice (once from crash!() and once from clap). With diff --git a/src/uu/head/src/app.rs b/src/uu/head/src/app.rs new file mode 100644 index 00000000000..76a632853b2 --- /dev/null +++ b/src/uu/head/src/app.rs @@ -0,0 +1,82 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "\ +Print the first 10 lines of each FILE to standard output.\n\ +With more than one FILE, precede each with a header giving the file name.\n\ +\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +Mandatory arguments to long flags are mandatory for short flags too.\ +"; +const USAGE: &str = "head [FLAG]... [FILE]..."; + +pub mod options { + pub const BYTES_NAME: &str = "BYTES"; + pub const LINES_NAME: &str = "LINES"; + pub const QUIET_NAME: &str = "QUIET"; + pub const VERBOSE_NAME: &str = "VERBOSE"; + pub const ZERO_NAME: &str = "ZERO"; + pub const FILES_NAME: &str = "FILE"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .usage(USAGE) + .arg( + Arg::with_name(options::BYTES_NAME) + .short("c") + .long("bytes") + .value_name("[-]NUM") + .takes_value(true) + .help( + "\ + print the first NUM bytes of each file;\n\ + with the leading '-', print all but the last\n\ + NUM bytes of each file\ + ", + ) + .overrides_with_all(&[options::BYTES_NAME, options::LINES_NAME]) + .allow_hyphen_values(true), + ) + .arg( + Arg::with_name(options::LINES_NAME) + .short("n") + .long("lines") + .value_name("[-]NUM") + .takes_value(true) + .help( + "\ + print the first NUM lines instead of the first 10;\n\ + with the leading '-', print all but the last\n\ + NUM lines of each file\ + ", + ) + .overrides_with_all(&[options::LINES_NAME, options::BYTES_NAME]) + .allow_hyphen_values(true), + ) + .arg( + Arg::with_name(options::QUIET_NAME) + .short("q") + .long("quiet") + .visible_alias("silent") + .help("never print headers giving file names") + .overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]), + ) + .arg( + Arg::with_name(options::VERBOSE_NAME) + .short("v") + .long("verbose") + .help("always print headers giving file names") + .overrides_with_all(&[options::QUIET_NAME, options::VERBOSE_NAME]), + ) + .arg( + Arg::with_name(options::ZERO_NAME) + .short("z") + .long("zero-terminated") + .help("line delimiter is NUL, not newline") + .overrides_with(options::ZERO_NAME), + ) + .arg(Arg::with_name(options::FILES_NAME).multiple(true)) +} diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index aceecd941f4..e376a08af34 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -5,7 +5,6 @@ // spell-checker:ignore (vars) zlines -use clap::{crate_version, App, Arg}; use std::convert::TryFrom; use std::ffi::OsString; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; @@ -15,24 +14,7 @@ const EXIT_FAILURE: i32 = 1; const EXIT_SUCCESS: i32 = 0; const BUF_SIZE: usize = 65536; -const ABOUT: &str = "\ - Print the first 10 lines of each FILE to standard output.\n\ - With more than one FILE, precede each with a header giving the file name.\n\ - \n\ - With no FILE, or when FILE is -, read standard input.\n\ - \n\ - Mandatory arguments to long flags are mandatory for short flags too.\ - "; -const USAGE: &str = "head [FLAG]... [FILE]..."; - -mod options { - pub const BYTES_NAME: &str = "BYTES"; - pub const LINES_NAME: &str = "LINES"; - pub const QUIET_NAME: &str = "QUIET"; - pub const VERBOSE_NAME: &str = "VERBOSE"; - pub const ZERO_NAME: &str = "ZERO"; - pub const FILES_NAME: &str = "FILE"; -} +pub mod app; mod lines; mod parse; mod split; @@ -40,67 +22,8 @@ mod take; use lines::zlines; use take::take_all_but; -fn app<'a>() -> App<'a, 'a> { - App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(USAGE) - .arg( - Arg::with_name(options::BYTES_NAME) - .short("c") - .long("bytes") - .value_name("[-]NUM") - .takes_value(true) - .help( - "\ - print the first NUM bytes of each file;\n\ - with the leading '-', print all but the last\n\ - NUM bytes of each file\ - ", - ) - .overrides_with_all(&[options::BYTES_NAME, options::LINES_NAME]) - .allow_hyphen_values(true), - ) - .arg( - Arg::with_name(options::LINES_NAME) - .short("n") - .long("lines") - .value_name("[-]NUM") - .takes_value(true) - .help( - "\ - print the first NUM lines instead of the first 10;\n\ - with the leading '-', print all but the last\n\ - NUM lines of each file\ - ", - ) - .overrides_with_all(&[options::LINES_NAME, options::BYTES_NAME]) - .allow_hyphen_values(true), - ) - .arg( - Arg::with_name(options::QUIET_NAME) - .short("q") - .long("quiet") - .visible_alias("silent") - .help("never print headers giving file names") - .overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]), - ) - .arg( - Arg::with_name(options::VERBOSE_NAME) - .short("v") - .long("verbose") - .help("always print headers giving file names") - .overrides_with_all(&[options::QUIET_NAME, options::VERBOSE_NAME]), - ) - .arg( - Arg::with_name(options::ZERO_NAME) - .short("z") - .long("zero-terminated") - .help("line delimiter is NUL, not newline") - .overrides_with(options::ZERO_NAME), - ) - .arg(Arg::with_name(options::FILES_NAME).multiple(true)) -} +use crate::app::{get_app, options}; + #[derive(PartialEq, Debug, Clone, Copy)] enum Modes { Lines(usize), @@ -167,7 +90,7 @@ impl HeadOptions { ///Construct options from matches pub fn get_from(args: impl uucore::Args) -> Result { - let matches = app().get_matches_from(arg_iterate(args)?); + let matches = get_app(executable!()).get_matches_from(arg_iterate(args)?); let mut options = HeadOptions::new(); diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index ab69541040d..e67f5f3632f 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/hostid.rs" [dependencies] +clap = "2.33" libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/hostid/src/app.rs b/src/uu/hostid/src/app.rs new file mode 100644 index 00000000000..02b976ced76 --- /dev/null +++ b/src/uu/hostid/src/app.rs @@ -0,0 +1,5 @@ +use clap::{crate_version, App}; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name).version(crate_version!()) +} diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index 5518665210a..29877b5f56e 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -11,11 +11,10 @@ extern crate uucore; use libc::c_long; -use uucore::InvalidEncodingHandling; -static SYNTAX: &str = "[options]"; -static SUMMARY: &str = ""; -static LONG_HELP: &str = ""; +use crate::app::get_app; + +pub mod app; // currently rust libc interface doesn't include gethostid extern "C" { @@ -23,10 +22,7 @@ extern "C" { } pub fn uumain(args: impl uucore::Args) -> i32 { - app!(SYNTAX, SUMMARY, LONG_HELP).parse( - args.collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(), - ); + get_app(executable!()).get_matches_from(args); hostid(); 0 } diff --git a/src/uu/hostname/src/app.rs b/src/uu/hostname/src/app.rs new file mode 100644 index 00000000000..136616359e6 --- /dev/null +++ b/src/uu/hostname/src/app.rs @@ -0,0 +1,39 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Display or set the system's host name."; + +pub const OPT_DOMAIN: &str = "domain"; +pub const OPT_IP_ADDRESS: &str = "ip-address"; +pub const OPT_FQDN: &str = "fqdn"; +pub const OPT_SHORT: &str = "short"; +pub const OPT_HOST: &str = "host"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_DOMAIN) + .short("d") + .long("domain") + .help("Display the name of the DNS domain if possible"), + ) + .arg( + Arg::with_name(OPT_IP_ADDRESS) + .short("i") + .long("ip-address") + .help("Display the network address(es) of the host"), + ) + // TODO: support --long + .arg( + Arg::with_name(OPT_FQDN) + .short("f") + .long("fqdn") + .help("Display the FQDN (Fully Qualified Domain Name) (default)"), + ) + .arg(Arg::with_name(OPT_SHORT).short("s").long("short").help( + "Display the short hostname (the portion before the first dot) if \ + possible", + )) + .arg(Arg::with_name(OPT_HOST)) +} diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index ff312fb58ab..344de22593f 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -10,23 +10,18 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::ArgMatches; use std::collections::hash_set::HashSet; use std::net::ToSocketAddrs; -use std::str; #[cfg(windows)] use winapi::shared::minwindef::MAKEWORD; #[cfg(windows)] use winapi::um::winsock2::{WSACleanup, WSAStartup}; -static ABOUT: &str = "Display or set the system's host name."; +use crate::app::*; -static OPT_DOMAIN: &str = "domain"; -static OPT_IP_ADDRESS: &str = "ip-address"; -static OPT_FQDN: &str = "fqdn"; -static OPT_SHORT: &str = "short"; -static OPT_HOST: &str = "host"; +pub mod app; pub fn uumain(args: impl uucore::Args) -> i32 { #![allow(clippy::let_and_return)] @@ -52,34 +47,8 @@ fn get_usage() -> String { } fn execute(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_DOMAIN) - .short("d") - .long("domain") - .help("Display the name of the DNS domain if possible"), - ) - .arg( - Arg::with_name(OPT_IP_ADDRESS) - .short("i") - .long("ip-address") - .help("Display the network address(es) of the host"), - ) - // TODO: support --long - .arg( - Arg::with_name(OPT_FQDN) - .short("f") - .long("fqdn") - .help("Display the FQDN (Fully Qualified Domain Name) (default)"), - ) - .arg(Arg::with_name(OPT_SHORT).short("s").long("short").help( - "Display the short hostname (the portion before the first dot) if \ - possible", - )) - .arg(Arg::with_name(OPT_HOST)) .get_matches_from(args); match matches.value_of(OPT_HOST) { diff --git a/src/uu/id/src/app.rs b/src/uu/id/src/app.rs new file mode 100644 index 00000000000..2a64b70db78 --- /dev/null +++ b/src/uu/id/src/app.rs @@ -0,0 +1,67 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Display user and group information for the specified USER,\n or (when USER omitted) for the current user."; + +pub const OPT_AUDIT: &str = "audit"; +pub const OPT_EFFECTIVE_USER: &str = "effective-user"; +pub const OPT_GROUP: &str = "group"; +pub const OPT_GROUPS: &str = "groups"; +pub const OPT_HUMAN_READABLE: &str = "human-readable"; +pub const OPT_NAME: &str = "name"; +pub const OPT_PASSWORD: &str = "password"; +pub const OPT_REAL_ID: &str = "real"; + +pub const ARG_USERS: &str = "users"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_AUDIT) + .short("A") + .help("Display the process audit (not available on Linux)"), + ) + .arg( + Arg::with_name(OPT_EFFECTIVE_USER) + .short("u") + .long("user") + .help("Display the effective user ID as a number"), + ) + .arg( + Arg::with_name(OPT_GROUP) + .short("g") + .long(OPT_GROUP) + .help("Display the effective group ID as a number"), + ) + .arg( + Arg::with_name(OPT_GROUPS) + .short("G") + .long(OPT_GROUPS) + .help("Display the different group IDs"), + ) + .arg( + Arg::with_name(OPT_HUMAN_READABLE) + .short("p") + .help("Make the output human-readable"), + ) + .arg( + Arg::with_name(OPT_NAME) + .short("n") + .help("Display the name of the user or group ID for the -G, -g and -u options"), + ) + .arg( + Arg::with_name(OPT_PASSWORD) + .short("P") + .help("Display the id as a password file entry"), + ) + .arg( + Arg::with_name(OPT_REAL_ID) + .short("r") + .long(OPT_REAL_ID) + .help( + "Display the real ID for the -G, -g and -u options instead of the effective ID.", + ), + ) + .arg(Arg::with_name(ARG_USERS).multiple(true).takes_value(true)) +} diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 4f8f92fe45d..a12f9c16a9d 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -18,13 +18,16 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::ffi::CStr; use uucore::entries::{self, Group, Locate, Passwd}; pub use uucore::libc; use uucore::libc::{getlogin, uid_t}; use uucore::process::{getegid, geteuid, getgid, getuid}; +use crate::app::*; + +pub mod app; + macro_rules! cstr2cow { ($v:expr) => { unsafe { CStr::from_ptr($v).to_string_lossy() } @@ -70,19 +73,6 @@ mod audit { } } -static ABOUT: &str = "Display user and group information for the specified USER,\n or (when USER omitted) for the current user."; - -static OPT_AUDIT: &str = "audit"; -static OPT_EFFECTIVE_USER: &str = "effective-user"; -static OPT_GROUP: &str = "group"; -static OPT_GROUPS: &str = "groups"; -static OPT_HUMAN_READABLE: &str = "human-readable"; -static OPT_NAME: &str = "name"; -static OPT_PASSWORD: &str = "password"; -static OPT_REAL_ID: &str = "real"; - -static ARG_USERS: &str = "users"; - fn get_usage() -> String { format!("{0} [OPTION]... [USER]", executable!()) } @@ -90,57 +80,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_AUDIT) - .short("A") - .help("Display the process audit (not available on Linux)"), - ) - .arg( - Arg::with_name(OPT_EFFECTIVE_USER) - .short("u") - .long("user") - .help("Display the effective user ID as a number"), - ) - .arg( - Arg::with_name(OPT_GROUP) - .short("g") - .long(OPT_GROUP) - .help("Display the effective group ID as a number"), - ) - .arg( - Arg::with_name(OPT_GROUPS) - .short("G") - .long(OPT_GROUPS) - .help("Display the different group IDs"), - ) - .arg( - Arg::with_name(OPT_HUMAN_READABLE) - .short("p") - .help("Make the output human-readable"), - ) - .arg( - Arg::with_name(OPT_NAME) - .short("n") - .help("Display the name of the user or group ID for the -G, -g and -u options"), - ) - .arg( - Arg::with_name(OPT_PASSWORD) - .short("P") - .help("Display the id as a password file entry"), - ) - .arg( - Arg::with_name(OPT_REAL_ID) - .short("r") - .long(OPT_REAL_ID) - .help( - "Display the real ID for the -G, -g and -u options instead of the effective ID.", - ), - ) - .arg(Arg::with_name(ARG_USERS).multiple(true).takes_value(true)) .get_matches_from(args); let users: Vec = matches diff --git a/src/uu/install/src/app.rs b/src/uu/install/src/app.rs new file mode 100644 index 00000000000..92be355f74b --- /dev/null +++ b/src/uu/install/src/app.rs @@ -0,0 +1,157 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing + DIRECTORY, while setting permission modes and owner/group"; + +pub const OPT_COMPARE: &str = "compare"; +pub const OPT_BACKUP: &str = "backup"; +pub const OPT_BACKUP_2: &str = "backup2"; +pub const OPT_DIRECTORY: &str = "directory"; +pub const OPT_IGNORED: &str = "ignored"; +pub const OPT_CREATE_LEADING: &str = "create-leading"; +pub const OPT_GROUP: &str = "group"; +pub const OPT_MODE: &str = "mode"; +pub const OPT_OWNER: &str = "owner"; +pub const OPT_PRESERVE_TIMESTAMPS: &str = "preserve-timestamps"; +pub const OPT_STRIP: &str = "strip"; +pub const OPT_STRIP_PROGRAM: &str = "strip-program"; +pub const OPT_SUFFIX: &str = "suffix"; +pub const OPT_TARGET_DIRECTORY: &str = "target-directory"; +pub const OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; +pub const OPT_VERBOSE: &str = "verbose"; +pub const OPT_PRESERVE_CONTEXT: &str = "preserve-context"; +pub const OPT_CONTEXT: &str = "context"; + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_BACKUP) + .long(OPT_BACKUP) + .help("(unimplemented) make a backup of each existing destination file") + .value_name("CONTROL") + ) + .arg( + // TODO implement flag + Arg::with_name(OPT_BACKUP_2) + .short("b") + .help("(unimplemented) like --backup but does not accept an argument") + ) + .arg( + Arg::with_name(OPT_IGNORED) + .short("c") + .help("ignored") + ) + .arg( + Arg::with_name(OPT_COMPARE) + .short("C") + .long(OPT_COMPARE) + .help("compare each pair of source and destination files, and in some cases, do not modify the destination at all") + ) + .arg( + Arg::with_name(OPT_DIRECTORY) + .short("d") + .long(OPT_DIRECTORY) + .help("treat all arguments as directory names. create all components of the specified directories") + ) + + .arg( + // TODO implement flag + Arg::with_name(OPT_CREATE_LEADING) + .short("D") + .help("create all leading components of DEST except the last, then copy SOURCE to DEST") + ) + .arg( + Arg::with_name(OPT_GROUP) + .short("g") + .long(OPT_GROUP) + .help("set group ownership, instead of process's current group") + .value_name("GROUP") + .takes_value(true) + ) + .arg( + Arg::with_name(OPT_MODE) + .short("m") + .long(OPT_MODE) + .help("set permission mode (as in chmod), instead of rwxr-xr-x") + .value_name("MODE") + .takes_value(true) + ) + .arg( + Arg::with_name(OPT_OWNER) + .short("o") + .long(OPT_OWNER) + .help("set ownership (super-user only)") + .value_name("OWNER") + .takes_value(true) + ) + .arg( + Arg::with_name(OPT_PRESERVE_TIMESTAMPS) + .short("p") + .long(OPT_PRESERVE_TIMESTAMPS) + .help("apply access/modification times of SOURCE files to corresponding destination files") + ) + .arg( + Arg::with_name(OPT_STRIP) + .short("s") + .long(OPT_STRIP) + .help("strip symbol tables (no action Windows)") + ) + .arg( + Arg::with_name(OPT_STRIP_PROGRAM) + .long(OPT_STRIP_PROGRAM) + .help("program used to strip binaries (no action Windows)") + .value_name("PROGRAM") + ) + .arg( + // TODO implement flag + Arg::with_name(OPT_SUFFIX) + .short("S") + .long(OPT_SUFFIX) + .help("(unimplemented) override the usual backup suffix") + .value_name("SUFFIX") + .takes_value(true) + .min_values(1) + ) + .arg( + // TODO implement flag + Arg::with_name(OPT_TARGET_DIRECTORY) + .short("t") + .long(OPT_TARGET_DIRECTORY) + .help("(unimplemented) move all SOURCE arguments into DIRECTORY") + .value_name("DIRECTORY") + ) + .arg( + // TODO implement flag + Arg::with_name(OPT_NO_TARGET_DIRECTORY) + .short("T") + .long(OPT_NO_TARGET_DIRECTORY) + .help("(unimplemented) treat DEST as a normal file") + + ) + .arg( + Arg::with_name(OPT_VERBOSE) + .short("v") + .long(OPT_VERBOSE) + .help("explain what is being done") + ) + .arg( + // TODO implement flag + Arg::with_name(OPT_PRESERVE_CONTEXT) + .short("P") + .long(OPT_PRESERVE_CONTEXT) + .help("(unimplemented) preserve security context") + ) + .arg( + // TODO implement flag + Arg::with_name(OPT_CONTEXT) + .short("Z") + .long(OPT_CONTEXT) + .help("(unimplemented) set security context of files and directories") + .value_name("CONTEXT") + ) + .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1)) +} diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index ad5ea694c39..d177f7b4914 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -7,12 +7,13 @@ // spell-checker:ignore (ToDO) rwxr sourcepath targetpath +pub mod app; mod mode; #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::ArgMatches; use file_diff::diff; use filetime::{set_file_times, FileTime}; use uucore::entries::{grp2gid, usr2uid}; @@ -26,6 +27,8 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::result::Result; +use crate::app::*; + const DEFAULT_MODE: u32 = 0o755; const DEFAULT_STRIP_PROGRAM: &str = "strip"; @@ -62,30 +65,6 @@ impl Behavior { } } -static ABOUT: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing - DIRECTORY, while setting permission modes and owner/group"; - -static OPT_COMPARE: &str = "compare"; -static OPT_BACKUP: &str = "backup"; -static OPT_BACKUP_2: &str = "backup2"; -static OPT_DIRECTORY: &str = "directory"; -static OPT_IGNORED: &str = "ignored"; -static OPT_CREATE_LEADING: &str = "create-leading"; -static OPT_GROUP: &str = "group"; -static OPT_MODE: &str = "mode"; -static OPT_OWNER: &str = "owner"; -static OPT_PRESERVE_TIMESTAMPS: &str = "preserve-timestamps"; -static OPT_STRIP: &str = "strip"; -static OPT_STRIP_PROGRAM: &str = "strip-program"; -static OPT_SUFFIX: &str = "suffix"; -static OPT_TARGET_DIRECTORY: &str = "target-directory"; -static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; -static OPT_VERBOSE: &str = "verbose"; -static OPT_PRESERVE_CONTEXT: &str = "preserve-context"; -static OPT_CONTEXT: &str = "context"; - -static ARG_FILES: &str = "files"; - fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } @@ -97,136 +76,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_BACKUP) - .long(OPT_BACKUP) - .help("(unimplemented) make a backup of each existing destination file") - .value_name("CONTROL") - ) - .arg( - // TODO implement flag - Arg::with_name(OPT_BACKUP_2) - .short("b") - .help("(unimplemented) like --backup but does not accept an argument") - ) - .arg( - Arg::with_name(OPT_IGNORED) - .short("c") - .help("ignored") - ) - .arg( - Arg::with_name(OPT_COMPARE) - .short("C") - .long(OPT_COMPARE) - .help("compare each pair of source and destination files, and in some cases, do not modify the destination at all") - ) - .arg( - Arg::with_name(OPT_DIRECTORY) - .short("d") - .long(OPT_DIRECTORY) - .help("treat all arguments as directory names. create all components of the specified directories") - ) - - .arg( - // TODO implement flag - Arg::with_name(OPT_CREATE_LEADING) - .short("D") - .help("create all leading components of DEST except the last, then copy SOURCE to DEST") - ) - .arg( - Arg::with_name(OPT_GROUP) - .short("g") - .long(OPT_GROUP) - .help("set group ownership, instead of process's current group") - .value_name("GROUP") - .takes_value(true) - ) - .arg( - Arg::with_name(OPT_MODE) - .short("m") - .long(OPT_MODE) - .help("set permission mode (as in chmod), instead of rwxr-xr-x") - .value_name("MODE") - .takes_value(true) - ) - .arg( - Arg::with_name(OPT_OWNER) - .short("o") - .long(OPT_OWNER) - .help("set ownership (super-user only)") - .value_name("OWNER") - .takes_value(true) - ) - .arg( - Arg::with_name(OPT_PRESERVE_TIMESTAMPS) - .short("p") - .long(OPT_PRESERVE_TIMESTAMPS) - .help("apply access/modification times of SOURCE files to corresponding destination files") - ) - .arg( - Arg::with_name(OPT_STRIP) - .short("s") - .long(OPT_STRIP) - .help("strip symbol tables (no action Windows)") - ) - .arg( - Arg::with_name(OPT_STRIP_PROGRAM) - .long(OPT_STRIP_PROGRAM) - .help("program used to strip binaries (no action Windows)") - .value_name("PROGRAM") - ) - .arg( - // TODO implement flag - Arg::with_name(OPT_SUFFIX) - .short("S") - .long(OPT_SUFFIX) - .help("(unimplemented) override the usual backup suffix") - .value_name("SUFFIX") - .takes_value(true) - .min_values(1) - ) - .arg( - // TODO implement flag - Arg::with_name(OPT_TARGET_DIRECTORY) - .short("t") - .long(OPT_TARGET_DIRECTORY) - .help("(unimplemented) move all SOURCE arguments into DIRECTORY") - .value_name("DIRECTORY") - ) - .arg( - // TODO implement flag - Arg::with_name(OPT_NO_TARGET_DIRECTORY) - .short("T") - .long(OPT_NO_TARGET_DIRECTORY) - .help("(unimplemented) treat DEST as a normal file") - - ) - .arg( - Arg::with_name(OPT_VERBOSE) - .short("v") - .long(OPT_VERBOSE) - .help("explain what is being done") - ) - .arg( - // TODO implement flag - Arg::with_name(OPT_PRESERVE_CONTEXT) - .short("P") - .long(OPT_PRESERVE_CONTEXT) - .help("(unimplemented) preserve security context") - ) - .arg( - // TODO implement flag - Arg::with_name(OPT_CONTEXT) - .short("Z") - .long(OPT_CONTEXT) - .help("(unimplemented) set security context of files and directories") - .value_name("CONTEXT") - ) - .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1)) .get_matches_from(args); let paths: Vec = matches diff --git a/src/uu/join/src/app.rs b/src/uu/join/src/app.rs new file mode 100644 index 00000000000..f73698fdd11 --- /dev/null +++ b/src/uu/join/src/app.rs @@ -0,0 +1,106 @@ +use clap::{crate_version, App, Arg}; + +// spell-checker:ignore (ToDO) FILENUM pairable unpairable nocheck + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about( + "For each pair of input lines with identical join fields, write a line to +standard output. The default join field is the first, delimited by blanks. + +When FILE1 or FILE2 (not both) is -, read standard input.", + ) + .help_message("display this help and exit") + .version_message("display version and exit") + .arg( + Arg::with_name("a") + .short("a") + .takes_value(true) + .possible_values(&["1", "2"]) + .value_name("FILENUM") + .help( + "also print unpairable lines from file FILENUM, where +FILENUM is 1 or 2, corresponding to FILE1 or FILE2", + ), + ) + .arg( + Arg::with_name("v") + .short("v") + .value_name("FILENUM") + .help("like -a FILENUM, but suppress joined output lines"), + ) + .arg( + Arg::with_name("e") + .short("e") + .takes_value(true) + .value_name("EMPTY") + .help("replace missing input fields with EMPTY"), + ) + .arg( + Arg::with_name("i") + .short("i") + .long("ignore-case") + .help("ignore differences in case when comparing fields"), + ) + .arg( + Arg::with_name("j") + .short("j") + .takes_value(true) + .value_name("FIELD") + .help("equivalent to '-1 FIELD -2 FIELD'"), + ) + .arg( + Arg::with_name("o") + .short("o") + .takes_value(true) + .value_name("FORMAT") + .help("obey FORMAT while constructing output line"), + ) + .arg( + Arg::with_name("t") + .short("t") + .takes_value(true) + .value_name("CHAR") + .help("use CHAR as input and output field separator"), + ) + .arg( + Arg::with_name("1") + .short("1") + .takes_value(true) + .value_name("FIELD") + .help("join on this FIELD of file 1"), + ) + .arg( + Arg::with_name("2") + .short("2") + .takes_value(true) + .value_name("FIELD") + .help("join on this FIELD of file 2"), + ) + .arg(Arg::with_name("check-order").long("check-order").help( + "check that the input is correctly sorted, \ + even if all input lines are pairable", + )) + .arg( + Arg::with_name("nocheck-order") + .long("nocheck-order") + .help("do not check that the input is correctly sorted"), + ) + .arg(Arg::with_name("header").long("header").help( + "treat the first line in each file as field headers, \ + print them without trying to pair them", + )) + .arg( + Arg::with_name("file1") + .required(true) + .value_name("FILE1") + .hidden(true), + ) + .arg( + Arg::with_name("file2") + .required(true) + .value_name("FILE2") + .hidden(true), + ) +} diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 4cdfe2141e6..946a0da432c 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -10,12 +10,13 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::cmp::{min, Ordering}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Lines, Stdin}; -static NAME: &str = "join"; +use crate::app::get_app; + +pub mod app; #[derive(Copy, Clone, PartialEq)] enum FileNum { @@ -442,107 +443,7 @@ impl<'a> State<'a> { } pub fn uumain(args: impl uucore::Args) -> i32 { - let matches = App::new(NAME) - .version(crate_version!()) - .about( - "For each pair of input lines with identical join fields, write a line to -standard output. The default join field is the first, delimited by blanks. - -When FILE1 or FILE2 (not both) is -, read standard input.", - ) - .help_message("display this help and exit") - .version_message("display version and exit") - .arg( - Arg::with_name("a") - .short("a") - .takes_value(true) - .possible_values(&["1", "2"]) - .value_name("FILENUM") - .help( - "also print unpairable lines from file FILENUM, where -FILENUM is 1 or 2, corresponding to FILE1 or FILE2", - ), - ) - .arg( - Arg::with_name("v") - .short("v") - .value_name("FILENUM") - .help("like -a FILENUM, but suppress joined output lines"), - ) - .arg( - Arg::with_name("e") - .short("e") - .takes_value(true) - .value_name("EMPTY") - .help("replace missing input fields with EMPTY"), - ) - .arg( - Arg::with_name("i") - .short("i") - .long("ignore-case") - .help("ignore differences in case when comparing fields"), - ) - .arg( - Arg::with_name("j") - .short("j") - .takes_value(true) - .value_name("FIELD") - .help("equivalent to '-1 FIELD -2 FIELD'"), - ) - .arg( - Arg::with_name("o") - .short("o") - .takes_value(true) - .value_name("FORMAT") - .help("obey FORMAT while constructing output line"), - ) - .arg( - Arg::with_name("t") - .short("t") - .takes_value(true) - .value_name("CHAR") - .help("use CHAR as input and output field separator"), - ) - .arg( - Arg::with_name("1") - .short("1") - .takes_value(true) - .value_name("FIELD") - .help("join on this FIELD of file 1"), - ) - .arg( - Arg::with_name("2") - .short("2") - .takes_value(true) - .value_name("FIELD") - .help("join on this FIELD of file 2"), - ) - .arg(Arg::with_name("check-order").long("check-order").help( - "check that the input is correctly sorted, \ - even if all input lines are pairable", - )) - .arg( - Arg::with_name("nocheck-order") - .long("nocheck-order") - .help("do not check that the input is correctly sorted"), - ) - .arg(Arg::with_name("header").long("header").help( - "treat the first line in each file as field headers, \ - print them without trying to pair them", - )) - .arg( - Arg::with_name("file1") - .required(true) - .value_name("FILE1") - .hidden(true), - ) - .arg( - Arg::with_name("file2") - .required(true) - .value_name("FILE2") - .hidden(true), - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let keys = parse_field_number_option(matches.value_of("j")); let key1 = parse_field_number_option(matches.value_of("1")); diff --git a/src/uu/kill/src/app.rs b/src/uu/kill/src/app.rs new file mode 100644 index 00000000000..35eaa8b227e --- /dev/null +++ b/src/uu/kill/src/app.rs @@ -0,0 +1,46 @@ +// spell-checker:ignore (ToDO) pids + +use clap::{crate_version, App, Arg}; + +pub mod options { + pub const PIDS_OR_SIGNALS: &str = "pids_or_signals"; + pub const LIST: &str = "list"; + pub const TABLE: &str = "table"; + pub const TABLE_OLD: &str = "table_old"; + pub const SIGNAL: &str = "signal"; +} + +pub const ABOUT: &str = "Send signal to processes or list information about signals."; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::LIST) + .short("l") + .long(options::LIST) + .help("Lists signals") + .conflicts_with(options::TABLE) + .conflicts_with(options::TABLE_OLD), + ) + .arg( + Arg::with_name(options::TABLE) + .short("t") + .long(options::TABLE) + .help("Lists table of signals"), + ) + .arg(Arg::with_name(options::TABLE_OLD).short("L").hidden(true)) + .arg( + Arg::with_name(options::SIGNAL) + .short("s") + .long(options::SIGNAL) + .help("Sends given signal") + .takes_value(true), + ) + .arg( + Arg::with_name(options::PIDS_OR_SIGNALS) + .hidden(true) + .multiple(true), + ) +} diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index a49acaa05e3..49d6dbc1b1d 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -10,25 +10,19 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use libc::{c_int, pid_t}; use std::io::Error; use uucore::signals::ALL_SIGNALS; use uucore::InvalidEncodingHandling; -static ABOUT: &str = "Send signal to processes or list information about signals."; +use crate::app::get_app; +use crate::app::options; + +pub mod app; 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, @@ -43,37 +37,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let (args, obs_signal) = handle_obsolete(args); let usage = format!("{} [OPTIONS]... PID...", executable!()); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .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) { diff --git a/src/uu/link/src/app.rs b/src/uu/link/src/app.rs new file mode 100644 index 00000000000..ed8be078c1f --- /dev/null +++ b/src/uu/link/src/app.rs @@ -0,0 +1,21 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1."; + +pub mod options { + pub const FILES: &str = "FILES"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::FILES) + .hidden(true) + .required(true) + .min_values(2) + .max_values(2) + .takes_value(true), + ) +} diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 08401ebaf48..67fa5428d32 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -8,16 +8,13 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::fs::hard_link; use std::io::Error; use std::path::Path; -static ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1."; +use crate::app::{get_app, options}; -pub mod options { - pub static FILES: &str = "FILES"; -} +pub mod app; fn get_usage() -> String { format!("{0} FILE1 FILE2", executable!()) @@ -32,18 +29,8 @@ pub fn normalize_error_message(e: Error) -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .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 files: Vec<_> = matches diff --git a/src/uu/ln/src/app.rs b/src/uu/ln/src/app.rs new file mode 100644 index 00000000000..df773c46ce9 --- /dev/null +++ b/src/uu/ln/src/app.rs @@ -0,0 +1,132 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "change file owner and group"; + +const LONG_USAGE: &str = "In the 1st form, create a link to TARGET with the name LINK_NAME. +In the 2nd form, create a link to TARGET in the current directory. +In the 3rd and 4th forms, create links to each TARGET in DIRECTORY. +Create hard links by default, symbolic links with --symbolic. +By default, each destination (name of new link) should not already exist. +When creating hard links, each TARGET must exist. Symbolic links +can hold arbitrary text; if later resolved, a relative link is +interpreted in relation to its parent directory. +"; + +pub mod options { + pub const B: &str = "b"; + pub const BACKUP: &str = "backup"; + pub const FORCE: &str = "force"; + pub const INTERACTIVE: &str = "interactive"; + pub const NO_DEREFERENCE: &str = "no-dereference"; + pub const SYMBOLIC: &str = "symbolic"; + pub const SUFFIX: &str = "suffix"; + pub const TARGET_DIRECTORY: &str = "target-directory"; + pub const NO_TARGET_DIRECTORY: &str = "no-target-directory"; + pub const RELATIVE: &str = "relative"; + pub const VERBOSE: &str = "verbose"; +} + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .after_help(LONG_USAGE) + .arg(Arg::with_name(options::B).short(options::B).help( + "make a backup of each file that would otherwise be overwritten or \ + removed", + )) + .arg( + Arg::with_name(options::BACKUP) + .long(options::BACKUP) + .help( + "make a backup of each file that would otherwise be overwritten \ + or removed", + ) + .takes_value(true) + .possible_values(&[ + "simple", "never", "numbered", "t", "existing", "nil", "none", "off", + ]) + .value_name("METHOD"), + ) + // TODO: opts.arg( + // Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \ + // to make hard links to directories"); + .arg( + Arg::with_name(options::FORCE) + .short("f") + .long(options::FORCE) + .help("remove existing destination files"), + ) + .arg( + Arg::with_name(options::INTERACTIVE) + .short("i") + .long(options::INTERACTIVE) + .help("prompt whether to remove existing destination files"), + ) + .arg( + Arg::with_name(options::NO_DEREFERENCE) + .short("n") + .long(options::NO_DEREFERENCE) + .help( + "treat LINK_NAME as a normal file if it is a \ + symbolic link to a directory", + ), + ) + // TODO: opts.arg( + // Arg::with_name(("L", "logical", "dereference TARGETs that are symbolic links"); + // + // TODO: opts.arg( + // Arg::with_name(("P", "physical", "make hard links directly to symbolic links"); + .arg( + Arg::with_name(options::SYMBOLIC) + .short("s") + .long("symbolic") + .help("make symbolic links instead of hard links") + // override added for https://github.com/uutils/coreutils/issues/2359 + .overrides_with(options::SYMBOLIC), + ) + .arg( + Arg::with_name(options::SUFFIX) + .short("S") + .long(options::SUFFIX) + .help("override the usual backup suffix") + .value_name("SUFFIX") + .takes_value(true), + ) + .arg( + Arg::with_name(options::TARGET_DIRECTORY) + .short("t") + .long(options::TARGET_DIRECTORY) + .help("specify the DIRECTORY in which to create the links") + .value_name("DIRECTORY") + .conflicts_with(options::NO_TARGET_DIRECTORY), + ) + .arg( + Arg::with_name(options::NO_TARGET_DIRECTORY) + .short("T") + .long(options::NO_TARGET_DIRECTORY) + .help("treat LINK_NAME as a normal file always"), + ) + .arg( + Arg::with_name(options::RELATIVE) + .short("r") + .long(options::RELATIVE) + .help("create symbolic links relative to link location") + .requires(options::SYMBOLIC), + ) + .arg( + Arg::with_name(options::VERBOSE) + .short("v") + .long(options::VERBOSE) + .help("print name of each linked file"), + ) + .arg( + Arg::with_name(ARG_FILES) + .multiple(true) + .takes_value(true) + .required(true) + .min_values(1), + ) +} diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index ce1dd15b0cd..327abc4e4c3 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -10,8 +10,6 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; - use std::borrow::Cow; use std::ffi::OsStr; use std::fs; @@ -24,6 +22,10 @@ use std::os::windows::fs::{symlink_dir, symlink_file}; use std::path::{Path, PathBuf}; use uucore::fs::{canonicalize, CanonicalizeMode}; +use crate::app::{get_app, options, ARG_FILES}; + +pub mod app; + pub struct Settings { overwrite: OverwriteMode, backup: BackupMode, @@ -61,143 +63,11 @@ fn get_usage() -> String { ) } -fn get_long_usage() -> String { - String::from( - " In the 1st form, create a link to TARGET with the name LINK_NAME. - In the 2nd form, create a link to TARGET in the current directory. - In the 3rd and 4th forms, create links to each TARGET in DIRECTORY. - Create hard links by default, symbolic links with --symbolic. - By default, each destination (name of new link) should not already exist. - When creating hard links, each TARGET must exist. Symbolic links - can hold arbitrary text; if later resolved, a relative link is - interpreted in relation to its parent directory. - ", - ) -} - -static ABOUT: &str = "change file owner and group"; - -mod options { - pub const B: &str = "b"; - pub const BACKUP: &str = "backup"; - pub const FORCE: &str = "force"; - pub const INTERACTIVE: &str = "interactive"; - pub const NO_DEREFERENCE: &str = "no-dereference"; - pub const SYMBOLIC: &str = "symbolic"; - pub const SUFFIX: &str = "suffix"; - pub const TARGET_DIRECTORY: &str = "target-directory"; - pub const NO_TARGET_DIRECTORY: &str = "no-target-directory"; - pub const RELATIVE: &str = "relative"; - pub const VERBOSE: &str = "verbose"; -} - -static ARG_FILES: &str = "files"; - pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let long_usage = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .after_help(&long_usage[..]) - .arg(Arg::with_name(options::B).short(options::B).help( - "make a backup of each file that would otherwise be overwritten or \ - removed", - )) - .arg( - Arg::with_name(options::BACKUP) - .long(options::BACKUP) - .help( - "make a backup of each file that would otherwise be overwritten \ - or removed", - ) - .takes_value(true) - .possible_values(&[ - "simple", "never", "numbered", "t", "existing", "nil", "none", "off", - ]) - .value_name("METHOD"), - ) - // TODO: opts.arg( - // Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \ - // to make hard links to directories"); - .arg( - Arg::with_name(options::FORCE) - .short("f") - .long(options::FORCE) - .help("remove existing destination files"), - ) - .arg( - Arg::with_name(options::INTERACTIVE) - .short("i") - .long(options::INTERACTIVE) - .help("prompt whether to remove existing destination files"), - ) - .arg( - Arg::with_name(options::NO_DEREFERENCE) - .short("n") - .long(options::NO_DEREFERENCE) - .help( - "treat LINK_NAME as a normal file if it is a \ - symbolic link to a directory", - ), - ) - // TODO: opts.arg( - // Arg::with_name(("L", "logical", "dereference TARGETs that are symbolic links"); - // - // TODO: opts.arg( - // Arg::with_name(("P", "physical", "make hard links directly to symbolic links"); - .arg( - Arg::with_name(options::SYMBOLIC) - .short("s") - .long("symbolic") - .help("make symbolic links instead of hard links") - // override added for https://github.com/uutils/coreutils/issues/2359 - .overrides_with(options::SYMBOLIC), - ) - .arg( - Arg::with_name(options::SUFFIX) - .short("S") - .long(options::SUFFIX) - .help("override the usual backup suffix") - .value_name("SUFFIX") - .takes_value(true), - ) - .arg( - Arg::with_name(options::TARGET_DIRECTORY) - .short("t") - .long(options::TARGET_DIRECTORY) - .help("specify the DIRECTORY in which to create the links") - .value_name("DIRECTORY") - .conflicts_with(options::NO_TARGET_DIRECTORY), - ) - .arg( - Arg::with_name(options::NO_TARGET_DIRECTORY) - .short("T") - .long(options::NO_TARGET_DIRECTORY) - .help("treat LINK_NAME as a normal file always"), - ) - .arg( - Arg::with_name(options::RELATIVE) - .short("r") - .long(options::RELATIVE) - .help("create symbolic links relative to link location") - .requires(options::SYMBOLIC), - ) - .arg( - Arg::with_name(options::VERBOSE) - .short("v") - .long(options::VERBOSE) - .help("print name of each linked file"), - ) - .arg( - Arg::with_name(ARG_FILES) - .multiple(true) - .takes_value(true) - .required(true) - .min_values(1), - ) .get_matches_from(args); /* the list of files */ diff --git a/src/uu/logname/src/app.rs b/src/uu/logname/src/app.rs new file mode 100644 index 00000000000..c8c39ffe99b --- /dev/null +++ b/src/uu/logname/src/app.rs @@ -0,0 +1,10 @@ +use clap::{crate_version, App}; + +const SUMMARY: &str = "Print user's login name"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .usage(app_name) + .version(crate_version!()) + .about(SUMMARY) +} diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index ba5880403b1..b5233c3b759 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -15,8 +15,9 @@ extern crate uucore; use std::ffi::CStr; use uucore::InvalidEncodingHandling; -use clap::{crate_version, App}; +use crate::app::get_app; +pub mod app; extern "C" { // POSIX requires using getlogin (or equivalent code) pub fn getlogin() -> *const libc::c_char; @@ -33,23 +34,12 @@ fn get_userlogin() -> Option { } } -static SUMMARY: &str = "Print user's login name"; - -fn get_usage() -> String { - String::from(executable!()) -} - pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = get_usage(); - let _ = App::new(executable!()) - .version(crate_version!()) - .about(SUMMARY) - .usage(&usage[..]) - .get_matches_from(args); + let _ = get_app(executable!()).get_matches_from(args); match get_userlogin() { Some(userlogin) => println!("{}", userlogin), diff --git a/src/uu/ls/src/app.rs b/src/uu/ls/src/app.rs new file mode 100644 index 00000000000..b95967865ff --- /dev/null +++ b/src/uu/ls/src/app.rs @@ -0,0 +1,616 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = " +By default, ls will list the files and contents of any directories on +the command line, expect that it will ignore files and directories +whose names start with '.' +"; +const AFTER_HELP: &str = "The TIME_STYLE argument can be full-iso, long-iso, iso. +Also the TIME_STYLE environment variable sets the default style to use."; + +pub mod options { + pub mod format { + pub const ONE_LINE: &str = "1"; + pub const LONG: &str = "long"; + pub const COLUMNS: &str = "C"; + pub const ACROSS: &str = "x"; + pub const COMMAS: &str = "m"; + pub const LONG_NO_OWNER: &str = "g"; + pub const LONG_NO_GROUP: &str = "o"; + pub const LONG_NUMERIC_UID_GID: &str = "numeric-uid-gid"; + } + pub mod files { + pub const ALL: &str = "all"; + pub const ALMOST_ALL: &str = "almost-all"; + } + pub mod sort { + pub const SIZE: &str = "S"; + pub const TIME: &str = "t"; + pub const NONE: &str = "U"; + pub const VERSION: &str = "v"; + pub const EXTENSION: &str = "X"; + } + pub mod time { + pub const ACCESS: &str = "u"; + pub const CHANGE: &str = "c"; + } + pub mod size { + pub const HUMAN_READABLE: &str = "human-readable"; + pub const SI: &str = "si"; + } + pub mod quoting { + pub const ESCAPE: &str = "escape"; + pub const LITERAL: &str = "literal"; + pub const C: &str = "quote-name"; + } + pub const QUOTING_STYLE: &str = "quoting-style"; + pub mod indicator_style { + pub const SLASH: &str = "p"; + pub const FILE_TYPE: &str = "file-type"; + pub const CLASSIFY: &str = "classify"; + } + pub mod dereference { + pub const ALL: &str = "dereference"; + pub const ARGS: &str = "dereference-command-line"; + pub const DIR_ARGS: &str = "dereference-command-line-symlink-to-dir"; + } + pub const HIDE_CONTROL_CHARS: &str = "hide-control-chars"; + pub const SHOW_CONTROL_CHARS: &str = "show-control-chars"; + pub const WIDTH: &str = "width"; + pub const AUTHOR: &str = "author"; + pub const NO_GROUP: &str = "no-group"; + pub const FORMAT: &str = "format"; + pub const SORT: &str = "sort"; + pub const TIME: &str = "time"; + pub const IGNORE_BACKUPS: &str = "ignore-backups"; + pub const DIRECTORY: &str = "directory"; + pub const INODE: &str = "inode"; + pub const REVERSE: &str = "reverse"; + pub const RECURSIVE: &str = "recursive"; + pub const COLOR: &str = "color"; + pub const PATHS: &str = "paths"; + pub const INDICATOR_STYLE: &str = "indicator-style"; + pub const TIME_STYLE: &str = "time-style"; + pub const FULL_TIME: &str = "full-time"; + pub const HIDE: &str = "hide"; + pub const IGNORE: &str = "ignore"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .after_help(AFTER_HELP) + + // Format arguments + .arg( + Arg::with_name(options::FORMAT) + .long(options::FORMAT) + .help("Set the display format.") + .takes_value(true) + .possible_values(&["long", "verbose", "single-column", "columns", "vertical", "across", "horizontal", "commas"]) + .hide_possible_values(true) + .require_equals(true) + .overrides_with_all(&[ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + ]), + ) + .arg( + Arg::with_name(options::format::COLUMNS) + .short(options::format::COLUMNS) + .help("Display the files in columns.") + .overrides_with_all(&[ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + ]), + ) + .arg( + Arg::with_name(options::format::LONG) + .short("l") + .long(options::format::LONG) + .help("Display detailed information.") + .overrides_with_all(&[ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + ]), + ) + .arg( + Arg::with_name(options::format::ACROSS) + .short(options::format::ACROSS) + .help("List entries in rows instead of in columns.") + .overrides_with_all(&[ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + ]), + ) + .arg( + Arg::with_name(options::format::COMMAS) + .short(options::format::COMMAS) + .help("List entries separated by commas.") + .overrides_with_all(&[ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + ]), + ) + // The next four arguments do not override with the other format + // options, see the comment in Config::from for the reason. + // Ideally, they would use Arg::override_with, with their own name + // but that doesn't seem to work in all cases. Example: + // ls -1g1 + // even though `ls -11` and `ls -1 -g -1` work. + .arg( + Arg::with_name(options::format::ONE_LINE) + .short(options::format::ONE_LINE) + .help("List one file per line.") + .multiple(true) + ) + .arg( + Arg::with_name(options::format::LONG_NO_GROUP) + .short(options::format::LONG_NO_GROUP) + .help("Long format without group information. Identical to --format=long with --no-group.") + .multiple(true) + ) + .arg( + Arg::with_name(options::format::LONG_NO_OWNER) + .short(options::format::LONG_NO_OWNER) + .help("Long format without owner information.") + .multiple(true) + ) + .arg( + Arg::with_name(options::format::LONG_NUMERIC_UID_GID) + .short("n") + .long(options::format::LONG_NUMERIC_UID_GID) + .help("-l with numeric UIDs and GIDs.") + .multiple(true) + ) + + // Quoting style + .arg( + Arg::with_name(options::QUOTING_STYLE) + .long(options::QUOTING_STYLE) + .takes_value(true) + .help("Set quoting style.") + .possible_values(&["literal", "shell", "shell-always", "shell-escape", "shell-escape-always", "c", "escape"]) + .overrides_with_all(&[ + options::QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]) + ) + .arg( + Arg::with_name(options::quoting::LITERAL) + .short("N") + .long(options::quoting::LITERAL) + .help("Use literal quoting style. Equivalent to `--quoting-style=literal`") + .overrides_with_all(&[ + options::QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]) + ) + .arg( + Arg::with_name(options::quoting::ESCAPE) + .short("b") + .long(options::quoting::ESCAPE) + .help("Use escape quoting style. Equivalent to `--quoting-style=escape`") + .overrides_with_all(&[ + options::QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]) + ) + .arg( + Arg::with_name(options::quoting::C) + .short("Q") + .long(options::quoting::C) + .help("Use C quoting style. Equivalent to `--quoting-style=c`") + .overrides_with_all(&[ + options::QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]) + ) + + // Control characters + .arg( + Arg::with_name(options::HIDE_CONTROL_CHARS) + .short("q") + .long(options::HIDE_CONTROL_CHARS) + .help("Replace control characters with '?' if they are not escaped.") + .overrides_with_all(&[ + options::HIDE_CONTROL_CHARS, + options::SHOW_CONTROL_CHARS, + ]) + ) + .arg( + Arg::with_name(options::SHOW_CONTROL_CHARS) + .long(options::SHOW_CONTROL_CHARS) + .help("Show control characters 'as is' if they are not escaped.") + .overrides_with_all(&[ + options::HIDE_CONTROL_CHARS, + options::SHOW_CONTROL_CHARS, + ]) + ) + + // Time arguments + .arg( + Arg::with_name(options::TIME) + .long(options::TIME) + .help("Show time in :\n\ + \taccess time (-u): atime, access, use;\n\ + \tchange time (-t): ctime, status.\n\ + \tbirth time: birth, creation;") + .value_name("field") + .takes_value(true) + .possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"]) + .hide_possible_values(true) + .require_equals(true) + .overrides_with_all(&[ + options::TIME, + options::time::ACCESS, + options::time::CHANGE, + ]) + ) + .arg( + Arg::with_name(options::time::CHANGE) + .short(options::time::CHANGE) + .help("If the long listing format (e.g., -l, -o) is being used, print the status \ + change time (the ‘ctime’ in the inode) instead of the modification time. When \ + explicitly sorting by time (--sort=time or -t) or when not using a long listing \ + format, sort according to the status change time.") + .overrides_with_all(&[ + options::TIME, + options::time::ACCESS, + options::time::CHANGE, + ]) + ) + .arg( + Arg::with_name(options::time::ACCESS) + .short(options::time::ACCESS) + .help("If the long listing format (e.g., -l, -o) is being used, print the status \ + access time instead of the modification time. When explicitly sorting by time \ + (--sort=time or -t) or when not using a long listing format, sort according to the \ + access time.") + .overrides_with_all(&[ + options::TIME, + options::time::ACCESS, + options::time::CHANGE, + ]) + ) + + // Hide and ignore + .arg( + Arg::with_name(options::HIDE) + .long(options::HIDE) + .takes_value(true) + .multiple(true) + .value_name("PATTERN") + .help("do not list implied entries matching shell PATTERN (overridden by -a or -A)") + ) + .arg( + Arg::with_name(options::IGNORE) + .short("I") + .long(options::IGNORE) + .takes_value(true) + .multiple(true) + .value_name("PATTERN") + .help("do not list implied entries matching shell PATTERN") + ) + .arg( + Arg::with_name(options::IGNORE_BACKUPS) + .short("B") + .long(options::IGNORE_BACKUPS) + .help("Ignore entries which end with ~."), + ) + + // Sort arguments + .arg( + Arg::with_name(options::SORT) + .long(options::SORT) + .help("Sort by : name, none (-U), time (-t), size (-S) or extension (-X)") + .value_name("field") + .takes_value(true) + .possible_values(&["name", "none", "time", "size", "version", "extension"]) + .require_equals(true) + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]) + ) + .arg( + Arg::with_name(options::sort::SIZE) + .short(options::sort::SIZE) + .help("Sort by file size, largest first.") + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]) + ) + .arg( + Arg::with_name(options::sort::TIME) + .short(options::sort::TIME) + .help("Sort by modification time (the 'mtime' in the inode), newest first.") + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]) + ) + .arg( + Arg::with_name(options::sort::VERSION) + .short(options::sort::VERSION) + .help("Natural sort of (version) numbers in the filenames.") + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]) + ) + .arg( + Arg::with_name(options::sort::EXTENSION) + .short(options::sort::EXTENSION) + .help("Sort alphabetically by entry extension.") + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]) + ) + .arg( + Arg::with_name(options::sort::NONE) + .short(options::sort::NONE) + .help("Do not sort; list the files in whatever order they are stored in the \ + directory. This is especially useful when listing very large directories, \ + since not doing any sorting can be noticeably faster.") + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]) + ) + + // Dereferencing + .arg( + Arg::with_name(options::dereference::ALL) + .short("L") + .long(options::dereference::ALL) + .help( + "When showing file information for a symbolic link, show information for the \ + file the link references rather than the link itself.", + ) + .overrides_with_all(&[ + options::dereference::ALL, + options::dereference::DIR_ARGS, + options::dereference::ARGS, + ]) + ) + .arg( + Arg::with_name(options::dereference::DIR_ARGS) + .long(options::dereference::DIR_ARGS) + .help( + "Do not dereference symlinks except when they link to directories and are \ + given as command line arguments.", + ) + .overrides_with_all(&[ + options::dereference::ALL, + options::dereference::DIR_ARGS, + options::dereference::ARGS, + ]) + ) + .arg( + Arg::with_name(options::dereference::ARGS) + .short("H") + .long(options::dereference::ARGS) + .help( + "Do not dereference symlinks except when given as command line arguments.", + ) + .overrides_with_all(&[ + options::dereference::ALL, + options::dereference::DIR_ARGS, + options::dereference::ARGS, + ]) + ) + + // Long format options + .arg( + Arg::with_name(options::NO_GROUP) + .long(options::NO_GROUP) + .short("-G") + .help("Do not show group in long format.") + ) + .arg( + Arg::with_name(options::AUTHOR) + .long(options::AUTHOR) + .help("Show author in long format. On the supported platforms, the author \ + always matches the file owner.") + ) + // Other Flags + .arg( + Arg::with_name(options::files::ALL) + .short("a") + .long(options::files::ALL) + .help("Do not ignore hidden files (files with names that start with '.')."), + ) + .arg( + Arg::with_name(options::files::ALMOST_ALL) + .short("A") + .long(options::files::ALMOST_ALL) + .help( + "In a directory, do not ignore all file names that start with '.', only ignore \ + '.' and '..'.", + ), + ) + .arg( + Arg::with_name(options::DIRECTORY) + .short("d") + .long(options::DIRECTORY) + .help( + "Only list the names of directories, rather than listing directory contents. \ + This will not follow symbolic links unless one of `--dereference-command-line \ + (-H)`, `--dereference (-L)`, or `--dereference-command-line-symlink-to-dir` is \ + specified.", + ), + ) + .arg( + Arg::with_name(options::size::HUMAN_READABLE) + .short("h") + .long(options::size::HUMAN_READABLE) + .help("Print human readable file sizes (e.g. 1K 234M 56G).") + .overrides_with(options::size::SI), + ) + .arg( + Arg::with_name(options::size::SI) + .long(options::size::SI) + .help("Print human readable file sizes using powers of 1000 instead of 1024.") + ) + .arg( + Arg::with_name(options::INODE) + .short("i") + .long(options::INODE) + .help("print the index number of each file"), + ) + .arg( + Arg::with_name(options::REVERSE) + .short("r") + .long(options::REVERSE) + .help("Reverse whatever the sorting method is--e.g., list files in reverse \ + alphabetical order, youngest first, smallest first, or whatever.", + )) + .arg( + Arg::with_name(options::RECURSIVE) + .short("R") + .long(options::RECURSIVE) + .help("List the contents of all directories recursively."), + ) + .arg( + Arg::with_name(options::WIDTH) + .long(options::WIDTH) + .short("w") + .help("Assume that the terminal is COLS columns wide.") + .value_name("COLS") + .takes_value(true) + ) + .arg( + Arg::with_name(options::COLOR) + .long(options::COLOR) + .help("Color output based on file type.") + .takes_value(true) + .require_equals(true) + .min_values(0), + ) + .arg( + Arg::with_name(options::INDICATOR_STYLE) + .long(options::INDICATOR_STYLE) + .help(" append indicator with style WORD to entry names: none (default), slash\ + (-p), file-type (--file-type), classify (-F)") + .takes_value(true) + .possible_values(&["none", "slash", "file-type", "classify"]) + .overrides_with_all(&[ + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, + options::INDICATOR_STYLE, + ])) + .arg( + Arg::with_name(options::indicator_style::CLASSIFY) + .short("F") + .long(options::indicator_style::CLASSIFY) + .help("Append a character to each file name indicating the file type. Also, for \ + regular files that are executable, append '*'. The file type indicators are \ + '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ + '>' for doors, and nothing for regular files.") + .overrides_with_all(&[ + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, + options::INDICATOR_STYLE, + ]) + ) + .arg( + Arg::with_name(options::indicator_style::FILE_TYPE) + .long(options::indicator_style::FILE_TYPE) + .help("Same as --classify, but do not append '*'") + .overrides_with_all(&[ + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, + options::INDICATOR_STYLE, + ])) + .arg( + Arg::with_name(options::indicator_style::SLASH) + .short(options::indicator_style::SLASH) + .help("Append / indicator to directories." + ) + .overrides_with_all(&[ + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, + options::INDICATOR_STYLE, + ])) + .arg( + //This still needs support for posix-*, +FORMAT + Arg::with_name(options::TIME_STYLE) + .long(options::TIME_STYLE) + .help("time/date format with -l; see TIME_STYLE below") + .value_name("TIME_STYLE") + .env("TIME_STYLE") + .possible_values(&[ + "full-iso", + "long-iso", + "iso", + "locale", + ]) + .overrides_with_all(&[ + options::TIME_STYLE + ]) + ) + .arg( + Arg::with_name(options::FULL_TIME) + .long(options::FULL_TIME) + .overrides_with(options::FULL_TIME) + .help("like -l --time-style=full-iso") + ) + + // Positional arguments + .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)) +} diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index dc67d57386c..b6b30522686 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -13,10 +13,11 @@ extern crate uucore; #[macro_use] extern crate lazy_static; +pub mod app; mod quoting_style; mod version_cmp; -use clap::{crate_version, App, Arg}; +use app::options; use globset::{self, Glob, GlobSet, GlobSetBuilder}; use lscolors::LsColors; use number_prefix::NumberPrefix; @@ -45,86 +46,10 @@ use unicode_width::UnicodeWidthStr; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; -static ABOUT: &str = " - By default, ls will list the files and contents of any directories on - the command line, expect that it will ignore files and directories - whose names start with '.' -"; -static AFTER_HELP: &str = "The TIME_STYLE argument can be full-iso, long-iso, iso. -Also the TIME_STYLE environment variable sets the default style to use."; - fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } -pub mod options { - pub mod format { - pub static ONE_LINE: &str = "1"; - pub static LONG: &str = "long"; - pub static COLUMNS: &str = "C"; - pub static ACROSS: &str = "x"; - pub static COMMAS: &str = "m"; - pub static LONG_NO_OWNER: &str = "g"; - pub static LONG_NO_GROUP: &str = "o"; - pub static LONG_NUMERIC_UID_GID: &str = "numeric-uid-gid"; - } - pub mod files { - pub static ALL: &str = "all"; - pub static ALMOST_ALL: &str = "almost-all"; - } - pub mod sort { - pub static SIZE: &str = "S"; - pub static TIME: &str = "t"; - pub static NONE: &str = "U"; - pub static VERSION: &str = "v"; - pub static EXTENSION: &str = "X"; - } - pub mod time { - pub static ACCESS: &str = "u"; - pub static CHANGE: &str = "c"; - } - pub mod size { - pub static HUMAN_READABLE: &str = "human-readable"; - pub static SI: &str = "si"; - } - pub mod quoting { - pub static ESCAPE: &str = "escape"; - pub static LITERAL: &str = "literal"; - pub static C: &str = "quote-name"; - } - pub static QUOTING_STYLE: &str = "quoting-style"; - pub mod indicator_style { - pub static SLASH: &str = "p"; - pub static FILE_TYPE: &str = "file-type"; - pub static CLASSIFY: &str = "classify"; - } - pub mod dereference { - pub static ALL: &str = "dereference"; - pub static ARGS: &str = "dereference-command-line"; - pub static DIR_ARGS: &str = "dereference-command-line-symlink-to-dir"; - } - pub static HIDE_CONTROL_CHARS: &str = "hide-control-chars"; - pub static SHOW_CONTROL_CHARS: &str = "show-control-chars"; - pub static WIDTH: &str = "width"; - pub static AUTHOR: &str = "author"; - pub static NO_GROUP: &str = "no-group"; - pub static FORMAT: &str = "format"; - pub static SORT: &str = "sort"; - pub static TIME: &str = "time"; - pub static IGNORE_BACKUPS: &str = "ignore-backups"; - pub static DIRECTORY: &str = "directory"; - pub static INODE: &str = "inode"; - pub static REVERSE: &str = "reverse"; - pub static RECURSIVE: &str = "recursive"; - pub static COLOR: &str = "color"; - pub static PATHS: &str = "paths"; - pub static INDICATOR_STYLE: &str = "indicator-style"; - pub static TIME_STYLE: &str = "time-style"; - pub static FULL_TIME: &str = "full-time"; - pub static HIDE: &str = "hide"; - pub static IGNORE: &str = "ignore"; -} - #[derive(PartialEq, Eq)] enum Format { Columns, @@ -557,544 +482,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let app = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - - // Format arguments - .arg( - Arg::with_name(options::FORMAT) - .long(options::FORMAT) - .help("Set the display format.") - .takes_value(true) - .possible_values(&["long", "verbose", "single-column", "columns", "vertical", "across", "horizontal", "commas"]) - .hide_possible_values(true) - .require_equals(true) - .overrides_with_all(&[ - options::FORMAT, - options::format::COLUMNS, - options::format::LONG, - options::format::ACROSS, - options::format::COLUMNS, - ]), - ) - .arg( - Arg::with_name(options::format::COLUMNS) - .short(options::format::COLUMNS) - .help("Display the files in columns.") - .overrides_with_all(&[ - options::FORMAT, - options::format::COLUMNS, - options::format::LONG, - options::format::ACROSS, - options::format::COLUMNS, - ]), - ) - .arg( - Arg::with_name(options::format::LONG) - .short("l") - .long(options::format::LONG) - .help("Display detailed information.") - .overrides_with_all(&[ - options::FORMAT, - options::format::COLUMNS, - options::format::LONG, - options::format::ACROSS, - options::format::COLUMNS, - ]), - ) - .arg( - Arg::with_name(options::format::ACROSS) - .short(options::format::ACROSS) - .help("List entries in rows instead of in columns.") - .overrides_with_all(&[ - options::FORMAT, - options::format::COLUMNS, - options::format::LONG, - options::format::ACROSS, - options::format::COLUMNS, - ]), - ) - .arg( - Arg::with_name(options::format::COMMAS) - .short(options::format::COMMAS) - .help("List entries separated by commas.") - .overrides_with_all(&[ - options::FORMAT, - options::format::COLUMNS, - options::format::LONG, - options::format::ACROSS, - options::format::COLUMNS, - ]), - ) - // The next four arguments do not override with the other format - // options, see the comment in Config::from for the reason. - // Ideally, they would use Arg::override_with, with their own name - // but that doesn't seem to work in all cases. Example: - // ls -1g1 - // even though `ls -11` and `ls -1 -g -1` work. - .arg( - Arg::with_name(options::format::ONE_LINE) - .short(options::format::ONE_LINE) - .help("List one file per line.") - .multiple(true) - ) - .arg( - Arg::with_name(options::format::LONG_NO_GROUP) - .short(options::format::LONG_NO_GROUP) - .help("Long format without group information. Identical to --format=long with --no-group.") - .multiple(true) - ) - .arg( - Arg::with_name(options::format::LONG_NO_OWNER) - .short(options::format::LONG_NO_OWNER) - .help("Long format without owner information.") - .multiple(true) - ) - .arg( - Arg::with_name(options::format::LONG_NUMERIC_UID_GID) - .short("n") - .long(options::format::LONG_NUMERIC_UID_GID) - .help("-l with numeric UIDs and GIDs.") - .multiple(true) - ) - - // Quoting style - .arg( - Arg::with_name(options::QUOTING_STYLE) - .long(options::QUOTING_STYLE) - .takes_value(true) - .help("Set quoting style.") - .possible_values(&["literal", "shell", "shell-always", "shell-escape", "shell-escape-always", "c", "escape"]) - .overrides_with_all(&[ - options::QUOTING_STYLE, - options::quoting::LITERAL, - options::quoting::ESCAPE, - options::quoting::C, - ]) - ) - .arg( - Arg::with_name(options::quoting::LITERAL) - .short("N") - .long(options::quoting::LITERAL) - .help("Use literal quoting style. Equivalent to `--quoting-style=literal`") - .overrides_with_all(&[ - options::QUOTING_STYLE, - options::quoting::LITERAL, - options::quoting::ESCAPE, - options::quoting::C, - ]) - ) - .arg( - Arg::with_name(options::quoting::ESCAPE) - .short("b") - .long(options::quoting::ESCAPE) - .help("Use escape quoting style. Equivalent to `--quoting-style=escape`") - .overrides_with_all(&[ - options::QUOTING_STYLE, - options::quoting::LITERAL, - options::quoting::ESCAPE, - options::quoting::C, - ]) - ) - .arg( - Arg::with_name(options::quoting::C) - .short("Q") - .long(options::quoting::C) - .help("Use C quoting style. Equivalent to `--quoting-style=c`") - .overrides_with_all(&[ - options::QUOTING_STYLE, - options::quoting::LITERAL, - options::quoting::ESCAPE, - options::quoting::C, - ]) - ) - - // Control characters - .arg( - Arg::with_name(options::HIDE_CONTROL_CHARS) - .short("q") - .long(options::HIDE_CONTROL_CHARS) - .help("Replace control characters with '?' if they are not escaped.") - .overrides_with_all(&[ - options::HIDE_CONTROL_CHARS, - options::SHOW_CONTROL_CHARS, - ]) - ) - .arg( - Arg::with_name(options::SHOW_CONTROL_CHARS) - .long(options::SHOW_CONTROL_CHARS) - .help("Show control characters 'as is' if they are not escaped.") - .overrides_with_all(&[ - options::HIDE_CONTROL_CHARS, - options::SHOW_CONTROL_CHARS, - ]) - ) - - // Time arguments - .arg( - Arg::with_name(options::TIME) - .long(options::TIME) - .help("Show time in :\n\ - \taccess time (-u): atime, access, use;\n\ - \tchange time (-t): ctime, status.\n\ - \tbirth time: birth, creation;") - .value_name("field") - .takes_value(true) - .possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"]) - .hide_possible_values(true) - .require_equals(true) - .overrides_with_all(&[ - options::TIME, - options::time::ACCESS, - options::time::CHANGE, - ]) - ) - .arg( - Arg::with_name(options::time::CHANGE) - .short(options::time::CHANGE) - .help("If the long listing format (e.g., -l, -o) is being used, print the status \ - change time (the ‘ctime’ in the inode) instead of the modification time. When \ - explicitly sorting by time (--sort=time or -t) or when not using a long listing \ - format, sort according to the status change time.") - .overrides_with_all(&[ - options::TIME, - options::time::ACCESS, - options::time::CHANGE, - ]) - ) - .arg( - Arg::with_name(options::time::ACCESS) - .short(options::time::ACCESS) - .help("If the long listing format (e.g., -l, -o) is being used, print the status \ - access time instead of the modification time. When explicitly sorting by time \ - (--sort=time or -t) or when not using a long listing format, sort according to the \ - access time.") - .overrides_with_all(&[ - options::TIME, - options::time::ACCESS, - options::time::CHANGE, - ]) - ) - - // Hide and ignore - .arg( - Arg::with_name(options::HIDE) - .long(options::HIDE) - .takes_value(true) - .multiple(true) - .value_name("PATTERN") - .help("do not list implied entries matching shell PATTERN (overridden by -a or -A)") - ) - .arg( - Arg::with_name(options::IGNORE) - .short("I") - .long(options::IGNORE) - .takes_value(true) - .multiple(true) - .value_name("PATTERN") - .help("do not list implied entries matching shell PATTERN") - ) - .arg( - Arg::with_name(options::IGNORE_BACKUPS) - .short("B") - .long(options::IGNORE_BACKUPS) - .help("Ignore entries which end with ~."), - ) - - // Sort arguments - .arg( - Arg::with_name(options::SORT) - .long(options::SORT) - .help("Sort by : name, none (-U), time (-t), size (-S) or extension (-X)") - .value_name("field") - .takes_value(true) - .possible_values(&["name", "none", "time", "size", "version", "extension"]) - .require_equals(true) - .overrides_with_all(&[ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]) - ) - .arg( - Arg::with_name(options::sort::SIZE) - .short(options::sort::SIZE) - .help("Sort by file size, largest first.") - .overrides_with_all(&[ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]) - ) - .arg( - Arg::with_name(options::sort::TIME) - .short(options::sort::TIME) - .help("Sort by modification time (the 'mtime' in the inode), newest first.") - .overrides_with_all(&[ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]) - ) - .arg( - Arg::with_name(options::sort::VERSION) - .short(options::sort::VERSION) - .help("Natural sort of (version) numbers in the filenames.") - .overrides_with_all(&[ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]) - ) - .arg( - Arg::with_name(options::sort::EXTENSION) - .short(options::sort::EXTENSION) - .help("Sort alphabetically by entry extension.") - .overrides_with_all(&[ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]) - ) - .arg( - Arg::with_name(options::sort::NONE) - .short(options::sort::NONE) - .help("Do not sort; list the files in whatever order they are stored in the \ - directory. This is especially useful when listing very large directories, \ - since not doing any sorting can be noticeably faster.") - .overrides_with_all(&[ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]) - ) - - // Dereferencing - .arg( - Arg::with_name(options::dereference::ALL) - .short("L") - .long(options::dereference::ALL) - .help( - "When showing file information for a symbolic link, show information for the \ - file the link references rather than the link itself.", - ) - .overrides_with_all(&[ - options::dereference::ALL, - options::dereference::DIR_ARGS, - options::dereference::ARGS, - ]) - ) - .arg( - Arg::with_name(options::dereference::DIR_ARGS) - .long(options::dereference::DIR_ARGS) - .help( - "Do not dereference symlinks except when they link to directories and are \ - given as command line arguments.", - ) - .overrides_with_all(&[ - options::dereference::ALL, - options::dereference::DIR_ARGS, - options::dereference::ARGS, - ]) - ) - .arg( - Arg::with_name(options::dereference::ARGS) - .short("H") - .long(options::dereference::ARGS) - .help( - "Do not dereference symlinks except when given as command line arguments.", - ) - .overrides_with_all(&[ - options::dereference::ALL, - options::dereference::DIR_ARGS, - options::dereference::ARGS, - ]) - ) - - // Long format options - .arg( - Arg::with_name(options::NO_GROUP) - .long(options::NO_GROUP) - .short("-G") - .help("Do not show group in long format.") - ) - .arg( - Arg::with_name(options::AUTHOR) - .long(options::AUTHOR) - .help("Show author in long format. On the supported platforms, the author \ - always matches the file owner.") - ) - // Other Flags - .arg( - Arg::with_name(options::files::ALL) - .short("a") - .long(options::files::ALL) - .help("Do not ignore hidden files (files with names that start with '.')."), - ) - .arg( - Arg::with_name(options::files::ALMOST_ALL) - .short("A") - .long(options::files::ALMOST_ALL) - .help( - "In a directory, do not ignore all file names that start with '.', only ignore \ - '.' and '..'.", - ), - ) - .arg( - Arg::with_name(options::DIRECTORY) - .short("d") - .long(options::DIRECTORY) - .help( - "Only list the names of directories, rather than listing directory contents. \ - This will not follow symbolic links unless one of `--dereference-command-line \ - (-H)`, `--dereference (-L)`, or `--dereference-command-line-symlink-to-dir` is \ - specified.", - ), - ) - .arg( - Arg::with_name(options::size::HUMAN_READABLE) - .short("h") - .long(options::size::HUMAN_READABLE) - .help("Print human readable file sizes (e.g. 1K 234M 56G).") - .overrides_with(options::size::SI), - ) - .arg( - Arg::with_name(options::size::SI) - .long(options::size::SI) - .help("Print human readable file sizes using powers of 1000 instead of 1024.") - ) - .arg( - Arg::with_name(options::INODE) - .short("i") - .long(options::INODE) - .help("print the index number of each file"), - ) - .arg( - Arg::with_name(options::REVERSE) - .short("r") - .long(options::REVERSE) - .help("Reverse whatever the sorting method is--e.g., list files in reverse \ - alphabetical order, youngest first, smallest first, or whatever.", - )) - .arg( - Arg::with_name(options::RECURSIVE) - .short("R") - .long(options::RECURSIVE) - .help("List the contents of all directories recursively."), - ) - .arg( - Arg::with_name(options::WIDTH) - .long(options::WIDTH) - .short("w") - .help("Assume that the terminal is COLS columns wide.") - .value_name("COLS") - .takes_value(true) - ) - .arg( - Arg::with_name(options::COLOR) - .long(options::COLOR) - .help("Color output based on file type.") - .takes_value(true) - .require_equals(true) - .min_values(0), - ) - .arg( - Arg::with_name(options::INDICATOR_STYLE) - .long(options::INDICATOR_STYLE) - .help(" append indicator with style WORD to entry names: none (default), slash\ - (-p), file-type (--file-type), classify (-F)") - .takes_value(true) - .possible_values(&["none", "slash", "file-type", "classify"]) - .overrides_with_all(&[ - options::indicator_style::FILE_TYPE, - options::indicator_style::SLASH, - options::indicator_style::CLASSIFY, - options::INDICATOR_STYLE, - ])) - .arg( - Arg::with_name(options::indicator_style::CLASSIFY) - .short("F") - .long(options::indicator_style::CLASSIFY) - .help("Append a character to each file name indicating the file type. Also, for \ - regular files that are executable, append '*'. The file type indicators are \ - '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ - '>' for doors, and nothing for regular files.") - .overrides_with_all(&[ - options::indicator_style::FILE_TYPE, - options::indicator_style::SLASH, - options::indicator_style::CLASSIFY, - options::INDICATOR_STYLE, - ]) - ) - .arg( - Arg::with_name(options::indicator_style::FILE_TYPE) - .long(options::indicator_style::FILE_TYPE) - .help("Same as --classify, but do not append '*'") - .overrides_with_all(&[ - options::indicator_style::FILE_TYPE, - options::indicator_style::SLASH, - options::indicator_style::CLASSIFY, - options::INDICATOR_STYLE, - ])) - .arg( - Arg::with_name(options::indicator_style::SLASH) - .short(options::indicator_style::SLASH) - .help("Append / indicator to directories." - ) - .overrides_with_all(&[ - options::indicator_style::FILE_TYPE, - options::indicator_style::SLASH, - options::indicator_style::CLASSIFY, - options::INDICATOR_STYLE, - ])) - .arg( - //This still needs support for posix-*, +FORMAT - Arg::with_name(options::TIME_STYLE) - .long(options::TIME_STYLE) - .help("time/date format with -l; see TIME_STYLE below") - .value_name("TIME_STYLE") - .env("TIME_STYLE") - .possible_values(&[ - "full-iso", - "long-iso", - "iso", - "locale", - ]) - .overrides_with_all(&[ - options::TIME_STYLE - ]) - ) - .arg( - Arg::with_name(options::FULL_TIME) - .long(options::FULL_TIME) - .overrides_with(options::FULL_TIME) - .help("like -l --time-style=full-iso") - ) - - // Positional arguments - .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)) - - .after_help(AFTER_HELP); + let app = get_app(executable!()).usage(&usage[..]); let matches = app.get_matches_from(args); @@ -1532,6 +920,8 @@ use std::sync::Mutex; use uucore::entries; use uucore::InvalidEncodingHandling; +use crate::app::get_app; + #[cfg(unix)] fn cached_uid2usr(uid: u32) -> String { lazy_static! { diff --git a/src/uu/mkdir/src/app.rs b/src/uu/mkdir/src/app.rs new file mode 100644 index 00000000000..c786b0182e3 --- /dev/null +++ b/src/uu/mkdir/src/app.rs @@ -0,0 +1,40 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist"; +pub const OPT_MODE: &str = "mode"; +pub const OPT_PARENTS: &str = "parents"; +pub const OPT_VERBOSE: &str = "verbose"; + +pub const ARG_DIRS: &str = "dirs"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_MODE) + .short("m") + .long(OPT_MODE) + .help("set file mode") + .default_value("755"), + ) + .arg( + Arg::with_name(OPT_PARENTS) + .short("p") + .long(OPT_PARENTS) + .alias("parent") + .help("make parent directories as needed"), + ) + .arg( + Arg::with_name(OPT_VERBOSE) + .short("v") + .long(OPT_VERBOSE) + .help("print a message for each printed directory"), + ) + .arg( + Arg::with_name(ARG_DIRS) + .multiple(true) + .takes_value(true) + .min_values(1), + ) +} diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index e8a8ef2dbc5..c111e2d6b19 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -8,16 +8,12 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::fs; use std::path::Path; -static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist"; -static OPT_MODE: &str = "mode"; -static OPT_PARENTS: &str = "parents"; -static OPT_VERBOSE: &str = "verbose"; +use crate::app::*; -static ARG_DIRS: &str = "dirs"; +pub mod app; fn get_usage() -> String { format!("{0} [OPTION]... [USER]", executable!()) @@ -32,36 +28,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // Linux-specific options, not implemented // opts.optflag("Z", "context", "set SELinux security context" + // " of each created directory to CTX"), - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_MODE) - .short("m") - .long(OPT_MODE) - .help("set file mode") - .default_value("755"), - ) - .arg( - Arg::with_name(OPT_PARENTS) - .short("p") - .long(OPT_PARENTS) - .alias("parent") - .help("make parent directories as needed"), - ) - .arg( - Arg::with_name(OPT_VERBOSE) - .short("v") - .long(OPT_VERBOSE) - .help("print a message for each printed directory"), - ) - .arg( - Arg::with_name(ARG_DIRS) - .multiple(true) - .takes_value(true) - .min_values(1), - ) .get_matches_from(args); let dirs: Vec = matches diff --git a/src/uu/mkfifo/src/app.rs b/src/uu/mkfifo/src/app.rs new file mode 100644 index 00000000000..ab0b26ad644 --- /dev/null +++ b/src/uu/mkfifo/src/app.rs @@ -0,0 +1,39 @@ +use clap::{crate_version, App, Arg}; + +const SUMMARY: &str = "Create a FIFO with the given name."; + +pub mod options { + pub const MODE: &str = "mode"; + pub const SE_LINUX_SECURITY_CONTEXT: &str = "Z"; + pub const CONTEXT: &str = "context"; + pub const FIFO: &str = "fifo"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(SUMMARY) + .arg( + Arg::with_name(options::MODE) + .short("m") + .long(options::MODE) + .help("file permissions for the fifo") + .default_value("0666") + .value_name("0666"), + ) + .arg( + Arg::with_name(options::SE_LINUX_SECURITY_CONTEXT) + .short(options::SE_LINUX_SECURITY_CONTEXT) + .help("set the SELinux security context to default type"), + ) + .arg( + Arg::with_name(options::CONTEXT) + .long(options::CONTEXT) + .value_name("CTX") + .help( + "like -Z, or if CTX is specified then set the SELinux \ + or SMACK security context to CTX", + ), + ) + .arg(Arg::with_name(options::FIFO).hidden(true).multiple(true)) +} diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index b8a6bbe38e0..41290123362 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -8,47 +8,26 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use libc::mkfifo; use std::ffi::CString; use uucore::InvalidEncodingHandling; -static NAME: &str = "mkfifo"; -static USAGE: &str = "mkfifo [OPTION]... NAME..."; -static SUMMARY: &str = "Create a FIFO with the given name."; +use crate::app::{get_app, options}; -mod options { - pub static MODE: &str = "mode"; - pub static SE_LINUX_SECURITY_CONTEXT: &str = "Z"; - pub static CONTEXT: &str = "context"; - pub static FIFO: &str = "fifo"; +pub mod app; + +fn get_usage() -> String { + format!("{} [OPTION]... NAME...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { + let usage = get_usage(); let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(USAGE) - .about(SUMMARY) - .arg( - Arg::with_name(options::MODE) - .short("m") - .long(options::MODE) - .help("file permissions for the fifo") - .default_value("0666") - .value_name("0666"), - ) - .arg( - Arg::with_name(options::SE_LINUX_SECURITY_CONTEXT) - .short(options::SE_LINUX_SECURITY_CONTEXT) - .help("set the SELinux security context to default type") - ) - .arg(Arg::with_name(options::CONTEXT).long(options::CONTEXT).value_name("CTX").help("like -Z, or if CTX is specified then set the SELinux\nor SMACK security context to CTX")) - .arg(Arg::with_name(options::FIFO).hidden(true).multiple(true)) + let matches = get_app(executable!()) + .usage(usage.as_str()) .get_matches_from(args); if matches.is_present(options::CONTEXT) { diff --git a/src/uu/mknod/src/app.rs b/src/uu/mknod/src/app.rs new file mode 100644 index 00000000000..b5514253063 --- /dev/null +++ b/src/uu/mknod/src/app.rs @@ -0,0 +1,85 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Create the special file NAME of the given TYPE."; +const USAGE: &str = "mknod [OPTION]... NAME TYPE [MAJOR MINOR]"; +const 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. +"; + +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) +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .usage(USAGE) + .after_help(LONG_HELP) + .about(ABOUT) + .arg( + Arg::with_name("mode") + .short("m") + .long("mode") + .value_name("MODE") + .help("set file permission bits to MODE, not a=rw - umask"), + ) + .arg( + Arg::with_name("name") + .value_name("NAME") + .help("name of the new file") + .required(true) + .index(1), + ) + .arg( + Arg::with_name("type") + .value_name("TYPE") + .help("type of the new file (b, c, u or p)") + .required(true) + .validator(valid_type) + .index(2), + ) + .arg( + Arg::with_name("major") + .value_name("MAJOR") + .help("major file type") + .validator(valid_u64) + .index(3), + ) + .arg( + Arg::with_name("minor") + .value_name("MINOR") + .help("minor file type") + .validator(valid_u64) + .index(4), + ) +} diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index e5e6ef1fa14..c083ca2c152 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -12,33 +12,15 @@ extern crate uucore; use std::ffi::CString; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::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 uucore::InvalidEncodingHandling; -static NAME: &str = "mknod"; -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 +use crate::app::get_app; -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. -"; +pub mod app; const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; @@ -72,7 +54,7 @@ fn _mknod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 { } if errno == -1 { - let c_str = CString::new(NAME).expect("Failed to convert to CString"); + let c_str = CString::new(executable!()).expect("Failed to convert to CString"); // shows the error from the mknod syscall libc::perror(c_str.as_ptr()); } @@ -89,48 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // opts.optflag("Z", "", "set the SELinux security context to default type"); // opts.optopt("", "context", "like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX"); - let matches = App::new(executable!()) - .version(crate_version!()) - .usage(USAGE) - .after_help(LONG_HELP) - .about(ABOUT) - .arg( - Arg::with_name("mode") - .short("m") - .long("mode") - .value_name("MODE") - .help("set file permission bits to MODE, not a=rw - umask"), - ) - .arg( - Arg::with_name("name") - .value_name("NAME") - .help("name of the new file") - .required(true) - .index(1), - ) - .arg( - Arg::with_name("type") - .value_name("TYPE") - .help("type of the new file (b, c, u or p)") - .required(true) - .validator(valid_type) - .index(2), - ) - .arg( - Arg::with_name("major") - .value_name("MAJOR") - .help("major file type") - .validator(valid_u64) - .index(3), - ) - .arg( - Arg::with_name("minor") - .value_name("MINOR") - .help("minor file type") - .validator(valid_u64) - .index(4), - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let mode = match get_mode(&matches) { Ok(mode) => mode, @@ -154,7 +95,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 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); + eprintln!("Try '{} --help' for more information.", executable!()); 1 } else { _mknod(file_name, S_IFIFO | mode, 0) @@ -163,7 +104,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 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); + eprintln!("Try '{} --help' for more information.", executable!()); 1 } (Some(major), Some(minor)) => { @@ -199,22 +140,3 @@ fn get_mode(matches: &ArgMatches) -> Result { }), } } - -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/mktemp/src/app.rs b/src/uu/mktemp/src/app.rs new file mode 100644 index 00000000000..40ee5b7834f --- /dev/null +++ b/src/uu/mktemp/src/app.rs @@ -0,0 +1,70 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "create a temporary file or directory."; + +pub const DEFAULT_TEMPLATE: &str = "tmp.XXXXXXXXXX"; + +pub const OPT_DIRECTORY: &str = "directory"; +pub const OPT_DRY_RUN: &str = "dry-run"; +pub const OPT_QUIET: &str = "quiet"; +pub const OPT_SUFFIX: &str = "suffix"; +pub const OPT_TMPDIR: &str = "tmpdir"; +pub const OPT_T: &str = "t"; + +pub const ARG_TEMPLATE: &str = "template"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_DIRECTORY) + .short("d") + .long(OPT_DIRECTORY) + .help("Make a directory instead of a file"), + ) + .arg( + Arg::with_name(OPT_DRY_RUN) + .short("u") + .long(OPT_DRY_RUN) + .help("do not create anything; merely print a name (unsafe)"), + ) + .arg( + Arg::with_name(OPT_QUIET) + .short("q") + .long("quiet") + .help("Fail silently if an error occurs."), + ) + .arg( + Arg::with_name(OPT_SUFFIX) + .long(OPT_SUFFIX) + .help( + "append SUFFIX to TEMPLATE; SUFFIX must not contain a path separator. \ + This option is implied if TEMPLATE does not end with X.", + ) + .value_name("SUFFIX"), + ) + .arg( + Arg::with_name(OPT_TMPDIR) + .short("p") + .long(OPT_TMPDIR) + .help( + "interpret TEMPLATE relative to DIR; if DIR is not specified, use \ + $TMPDIR if set, else /tmp. With this option, TEMPLATE must not \ + be an absolute name; unlike with -t, TEMPLATE may contain \ + slashes, but mktemp creates only the final component", + ) + .value_name("DIR"), + ) + .arg(Arg::with_name(OPT_T).short(OPT_T).help( + "Generate a template (using the supplied prefix and TMPDIR if set) \ + to create a filename template [deprecated]", + )) + .arg( + Arg::with_name(ARG_TEMPLATE) + .multiple(false) + .takes_value(true) + .max_values(1) + .default_value(DEFAULT_TEMPLATE), + ) +} diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index e04de870217..527a7fe33e6 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -11,8 +11,6 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; - use std::env; use std::iter; use std::path::{is_separator, PathBuf}; @@ -20,18 +18,9 @@ use std::path::{is_separator, PathBuf}; use rand::Rng; use tempfile::Builder; -static ABOUT: &str = "create a temporary file or directory."; - -static DEFAULT_TEMPLATE: &str = "tmp.XXXXXXXXXX"; - -static OPT_DIRECTORY: &str = "directory"; -static OPT_DRY_RUN: &str = "dry-run"; -static OPT_QUIET: &str = "quiet"; -static OPT_SUFFIX: &str = "suffix"; -static OPT_TMPDIR: &str = "tmpdir"; -static OPT_T: &str = "t"; +use crate::app::*; -static ARG_TEMPLATE: &str = "template"; +pub mod app; fn get_usage() -> String { format!("{0} [OPTION]... [TEMPLATE]", executable!()) @@ -40,60 +29,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_DIRECTORY) - .short("d") - .long(OPT_DIRECTORY) - .help("Make a directory instead of a file"), - ) - .arg( - Arg::with_name(OPT_DRY_RUN) - .short("u") - .long(OPT_DRY_RUN) - .help("do not create anything; merely print a name (unsafe)"), - ) - .arg( - Arg::with_name(OPT_QUIET) - .short("q") - .long("quiet") - .help("Fail silently if an error occurs."), - ) - .arg( - Arg::with_name(OPT_SUFFIX) - .long(OPT_SUFFIX) - .help( - "append SUFFIX to TEMPLATE; SUFFIX must not contain a path separator. \ - This option is implied if TEMPLATE does not end with X.", - ) - .value_name("SUFFIX"), - ) - .arg( - Arg::with_name(OPT_TMPDIR) - .short("p") - .long(OPT_TMPDIR) - .help( - "interpret TEMPLATE relative to DIR; if DIR is not specified, use \ - $TMPDIR if set, else /tmp. With this option, TEMPLATE must not \ - be an absolute name; unlike with -t, TEMPLATE may contain \ - slashes, but mktemp creates only the final component", - ) - .value_name("DIR"), - ) - .arg(Arg::with_name(OPT_T).short(OPT_T).help( - "Generate a template (using the supplied prefix and TMPDIR if set) \ - to create a filename template [deprecated]", - )) - .arg( - Arg::with_name(ARG_TEMPLATE) - .multiple(false) - .takes_value(true) - .max_values(1) - .default_value(DEFAULT_TEMPLATE), - ) .get_matches_from(args); let template = matches.value_of(ARG_TEMPLATE).unwrap(); diff --git a/src/uu/more/src/app.rs b/src/uu/more/src/app.rs new file mode 100644 index 00000000000..9aa25a90829 --- /dev/null +++ b/src/uu/more/src/app.rs @@ -0,0 +1,107 @@ +use clap::{crate_version, App, Arg}; + +#[allow(dead_code)] +pub mod options { + pub const SILENT: &str = "silent"; + pub const LOGICAL: &str = "logical"; + pub const NO_PAUSE: &str = "no-pause"; + pub const PRINT_OVER: &str = "print-over"; + pub const CLEAN_PRINT: &str = "clean-print"; + pub const SQUEEZE: &str = "squeeze"; + pub const PLAIN: &str = "plain"; + pub const LINES: &str = "lines"; + pub const NUMBER: &str = "number"; + pub const PATTERN: &str = "pattern"; + pub const FROM_LINE: &str = "from-line"; + pub const FILES: &str = "files"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .about("A file perusal filter for CRT viewing.") + .version(crate_version!()) + .arg( + Arg::with_name(options::SILENT) + .short("d") + .long(options::SILENT) + .help("Display help instead of ringing bell"), + ) + // The commented arguments below are unimplemented: + /* + .arg( + Arg::with_name(options::LOGICAL) + .short("f") + .long(options::LOGICAL) + .help("Count logical rather than screen lines"), + ) + .arg( + Arg::with_name(options::NO_PAUSE) + .short("l") + .long(options::NO_PAUSE) + .help("Suppress pause after form feed"), + ) + .arg( + Arg::with_name(options::PRINT_OVER) + .short("c") + .long(options::PRINT_OVER) + .help("Do not scroll, display text and clean line ends"), + ) + .arg( + Arg::with_name(options::CLEAN_PRINT) + .short("p") + .long(options::CLEAN_PRINT) + .help("Do not scroll, clean screen and display text"), + ) + .arg( + Arg::with_name(options::SQUEEZE) + .short("s") + .long(options::SQUEEZE) + .help("Squeeze multiple blank lines into one"), + ) + .arg( + Arg::with_name(options::PLAIN) + .short("u") + .long(options::PLAIN) + .help("Suppress underlining and bold"), + ) + .arg( + Arg::with_name(options::LINES) + .short("n") + .long(options::LINES) + .value_name("number") + .takes_value(true) + .help("The number of lines per screen full"), + ) + .arg( + Arg::with_name(options::NUMBER) + .allow_hyphen_values(true) + .long(options::NUMBER) + .required(false) + .takes_value(true) + .help("Same as --lines"), + ) + .arg( + Arg::with_name(options::FROM_LINE) + .short("F") + .allow_hyphen_values(true) + .required(false) + .takes_value(true) + .value_name("number") + .help("Display file beginning from line number"), + ) + .arg( + Arg::with_name(options::PATTERN) + .short("P") + .allow_hyphen_values(true) + .required(false) + .takes_value(true) + .help("Display file beginning from pattern match"), + ) + */ + .arg( + Arg::with_name(options::FILES) + .required(false) + .multiple(true) + .help("Path to the files to be read"), + ) +} diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 206cebbc2e2..b7f6c44ba16 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -20,7 +20,6 @@ use std::{ #[cfg(all(unix, not(target_os = "fuchsia")))] extern crate nix; -use clap::{crate_version, App, Arg}; use crossterm::{ event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, execute, queue, @@ -31,114 +30,16 @@ use crossterm::{ use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; -const BELL: &str = "\x07"; +use crate::app::{get_app, options}; -pub mod options { - pub const SILENT: &str = "silent"; - pub const LOGICAL: &str = "logical"; - pub const NO_PAUSE: &str = "no-pause"; - pub const PRINT_OVER: &str = "print-over"; - pub const CLEAN_PRINT: &str = "clean-print"; - pub const SQUEEZE: &str = "squeeze"; - pub const PLAIN: &str = "plain"; - pub const LINES: &str = "lines"; - pub const NUMBER: &str = "number"; - pub const PATTERN: &str = "pattern"; - pub const FROM_LINE: &str = "from-line"; - pub const FILES: &str = "files"; -} +pub mod app; + +const BELL: &str = "\x07"; const MULTI_FILE_TOP_PROMPT: &str = "::::::::::::::\n{}\n::::::::::::::\n"; pub fn uumain(args: impl uucore::Args) -> i32 { - let matches = App::new(executable!()) - .about("A file perusal filter for CRT viewing.") - .version(crate_version!()) - .arg( - Arg::with_name(options::SILENT) - .short("d") - .long(options::SILENT) - .help("Display help instead of ringing bell"), - ) - // The commented arguments below are unimplemented: - /* - .arg( - Arg::with_name(options::LOGICAL) - .short("f") - .long(options::LOGICAL) - .help("Count logical rather than screen lines"), - ) - .arg( - Arg::with_name(options::NO_PAUSE) - .short("l") - .long(options::NO_PAUSE) - .help("Suppress pause after form feed"), - ) - .arg( - Arg::with_name(options::PRINT_OVER) - .short("c") - .long(options::PRINT_OVER) - .help("Do not scroll, display text and clean line ends"), - ) - .arg( - Arg::with_name(options::CLEAN_PRINT) - .short("p") - .long(options::CLEAN_PRINT) - .help("Do not scroll, clean screen and display text"), - ) - .arg( - Arg::with_name(options::SQUEEZE) - .short("s") - .long(options::SQUEEZE) - .help("Squeeze multiple blank lines into one"), - ) - .arg( - Arg::with_name(options::PLAIN) - .short("u") - .long(options::PLAIN) - .help("Suppress underlining and bold"), - ) - .arg( - Arg::with_name(options::LINES) - .short("n") - .long(options::LINES) - .value_name("number") - .takes_value(true) - .help("The number of lines per screen full"), - ) - .arg( - Arg::with_name(options::NUMBER) - .allow_hyphen_values(true) - .long(options::NUMBER) - .required(false) - .takes_value(true) - .help("Same as --lines"), - ) - .arg( - Arg::with_name(options::FROM_LINE) - .short("F") - .allow_hyphen_values(true) - .required(false) - .takes_value(true) - .value_name("number") - .help("Display file beginning from line number"), - ) - .arg( - Arg::with_name(options::PATTERN) - .short("P") - .allow_hyphen_values(true) - .required(false) - .takes_value(true) - .help("Display file beginning from pattern match"), - ) - */ - .arg( - Arg::with_name(options::FILES) - .required(false) - .multiple(true) - .help("Path to the files to be read"), - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let mut buff = String::new(); let silent = matches.is_present(options::SILENT); diff --git a/src/uu/mv/src/app.rs b/src/uu/mv/src/app.rs new file mode 100644 index 00000000000..d2c68d871ef --- /dev/null +++ b/src/uu/mv/src/app.rs @@ -0,0 +1,102 @@ +use clap::{crate_version, App, Arg}; +use uucore::backup_control; + +const ABOUT: &str = "Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; + +pub const OPT_BACKUP: &str = "backup"; +pub const OPT_BACKUP_NO_ARG: &str = "b"; +pub const OPT_FORCE: &str = "force"; +pub const OPT_INTERACTIVE: &str = "interactive"; +pub const OPT_NO_CLOBBER: &str = "no-clobber"; +pub const OPT_STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; +pub const OPT_SUFFIX: &str = "suffix"; +pub const OPT_TARGET_DIRECTORY: &str = "target-directory"; +pub const OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; +pub const OPT_UPDATE: &str = "update"; +pub const OPT_VERBOSE: &str = "verbose"; + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) +.arg( + Arg::with_name(OPT_BACKUP) + .long(OPT_BACKUP) + .help("make a backup of each existing destination file") + .takes_value(true) + .require_equals(true) + .min_values(0) + .possible_values(backup_control::BACKUP_CONTROL_VALUES) + .value_name("CONTROL") +) +.arg( + Arg::with_name(OPT_BACKUP_NO_ARG) + .short(OPT_BACKUP_NO_ARG) + .help("like --backup but does not accept an argument") +) +.arg( + Arg::with_name(OPT_FORCE) + .short("f") + .long(OPT_FORCE) + .help("do not prompt before overwriting") +) +.arg( + Arg::with_name(OPT_INTERACTIVE) + .short("i") + .long(OPT_INTERACTIVE) + .help("prompt before override") +) +.arg( + Arg::with_name(OPT_NO_CLOBBER).short("n") + .long(OPT_NO_CLOBBER) + .help("do not overwrite an existing file") +) +.arg( + Arg::with_name(OPT_STRIP_TRAILING_SLASHES) + .long(OPT_STRIP_TRAILING_SLASHES) + .help("remove any trailing slashes from each SOURCE argument") +) +.arg( + Arg::with_name(OPT_SUFFIX) + .short("S") + .long(OPT_SUFFIX) + .help("override the usual backup suffix") + .takes_value(true) + .value_name("SUFFIX") +) +.arg( + Arg::with_name(OPT_TARGET_DIRECTORY) + .short("t") + .long(OPT_TARGET_DIRECTORY) + .help("move all SOURCE arguments into DIRECTORY") + .takes_value(true) + .value_name("DIRECTORY") + .conflicts_with(OPT_NO_TARGET_DIRECTORY) +) +.arg( + Arg::with_name(OPT_NO_TARGET_DIRECTORY) + .short("T") + .long(OPT_NO_TARGET_DIRECTORY). + help("treat DEST as a normal file") +) +.arg( + Arg::with_name(OPT_UPDATE) + .short("u") + .long(OPT_UPDATE) + .help("move only when the SOURCE file is newer than the destination file or when the destination file is missing") +) +.arg( + Arg::with_name(OPT_VERBOSE) + .short("v") + .long(OPT_VERBOSE).help("explain what is being done") +) +.arg( + Arg::with_name(ARG_FILES) + .multiple(true) + .takes_value(true) + .min_values(2) + .required(true) + ) +} diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index bb402737e4a..00ef3d8a5bf 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -11,8 +11,7 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg, ArgMatches}; -use std::env; +use clap::ArgMatches; use std::fs; use std::io::{self, stdin}; #[cfg(unix)] @@ -24,6 +23,10 @@ use uucore::backup_control::{self, BackupMode}; use fs_extra::dir::{move_dir, CopyOptions as DirCopyOptions}; +use crate::app::*; + +pub mod app; + pub struct Behavior { overwrite: OverwriteMode, backup: BackupMode, @@ -41,22 +44,7 @@ pub enum OverwriteMode { Force, } -static ABOUT: &str = "Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; -static LONG_HELP: &str = ""; - -static OPT_BACKUP: &str = "backup"; -static OPT_BACKUP_NO_ARG: &str = "b"; -static OPT_FORCE: &str = "force"; -static OPT_INTERACTIVE: &str = "interactive"; -static OPT_NO_CLOBBER: &str = "no-clobber"; -static OPT_STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; -static OPT_SUFFIX: &str = "suffix"; -static OPT_TARGET_DIRECTORY: &str = "target-directory"; -static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; -static OPT_UPDATE: &str = "update"; -static OPT_VERBOSE: &str = "verbose"; - -static ARG_FILES: &str = "files"; +const LONG_HELP: &str = ""; fn get_usage() -> String { format!( @@ -70,90 +58,14 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP)) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_BACKUP) - .long(OPT_BACKUP) - .help("make a backup of each existing destination file") - .takes_value(true) - .require_equals(true) - .min_values(0) - .possible_values(backup_control::BACKUP_CONTROL_VALUES) - .value_name("CONTROL") - ) - .arg( - Arg::with_name(OPT_BACKUP_NO_ARG) - .short(OPT_BACKUP_NO_ARG) - .help("like --backup but does not accept an argument") - ) - .arg( - Arg::with_name(OPT_FORCE) - .short("f") - .long(OPT_FORCE) - .help("do not prompt before overwriting") - ) - .arg( - Arg::with_name(OPT_INTERACTIVE) - .short("i") - .long(OPT_INTERACTIVE) - .help("prompt before override") - ) - .arg( - Arg::with_name(OPT_NO_CLOBBER).short("n") - .long(OPT_NO_CLOBBER) - .help("do not overwrite an existing file") - ) - .arg( - Arg::with_name(OPT_STRIP_TRAILING_SLASHES) - .long(OPT_STRIP_TRAILING_SLASHES) - .help("remove any trailing slashes from each SOURCE argument") - ) - .arg( - Arg::with_name(OPT_SUFFIX) - .short("S") - .long(OPT_SUFFIX) - .help("override the usual backup suffix") - .takes_value(true) - .value_name("SUFFIX") - ) - .arg( - Arg::with_name(OPT_TARGET_DIRECTORY) - .short("t") - .long(OPT_TARGET_DIRECTORY) - .help("move all SOURCE arguments into DIRECTORY") - .takes_value(true) - .value_name("DIRECTORY") - .conflicts_with(OPT_NO_TARGET_DIRECTORY) - ) - .arg( - Arg::with_name(OPT_NO_TARGET_DIRECTORY) - .short("T") - .long(OPT_NO_TARGET_DIRECTORY). - help("treat DEST as a normal file") - ) - .arg( - Arg::with_name(OPT_UPDATE) - .short("u") - .long(OPT_UPDATE) - .help("move only when the SOURCE file is newer than the destination file or when the destination file is missing") - ) - .arg( - Arg::with_name(OPT_VERBOSE) - .short("v") - .long(OPT_VERBOSE).help("explain what is being done") - ) - .arg( - Arg::with_name(ARG_FILES) - .multiple(true) - .takes_value(true) - .min_values(2) - .required(true) - ) - .get_matches_from(args); + .after_help(&*format!( + "{}\n{}", + LONG_HELP, + backup_control::BACKUP_CONTROL_LONG_HELP + )) + .get_matches_from(args); let files: Vec = matches .values_of(ARG_FILES) diff --git a/src/uu/nice/src/app.rs b/src/uu/nice/src/app.rs new file mode 100644 index 00000000000..14dd6c9e279 --- /dev/null +++ b/src/uu/nice/src/app.rs @@ -0,0 +1,21 @@ +use clap::{crate_version, App, AppSettings, Arg}; + +pub mod options { + pub static ADJUSTMENT: &str = "adjustment"; + pub static COMMAND: &str = "COMMAND"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .setting(AppSettings::TrailingVarArg) + .version(crate_version!()) + .arg( + Arg::with_name(options::ADJUSTMENT) + .short("n") + .long(options::ADJUSTMENT) + .help("add N to the niceness (default is 10)") + .takes_value(true) + .allow_hyphen_values(true), + ) + .arg(Arg::with_name(options::COMMAND).multiple(true)) +} diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 77baad0ca78..70ca1f80377 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -15,7 +15,9 @@ use std::ffi::CString; use std::io::Error; use std::ptr; -use clap::{crate_version, App, AppSettings, Arg}; +use crate::app::{get_app, options}; + +pub mod app; // XXX: PRIO_PROCESS is 0 on at least FreeBSD and Linux. Don't know about Mac OS X. const PRIO_PROCESS: c_int = 0; @@ -25,11 +27,6 @@ extern "C" { fn setpriority(which: c_int, who: c_int, prio: c_int) -> c_int; } -pub mod options { - pub static ADJUSTMENT: &str = "adjustment"; - pub static COMMAND: &str = "COMMAND"; -} - fn get_usage() -> String { format!( " @@ -46,19 +43,8 @@ process).", pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .setting(AppSettings::TrailingVarArg) - .version(crate_version!()) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(options::ADJUSTMENT) - .short("n") - .long(options::ADJUSTMENT) - .help("add N to the niceness (default is 10)") - .takes_value(true) - .allow_hyphen_values(true), - ) - .arg(Arg::with_name(options::COMMAND).multiple(true)) .get_matches_from(args); let mut niceness = unsafe { diff --git a/src/uu/nl/src/app.rs b/src/uu/nl/src/app.rs new file mode 100644 index 00000000000..82ed07a98d2 --- /dev/null +++ b/src/uu/nl/src/app.rs @@ -0,0 +1,101 @@ +use clap::{crate_version, App, Arg}; + +const USAGE: &str = "nl [OPTION]... [FILE]..."; + +pub mod options { + pub const FILE: &str = "file"; + pub const BODY_NUMBERING: &str = "body-numbering"; + pub const SECTION_DELIMITER: &str = "section-delimiter"; + pub const FOOTER_NUMBERING: &str = "footer-numbering"; + pub const HEADER_NUMBERING: &str = "header-numbering"; + pub const LINE_INCREMENT: &str = "line-increment"; + pub const JOIN_BLANK_LINES: &str = "join-blank-lines"; + pub const NUMBER_FORMAT: &str = "number-format"; + pub const NO_RENUMBER: &str = "no-renumber"; + pub const NUMBER_SEPARATOR: &str = "number-separator"; + pub const STARTING_LINE_NUMBER: &str = "starting-line-number"; + pub const NUMBER_WIDTH: &str = "number-width"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .usage(USAGE) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .arg( + Arg::with_name(options::BODY_NUMBERING) + .short("b") + .long(options::BODY_NUMBERING) + .help("use STYLE for numbering body lines") + .value_name("SYNTAX"), + ) + .arg( + Arg::with_name(options::SECTION_DELIMITER) + .short("d") + .long(options::SECTION_DELIMITER) + .help("use CC for separating logical pages") + .value_name("CC"), + ) + .arg( + Arg::with_name(options::FOOTER_NUMBERING) + .short("f") + .long(options::FOOTER_NUMBERING) + .help("use STYLE for numbering footer lines") + .value_name("STYLE"), + ) + .arg( + Arg::with_name(options::HEADER_NUMBERING) + .short("h") + .long(options::HEADER_NUMBERING) + .help("use STYLE for numbering header lines") + .value_name("STYLE"), + ) + .arg( + Arg::with_name(options::LINE_INCREMENT) + .short("i") + .long(options::LINE_INCREMENT) + .help("line number increment at each line") + .value_name("NUMBER"), + ) + .arg( + Arg::with_name(options::JOIN_BLANK_LINES) + .short("l") + .long(options::JOIN_BLANK_LINES) + .help("group of NUMBER empty lines counted as one") + .value_name("NUMBER"), + ) + .arg( + Arg::with_name(options::NUMBER_FORMAT) + .short("n") + .long(options::NUMBER_FORMAT) + .help("insert line numbers according to FORMAT") + .value_name("FORMAT"), + ) + .arg( + Arg::with_name(options::NO_RENUMBER) + .short("p") + .long(options::NO_RENUMBER) + .help("do not reset line numbers at logical pages"), + ) + .arg( + Arg::with_name(options::NUMBER_SEPARATOR) + .short("s") + .long(options::NUMBER_SEPARATOR) + .help("add STRING after (possible) line number") + .value_name("STRING"), + ) + .arg( + Arg::with_name(options::STARTING_LINE_NUMBER) + .short("v") + .long(options::STARTING_LINE_NUMBER) + .help("first line number on each logical page") + .value_name("NUMBER"), + ) + .arg( + Arg::with_name(options::NUMBER_WIDTH) + .short("w") + .long(options::NUMBER_WIDTH) + .help("use NUMBER columns for line numbers") + .value_name("NUMBER"), + ) +} diff --git a/src/uu/nl/src/helper.rs b/src/uu/nl/src/helper.rs index 562cdeb9315..d2bff0d4ce2 100644 --- a/src/uu/nl/src/helper.rs +++ b/src/uu/nl/src/helper.rs @@ -1,6 +1,6 @@ // spell-checker:ignore (ToDO) conv -use crate::options; +use crate::app::options; // parse_style parses a style string into a NumberingStyle. fn parse_style(chars: &[char]) -> Result { diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index a3181e11f2e..293cb102e73 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -11,17 +11,17 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::iter::repeat; use std::path::Path; use uucore::InvalidEncodingHandling; +use crate::app::{get_app, options}; + +pub mod app; mod helper; -static NAME: &str = "nl"; -static USAGE: &str = "nl [OPTION]... [FILE]..."; // A regular expression matching everything. // Settings store options used by nl to produce its output. @@ -68,108 +68,12 @@ enum NumberFormat { RightZero, } -pub mod options { - pub const FILE: &str = "file"; - pub const BODY_NUMBERING: &str = "body-numbering"; - pub const SECTION_DELIMITER: &str = "section-delimiter"; - pub const FOOTER_NUMBERING: &str = "footer-numbering"; - pub const HEADER_NUMBERING: &str = "header-numbering"; - pub const LINE_INCREMENT: &str = "line-increment"; - pub const JOIN_BLANK_LINES: &str = "join-blank-lines"; - pub const NUMBER_FORMAT: &str = "number-format"; - pub const NO_RENUMBER: &str = "no-renumber"; - pub const NUMBER_SEPARATOR: &str = "number-separator"; - pub const STARTING_LINE_NUMBER: &str = "starting-line-number"; - pub const NUMBER_WIDTH: &str = "number-width"; -} - pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(USAGE) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .arg( - Arg::with_name(options::BODY_NUMBERING) - .short("b") - .long(options::BODY_NUMBERING) - .help("use STYLE for numbering body lines") - .value_name("SYNTAX"), - ) - .arg( - Arg::with_name(options::SECTION_DELIMITER) - .short("d") - .long(options::SECTION_DELIMITER) - .help("use CC for separating logical pages") - .value_name("CC"), - ) - .arg( - Arg::with_name(options::FOOTER_NUMBERING) - .short("f") - .long(options::FOOTER_NUMBERING) - .help("use STYLE for numbering footer lines") - .value_name("STYLE"), - ) - .arg( - Arg::with_name(options::HEADER_NUMBERING) - .short("h") - .long(options::HEADER_NUMBERING) - .help("use STYLE for numbering header lines") - .value_name("STYLE"), - ) - .arg( - Arg::with_name(options::LINE_INCREMENT) - .short("i") - .long(options::LINE_INCREMENT) - .help("line number increment at each line") - .value_name("NUMBER"), - ) - .arg( - Arg::with_name(options::JOIN_BLANK_LINES) - .short("l") - .long(options::JOIN_BLANK_LINES) - .help("group of NUMBER empty lines counted as one") - .value_name("NUMBER"), - ) - .arg( - Arg::with_name(options::NUMBER_FORMAT) - .short("n") - .long(options::NUMBER_FORMAT) - .help("insert line numbers according to FORMAT") - .value_name("FORMAT"), - ) - .arg( - Arg::with_name(options::NO_RENUMBER) - .short("p") - .long(options::NO_RENUMBER) - .help("do not reset line numbers at logical pages"), - ) - .arg( - Arg::with_name(options::NUMBER_SEPARATOR) - .short("s") - .long(options::NUMBER_SEPARATOR) - .help("add STRING after (possible) line number") - .value_name("STRING"), - ) - .arg( - Arg::with_name(options::STARTING_LINE_NUMBER) - .short("v") - .long(options::STARTING_LINE_NUMBER) - .help("first line number on each logical page") - .value_name("NUMBER"), - ) - .arg( - Arg::with_name(options::NUMBER_WIDTH) - .short("w") - .long(options::NUMBER_WIDTH) - .help("use NUMBER columns for line numbers") - .value_name("NUMBER"), - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); // A mutable settings object, initialized with the defaults. let mut settings = Settings { diff --git a/src/uu/nohup/src/app.rs b/src/uu/nohup/src/app.rs new file mode 100644 index 00000000000..542d9b73812 --- /dev/null +++ b/src/uu/nohup/src/app.rs @@ -0,0 +1,27 @@ +use clap::{crate_version, App, AppSettings, Arg}; + +const ABOUT: &str = "Run COMMAND ignoring hangup signals."; +const LONG_HELP: &str = " +If standard input is terminal, it'll be replaced with /dev/null. +If standard output is terminal, it'll be appended to nohup.out instead, +or $HOME/nohup.out, if nohup.out open failed. +If standard error is terminal, it'll be redirected to stdout. +"; + +pub mod options { + pub const CMD: &str = "cmd"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::CMD) + .hidden(true) + .required(true) + .multiple(true), + ) + .setting(AppSettings::TrailingVarArg) +} diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 4e6fd7a7e43..e1b6978baa9 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -10,7 +10,6 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, AppSettings, Arg}; use libc::{c_char, dup2, execvp, signal}; use libc::{SIGHUP, SIG_IGN}; use std::env; @@ -21,13 +20,10 @@ use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; use uucore::InvalidEncodingHandling; -static ABOUT: &str = "Run COMMAND ignoring hangup signals."; -static LONG_HELP: &str = " -If standard input is terminal, it'll be replaced with /dev/null. -If standard output is terminal, it'll be appended to nohup.out instead, -or $HOME/nohup.out, if nohup.out open failed. -If standard error is terminal, it'll be redirected to stdout. -"; +use crate::app::{get_app, options}; + +pub mod app; + static NOHUP_OUT: &str = "nohup.out"; // exit codes that match the GNU implementation static EXIT_CANCELED: i32 = 125; @@ -35,28 +31,14 @@ static EXIT_CANNOT_INVOKE: i32 = 126; static EXIT_ENOENT: i32 = 127; static POSIX_NOHUP_FAILURE: i32 = 127; -mod options { - pub const CMD: &str = "cmd"; -} - pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::CMD) - .hidden(true) - .required(true) - .multiple(true), - ) - .setting(AppSettings::TrailingVarArg) .get_matches_from(args); replace_fds(); diff --git a/src/uu/nproc/src/app.rs b/src/uu/nproc/src/app.rs new file mode 100644 index 00000000000..743793ea14a --- /dev/null +++ b/src/uu/nproc/src/app.rs @@ -0,0 +1,25 @@ +use clap::{crate_version, App, Arg}; + +pub const OPT_ALL: &str = "all"; +pub const OPT_IGNORE: &str = "ignore"; + +const ABOUT: &str = "Print the number of cores available to the current process."; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_ALL) + .short("") + .long(OPT_ALL) + .help("print the number of cores available to the system"), + ) + .arg( + Arg::with_name(OPT_IGNORE) + .short("") + .long(OPT_IGNORE) + .takes_value(true) + .help("ignore up to N cores"), + ) +} diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index 13f1862d2db..394856f73f9 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -10,9 +10,12 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::env; +use crate::app::*; + +pub mod app; + #[cfg(target_os = "linux")] pub const _SC_NPROCESSORS_CONF: libc::c_int = 83; #[cfg(target_vendor = "apple")] @@ -22,34 +25,14 @@ pub const _SC_NPROCESSORS_CONF: libc::c_int = 57; #[cfg(target_os = "netbsd")] pub const _SC_NPROCESSORS_CONF: libc::c_int = 1001; -static OPT_ALL: &str = "all"; -static OPT_IGNORE: &str = "ignore"; - -static ABOUT: &str = "Print the number of cores available to the current process."; - fn get_usage() -> String { format!("{0} [OPTIONS]...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_ALL) - .short("") - .long(OPT_ALL) - .help("print the number of cores available to the system"), - ) - .arg( - Arg::with_name(OPT_IGNORE) - .short("") - .long(OPT_IGNORE) - .takes_value(true) - .help("ignore up to N cores"), - ) .get_matches_from(args); let mut ignore = match matches.value_of(OPT_IGNORE) { diff --git a/src/uu/numfmt/src/app.rs b/src/uu/numfmt/src/app.rs new file mode 100644 index 00000000000..8383d84b4f3 --- /dev/null +++ b/src/uu/numfmt/src/app.rs @@ -0,0 +1,101 @@ +use clap::{crate_version, App, AppSettings, Arg}; + +const ABOUT: &str = "Convert numbers from/to human-readable strings"; +const LONG_HELP: &str = "UNIT options: + none no auto-scaling is done; suffixes will trigger an error + + auto accept optional single/two letter suffix: + + 1K = 1000, 1Ki = 1024, 1M = 1000000, 1Mi = 1048576, + + si accept optional single letter suffix: + + 1K = 1000, 1M = 1000000, ... + + iec accept optional single letter suffix: + + 1K = 1024, 1M = 1048576, ... + + iec-i accept optional two-letter suffix: + + 1Ki = 1024, 1Mi = 1048576, ... + +FIELDS supports cut(1) style field ranges: + N N'th field, counted from 1 + N- from N'th field, to end of line + N-M from N'th to M'th field (inclusive) + -M from first to M'th field (inclusive) + - all fields +Multiple fields/ranges can be separated with commas +"; + +pub const DELIMITER: &str = "delimiter"; +pub const FIELD: &str = "field"; +pub const FIELD_DEFAULT: &str = "1"; +pub const FROM: &str = "from"; +pub const FROM_DEFAULT: &str = "none"; +pub const HEADER: &str = "header"; +pub const HEADER_DEFAULT: &str = "1"; +pub const NUMBER: &str = "NUMBER"; +pub const PADDING: &str = "padding"; +pub const TO: &str = "to"; +pub const TO_DEFAULT: &str = "none"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .after_help(LONG_HELP) + .setting(AppSettings::AllowNegativeNumbers) + .arg( + Arg::with_name(DELIMITER) + .short("d") + .long(DELIMITER) + .value_name("X") + .help("use X instead of whitespace for field delimiter"), + ) + .arg( + Arg::with_name(FIELD) + .long(FIELD) + .help("replace the numbers in these input fields (default=1) see FIELDS below") + .value_name("FIELDS") + .default_value(FIELD_DEFAULT), + ) + .arg( + Arg::with_name(FROM) + .long(FROM) + .help("auto-scale input numbers to UNITs; see UNIT below") + .value_name("UNIT") + .default_value(FROM_DEFAULT), + ) + .arg( + Arg::with_name(TO) + .long(TO) + .help("auto-scale output numbers to UNITs; see UNIT below") + .value_name("UNIT") + .default_value(TO_DEFAULT), + ) + .arg( + Arg::with_name(PADDING) + .long(PADDING) + .help( + "pad the output to N characters; positive N will \ + right-align; negative N will left-align; padding is \ + ignored if the output is wider than N; the default is \ + to automatically pad if a whitespace is found", + ) + .value_name("N"), + ) + .arg( + Arg::with_name(HEADER) + .long(HEADER) + .help( + "print (without converting) the first N header lines; \ + N defaults to 1 if not specified", + ) + .value_name("N") + .default_value(HEADER_DEFAULT) + .hide_default_value(true), + ) + .arg(Arg::with_name(NUMBER).hidden(true).multiple(true)) +} diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 08633643722..af9449b061e 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -8,46 +8,20 @@ #[macro_use] extern crate uucore; +use crate::app::*; use crate::format::format_and_print; -use crate::options::*; +use crate::options::TransformOptions; use crate::units::{Result, Transform, Unit}; -use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; +use clap::ArgMatches; +use options::NumfmtOptions; use std::io::{BufRead, Write}; use uucore::ranges::Range; +pub mod app; pub mod format; mod options; mod units; -static ABOUT: &str = "Convert numbers from/to human-readable strings"; -static LONG_HELP: &str = "UNIT options: - none no auto-scaling is done; suffixes will trigger an error - - auto accept optional single/two letter suffix: - - 1K = 1000, 1Ki = 1024, 1M = 1000000, 1Mi = 1048576, - - si accept optional single letter suffix: - - 1K = 1000, 1M = 1000000, ... - - iec accept optional single letter suffix: - - 1K = 1024, 1M = 1048576, ... - - iec-i accept optional two-letter suffix: - - 1Ki = 1024, 1Mi = 1048576, ... - -FIELDS supports cut(1) style field ranges: - N N'th field, counted from 1 - N- from N'th field, to end of line - N-M from N'th to M'th field (inclusive) - -M from first to M'th field (inclusive) - - all fields -Multiple fields/ranges can be separated with commas -"; - fn get_usage() -> String { format!("{0} [OPTION]... [NUMBER]...", executable!()) } @@ -89,23 +63,23 @@ fn parse_unit(s: &str) -> Result { } fn parse_options(args: &ArgMatches) -> Result { - let from = parse_unit(args.value_of(options::FROM).unwrap())?; - let to = parse_unit(args.value_of(options::TO).unwrap())?; + let from = parse_unit(args.value_of(FROM).unwrap())?; + let to = parse_unit(args.value_of(TO).unwrap())?; let transform = TransformOptions { from: Transform { unit: from }, to: Transform { unit: to }, }; - let padding = match args.value_of(options::PADDING) { + let padding = match args.value_of(PADDING) { Some(s) => s.parse::().map_err(|err| err.to_string()), None => Ok(0), }?; - let header = match args.occurrences_of(options::HEADER) { + let header = match args.occurrences_of(HEADER) { 0 => Ok(0), _ => { - let value = args.value_of(options::HEADER).unwrap(); + let value = args.value_of(HEADER).unwrap(); value .parse::() @@ -118,7 +92,7 @@ fn parse_options(args: &ArgMatches) -> Result { } }?; - let fields = match args.value_of(options::FIELD) { + let fields = match args.value_of(FIELD) { Some("-") => vec![Range { low: 1, high: std::usize::MAX, @@ -127,7 +101,7 @@ fn parse_options(args: &ArgMatches) -> Result { None => unreachable!(), }; - let delimiter = args.value_of(options::DELIMITER).map_or(Ok(None), |arg| { + let delimiter = args.value_of(DELIMITER).map_or(Ok(None), |arg| { if arg.len() == 1 { Ok(Some(arg.to_string())) } else { @@ -147,70 +121,14 @@ fn parse_options(args: &ArgMatches) -> Result { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .after_help(LONG_HELP) - .setting(AppSettings::AllowNegativeNumbers) - .arg( - Arg::with_name(options::DELIMITER) - .short("d") - .long(options::DELIMITER) - .value_name("X") - .help("use X instead of whitespace for field delimiter"), - ) - .arg( - Arg::with_name(options::FIELD) - .long(options::FIELD) - .help("replace the numbers in these input fields (default=1) see FIELDS below") - .value_name("FIELDS") - .default_value(options::FIELD_DEFAULT), - ) - .arg( - Arg::with_name(options::FROM) - .long(options::FROM) - .help("auto-scale input numbers to UNITs; see UNIT below") - .value_name("UNIT") - .default_value(options::FROM_DEFAULT), - ) - .arg( - Arg::with_name(options::TO) - .long(options::TO) - .help("auto-scale output numbers to UNITs; see UNIT below") - .value_name("UNIT") - .default_value(options::TO_DEFAULT), - ) - .arg( - Arg::with_name(options::PADDING) - .long(options::PADDING) - .help( - "pad the output to N characters; positive N will \ - right-align; negative N will left-align; padding is \ - ignored if the output is wider than N; the default is \ - to automatically pad if a whitespace is found", - ) - .value_name("N"), - ) - .arg( - Arg::with_name(options::HEADER) - .long(options::HEADER) - .help( - "print (without converting) the first N header lines; \ - N defaults to 1 if not specified", - ) - .value_name("N") - .default_value(options::HEADER_DEFAULT) - .hide_default_value(true), - ) - .arg(Arg::with_name(options::NUMBER).hidden(true).multiple(true)) .get_matches_from(args); - let result = - parse_options(&matches).and_then(|options| match matches.values_of(options::NUMBER) { - Some(values) => handle_args(values, options), - None => handle_stdin(options), - }); + let result = parse_options(&matches).and_then(|options| match matches.values_of(NUMBER) { + Some(values) => handle_args(values, options), + None => handle_stdin(options), + }); match result { Err(e) => { diff --git a/src/uu/numfmt/src/options.rs b/src/uu/numfmt/src/options.rs index 17f0a6fbe26..165f0ca253d 100644 --- a/src/uu/numfmt/src/options.rs +++ b/src/uu/numfmt/src/options.rs @@ -1,18 +1,6 @@ use crate::units::Transform; use uucore::ranges::Range; -pub const DELIMITER: &str = "delimiter"; -pub const FIELD: &str = "field"; -pub const FIELD_DEFAULT: &str = "1"; -pub const FROM: &str = "from"; -pub const FROM_DEFAULT: &str = "none"; -pub const HEADER: &str = "header"; -pub const HEADER_DEFAULT: &str = "1"; -pub const NUMBER: &str = "NUMBER"; -pub const PADDING: &str = "padding"; -pub const TO: &str = "to"; -pub const TO_DEFAULT: &str = "none"; - pub struct TransformOptions { pub from: Transform, pub to: Transform, diff --git a/src/uu/od/src/app.rs b/src/uu/od/src/app.rs new file mode 100644 index 00000000000..390a50e519f --- /dev/null +++ b/src/uu/od/src/app.rs @@ -0,0 +1,286 @@ +// spell-checker:ignore (clap) DontDelimitTrailingValues +// spell-checker:ignore (ToDO) exitcode + +use clap::{App, AppSettings, Arg}; + +const ABOUT: &str = "dump files in octal and other formats"; + +const USAGE: &str = r#" + od [OPTION]... [--] [FILENAME]... + od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]] + od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]"#; + +const LONG_HELP: &str = r#" +Displays data in various human-readable formats. If multiple formats are +specified, the output will contain all formats in the order they appear on the +command line. Each format will be printed on a new line. Only the line +containing the first format will be prefixed with the offset. + +If no filename is specified, or it is "-", stdin will be used. After a "--", no +more options will be recognized. This allows for filenames starting with a "-". + +If a filename is a valid number which can be used as an offset in the second +form, you can force it to be recognized as a filename if you include an option +like "-j0", which is only valid in the first form. + +RADIX is one of o,d,x,n for octal, decimal, hexadecimal or none. + +BYTES is decimal by default, octal if prefixed with a "0", or hexadecimal if +prefixed with "0x". The suffixes b, KB, K, MB, M, GB, G, will multiply the +number with 512, 1000, 1024, 1000^2, 1024^2, 1000^3, 1024^3, 1000^2, 1024^2. + +OFFSET and LABEL are octal by default, hexadecimal if prefixed with "0x" or +decimal if a "." suffix is added. The "b" suffix will multiply with 512. + +TYPE contains one or more format specifications consisting of: + a for printable 7-bits ASCII + c for utf-8 characters or octal for undefined characters + d[SIZE] for signed decimal + f[SIZE] for floating point + o[SIZE] for octal + u[SIZE] for unsigned decimal + x[SIZE] for hexadecimal +SIZE is the number of bytes which can be the number 1, 2, 4, 8 or 16, + or C, I, S, L for 1, 2, 4, 8 bytes for integer types, + or F, D, L for 4, 8, 16 bytes for floating point. +Any type specification can have a "z" suffix, which will add a ASCII dump at + the end of the line. + +If an error occurred, a diagnostic message will be printed to stderr, and the +exitcode will be non-zero."#; + +pub(crate) mod options { + pub const ADDRESS_RADIX: &str = "address-radix"; + pub const SKIP_BYTES: &str = "skip-bytes"; + pub const READ_BYTES: &str = "read-bytes"; + pub const ENDIAN: &str = "endian"; + pub const STRINGS: &str = "strings"; + pub const FORMAT: &str = "format"; + pub const OUTPUT_DUPLICATES: &str = "output-duplicates"; + pub const TRADITIONAL: &str = "traditional"; + pub const WIDTH: &str = "width"; + pub const FILENAME: &str = "FILENAME"; +} +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(clap::crate_version!()) + .about(ABOUT) + .usage(USAGE) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::ADDRESS_RADIX) + .short("A") + .long(options::ADDRESS_RADIX) + .help("Select the base in which file offsets are printed.") + .value_name("RADIX"), + ) + .arg( + Arg::with_name(options::SKIP_BYTES) + .short("j") + .long(options::SKIP_BYTES) + .help("Skip bytes input bytes before formatting and writing.") + .value_name("BYTES"), + ) + .arg( + Arg::with_name(options::READ_BYTES) + .short("N") + .long(options::READ_BYTES) + .help("limit dump to BYTES input bytes") + .value_name("BYTES"), + ) + .arg( + Arg::with_name(options::ENDIAN) + .long(options::ENDIAN) + .help("byte order to use for multi-byte formats") + .possible_values(&["big", "little"]) + .value_name("big|little"), + ) + .arg( + Arg::with_name(options::STRINGS) + .short("S") + .long(options::STRINGS) + .help( + "NotImplemented: output strings of at least BYTES graphic chars. 3 is assumed when \ + BYTES is not specified.", + ) + .default_value("3") + .value_name("BYTES"), + ) + .arg( + Arg::with_name("a") + .short("a") + .help("named characters, ignoring high-order bit") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("b") + .short("b") + .help("octal bytes") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("c") + .short("c") + .help("ASCII characters or backslash escapes") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("d") + .short("d") + .help("unsigned decimal 2-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("D") + .short("D") + .help("unsigned decimal 4-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("o") + .short("o") + .help("octal 2-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("I") + .short("I") + .help("decimal 8-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("L") + .short("L") + .help("decimal 8-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("i") + .short("i") + .help("decimal 4-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("l") + .short("l") + .help("decimal 8-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("x") + .short("x") + .help("hexadecimal 2-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("h") + .short("h") + .help("hexadecimal 2-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("O") + .short("O") + .help("octal 4-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("s") + .short("s") + .help("decimal 2-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("X") + .short("X") + .help("hexadecimal 4-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("H") + .short("H") + .help("hexadecimal 4-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("e") + .short("e") + .help("floating point double precision (64-bit) units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("f") + .short("f") + .help("floating point double precision (32-bit) units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("F") + .short("F") + .help("floating point double precision (64-bit) units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name(options::FORMAT) + .short("t") + .long(options::FORMAT) + .help("select output format or formats") + .multiple(true) + .value_name("TYPE"), + ) + .arg( + Arg::with_name(options::OUTPUT_DUPLICATES) + .short("v") + .long(options::OUTPUT_DUPLICATES) + .help("do not use * to mark line suppression") + .takes_value(false) + .possible_values(&["big", "little"]), + ) + .arg( + Arg::with_name(options::WIDTH) + .short("w") + .long(options::WIDTH) + .help( + "output BYTES bytes per output line. 32 is implied when BYTES is not \ + specified.", + ) + .default_value("32") + .value_name("BYTES"), + ) + .arg( + Arg::with_name(options::TRADITIONAL) + .long(options::TRADITIONAL) + .help("compatibility mode with one input, offset and label.") + .takes_value(false), + ) + .arg( + Arg::with_name(options::FILENAME) + .hidden(true) + .multiple(true), + ) + .settings(&[ + AppSettings::TrailingVarArg, + AppSettings::DontDelimitTrailingValues, + AppSettings::DisableVersion, + AppSettings::DeriveDisplayOrder, + ]) +} diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index bf6c3901149..2a8ecff84dc 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -5,12 +5,12 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore (clap) DontDelimitTrailingValues // spell-checker:ignore (ToDO) formatteriteminfo inputdecoder inputoffset mockstream nrofbytes partialreader odfunc multifile exitcode #[macro_use] extern crate uucore; +pub mod app; mod byteorder_io; mod formatteriteminfo; mod inputdecoder; @@ -30,6 +30,8 @@ mod prn_int; use std::cmp; +use crate::app::get_app; +use crate::app::options; use crate::byteorder_io::*; use crate::formatteriteminfo::*; use crate::inputdecoder::{InputDecoder, MemoryDecoder}; @@ -42,69 +44,11 @@ use crate::parse_nrofbytes::parse_number_of_bytes; use crate::partialreader::*; use crate::peekreader::*; use crate::prn_char::format_ascii_dump; -use clap::{self, crate_version, AppSettings, Arg, ArgMatches}; +use clap::ArgMatches; use uucore::parse_size::ParseSizeError; use uucore::InvalidEncodingHandling; const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes -static ABOUT: &str = "dump files in octal and other formats"; - -static USAGE: &str = r#" - od [OPTION]... [--] [FILENAME]... - od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]] - od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]"#; - -static LONG_HELP: &str = r#" -Displays data in various human-readable formats. If multiple formats are -specified, the output will contain all formats in the order they appear on the -command line. Each format will be printed on a new line. Only the line -containing the first format will be prefixed with the offset. - -If no filename is specified, or it is "-", stdin will be used. After a "--", no -more options will be recognized. This allows for filenames starting with a "-". - -If a filename is a valid number which can be used as an offset in the second -form, you can force it to be recognized as a filename if you include an option -like "-j0", which is only valid in the first form. - -RADIX is one of o,d,x,n for octal, decimal, hexadecimal or none. - -BYTES is decimal by default, octal if prefixed with a "0", or hexadecimal if -prefixed with "0x". The suffixes b, KB, K, MB, M, GB, G, will multiply the -number with 512, 1000, 1024, 1000^2, 1024^2, 1000^3, 1024^3, 1000^2, 1024^2. - -OFFSET and LABEL are octal by default, hexadecimal if prefixed with "0x" or -decimal if a "." suffix is added. The "b" suffix will multiply with 512. - -TYPE contains one or more format specifications consisting of: - a for printable 7-bits ASCII - c for utf-8 characters or octal for undefined characters - d[SIZE] for signed decimal - f[SIZE] for floating point - o[SIZE] for octal - u[SIZE] for unsigned decimal - x[SIZE] for hexadecimal -SIZE is the number of bytes which can be the number 1, 2, 4, 8 or 16, - or C, I, S, L for 1, 2, 4, 8 bytes for integer types, - or F, D, L for 4, 8, 16 bytes for floating point. -Any type specification can have a "z" suffix, which will add a ASCII dump at - the end of the line. - -If an error occurred, a diagnostic message will be printed to stderr, and the -exitcode will be non-zero."#; - -pub(crate) mod options { - pub const ADDRESS_RADIX: &str = "address-radix"; - pub const SKIP_BYTES: &str = "skip-bytes"; - pub const READ_BYTES: &str = "read-bytes"; - pub const ENDIAN: &str = "endian"; - pub const STRINGS: &str = "strings"; - pub const FORMAT: &str = "format"; - pub const OUTPUT_DUPLICATES: &str = "output-duplicates"; - pub const TRADITIONAL: &str = "traditional"; - pub const WIDTH: &str = "width"; - pub const FILENAME: &str = "FILENAME"; -} struct OdOptions { byte_order: ByteOrder, @@ -214,227 +158,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let clap_opts = clap::App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(USAGE) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::ADDRESS_RADIX) - .short("A") - .long(options::ADDRESS_RADIX) - .help("Select the base in which file offsets are printed.") - .value_name("RADIX"), - ) - .arg( - Arg::with_name(options::SKIP_BYTES) - .short("j") - .long(options::SKIP_BYTES) - .help("Skip bytes input bytes before formatting and writing.") - .value_name("BYTES"), - ) - .arg( - Arg::with_name(options::READ_BYTES) - .short("N") - .long(options::READ_BYTES) - .help("limit dump to BYTES input bytes") - .value_name("BYTES"), - ) - .arg( - Arg::with_name(options::ENDIAN) - .long(options::ENDIAN) - .help("byte order to use for multi-byte formats") - .possible_values(&["big", "little"]) - .value_name("big|little"), - ) - .arg( - Arg::with_name(options::STRINGS) - .short("S") - .long(options::STRINGS) - .help( - "NotImplemented: output strings of at least BYTES graphic chars. 3 is assumed when \ - BYTES is not specified.", - ) - .default_value("3") - .value_name("BYTES"), - ) - .arg( - Arg::with_name("a") - .short("a") - .help("named characters, ignoring high-order bit") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("b") - .short("b") - .help("octal bytes") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("c") - .short("c") - .help("ASCII characters or backslash escapes") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("d") - .short("d") - .help("unsigned decimal 2-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("D") - .short("D") - .help("unsigned decimal 4-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("o") - .short("o") - .help("octal 2-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("I") - .short("I") - .help("decimal 8-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("L") - .short("L") - .help("decimal 8-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("i") - .short("i") - .help("decimal 4-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("l") - .short("l") - .help("decimal 8-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("x") - .short("x") - .help("hexadecimal 2-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("h") - .short("h") - .help("hexadecimal 2-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("O") - .short("O") - .help("octal 4-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("s") - .short("s") - .help("decimal 2-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("X") - .short("X") - .help("hexadecimal 4-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("H") - .short("H") - .help("hexadecimal 4-byte units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("e") - .short("e") - .help("floating point double precision (64-bit) units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("f") - .short("f") - .help("floating point double precision (32-bit) units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name("F") - .short("F") - .help("floating point double precision (64-bit) units") - .multiple(true) - .takes_value(false), - ) - .arg( - Arg::with_name(options::FORMAT) - .short("t") - .long(options::FORMAT) - .help("select output format or formats") - .multiple(true) - .value_name("TYPE"), - ) - .arg( - Arg::with_name(options::OUTPUT_DUPLICATES) - .short("v") - .long(options::OUTPUT_DUPLICATES) - .help("do not use * to mark line suppression") - .takes_value(false) - .possible_values(&["big", "little"]), - ) - .arg( - Arg::with_name(options::WIDTH) - .short("w") - .long(options::WIDTH) - .help( - "output BYTES bytes per output line. 32 is implied when BYTES is not \ - specified.", - ) - .default_value("32") - .value_name("BYTES"), - ) - .arg( - Arg::with_name(options::TRADITIONAL) - .long(options::TRADITIONAL) - .help("compatibility mode with one input, offset and label.") - .takes_value(false), - ) - .arg( - Arg::with_name(options::FILENAME) - .hidden(true) - .multiple(true), - ) - .settings(&[ - AppSettings::TrailingVarArg, - AppSettings::DontDelimitTrailingValues, - AppSettings::DisableVersion, - AppSettings::DeriveDisplayOrder, - ]); + let clap_opts = get_app(executable!()); let clap_matches = clap_opts .clone() // Clone to reuse clap_opts to print help diff --git a/src/uu/paste/src/app.rs b/src/uu/paste/src/app.rs new file mode 100644 index 00000000000..5902cd031ca --- /dev/null +++ b/src/uu/paste/src/app.rs @@ -0,0 +1,37 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Write lines consisting of the sequentially corresponding lines from each +FILE, separated by TABs, to standard output."; + +pub mod options { + pub const DELIMITER: &str = "delimiters"; + pub const SERIAL: &str = "serial"; + pub const FILE: &str = "file"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::SERIAL) + .long(options::SERIAL) + .short("s") + .help("paste one file at a time instead of in parallel"), + ) + .arg( + Arg::with_name(options::DELIMITER) + .long(options::DELIMITER) + .short("d") + .help("reuse characters from LIST instead of TABs") + .value_name("LIST") + .default_value("\t") + .hide_default_value(true), + ) + .arg( + Arg::with_name(options::FILE) + .value_name("FILE") + .multiple(true) + .default_value("-"), + ) +} diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index f2fa3c81c8b..90c4a06485c 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -10,20 +10,14 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::iter::repeat; use std::path::Path; -static ABOUT: &str = "Write lines consisting of the sequentially corresponding lines from each -FILE, separated by TABs, to standard output."; +use crate::app::{get_app, options}; -mod options { - pub const DELIMITER: &str = "delimiters"; - pub const SERIAL: &str = "serial"; - pub const FILE: &str = "file"; -} +pub mod app; // Wraps BufReader and stdin fn read_line( @@ -37,31 +31,7 @@ fn read_line( } pub fn uumain(args: impl uucore::Args) -> i32 { - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .arg( - Arg::with_name(options::SERIAL) - .long(options::SERIAL) - .short("s") - .help("paste one file at a time instead of in parallel"), - ) - .arg( - Arg::with_name(options::DELIMITER) - .long(options::DELIMITER) - .short("d") - .help("reuse characters from LIST instead of TABs") - .value_name("LIST") - .default_value("\t") - .hide_default_value(true), - ) - .arg( - Arg::with_name(options::FILE) - .value_name("FILE") - .multiple(true) - .default_value("-"), - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let serial = matches.is_present(options::SERIAL); let delimiters = matches.value_of(options::DELIMITER).unwrap().to_owned(); diff --git a/src/uu/pathchk/src/app.rs b/src/uu/pathchk/src/app.rs new file mode 100644 index 00000000000..1af2cfca896 --- /dev/null +++ b/src/uu/pathchk/src/app.rs @@ -0,0 +1,32 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Check whether file names are valid or portable"; + +pub mod options { + pub const POSIX: &str = "posix"; + pub const POSIX_SPECIAL: &str = "posix-special"; + pub const PORTABILITY: &str = "portability"; + pub const PATH: &str = "path"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::POSIX) + .short("p") + .help("check for most POSIX systems"), + ) + .arg( + Arg::with_name(options::POSIX_SPECIAL) + .short("P") + .help(r#"check for empty names and leading "-""#), + ) + .arg( + Arg::with_name(options::PORTABILITY) + .long(options::PORTABILITY) + .help("check for all POSIX systems (equivalent to -p -P)"), + ) + .arg(Arg::with_name(options::PATH).hidden(true).multiple(true)) +} diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 3588815090e..93cc5448e5d 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -12,11 +12,14 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::fs; use std::io::{ErrorKind, Write}; use uucore::InvalidEncodingHandling; +use crate::app::{get_app, options}; + +pub mod app; + // operating mode enum Mode { Default, // use filesystem to determine information and limits @@ -25,16 +28,6 @@ enum Mode { Both, // a combination of `Basic` and `Extra` } -static NAME: &str = "pathchk"; -static ABOUT: &str = "Check whether file names are valid or portable"; - -mod options { - pub const POSIX: &str = "posix"; - pub const POSIX_SPECIAL: &str = "posix-special"; - pub const PORTABILITY: &str = "portability"; - pub const PATH: &str = "path"; -} - // a few global constants as used in the GNU implementation const POSIX_PATH_MAX: usize = 256; const POSIX_NAME_MAX: usize = 14; @@ -49,26 +42,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(options::POSIX) - .short("p") - .help("check for most POSIX systems"), - ) - .arg( - Arg::with_name(options::POSIX_SPECIAL) - .short("P") - .help(r#"check for empty names and leading "-""#), - ) - .arg( - Arg::with_name(options::PORTABILITY) - .long(options::PORTABILITY) - .help("check for all POSIX systems (equivalent to -p -P)"), - ) - .arg(Arg::with_name(options::PATH).hidden(true).multiple(true)) .get_matches_from(args); // set working mode @@ -89,7 +64,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // take necessary actions let paths = matches.values_of(options::PATH); let mut res = if paths.is_none() { - show_error!("missing operand\nTry {} --help for more information", NAME); + show_error!( + "missing operand\nTry {} --help for more information", + executable!() + ); false } else { true diff --git a/src/uu/pinky/src/app.rs b/src/uu/pinky/src/app.rs new file mode 100644 index 00000000000..af9d729c25b --- /dev/null +++ b/src/uu/pinky/src/app.rs @@ -0,0 +1,73 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "pinky - lightweight finger"; + +pub 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"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::LONG_FORMAT) + .short("l") + .requires(options::USER) + .help("produce long format output for the specified USERs"), + ) + .arg( + Arg::with_name(options::OMIT_HOME_DIR) + .short("b") + .help("omit the user's home directory and shell in long format"), + ) + .arg( + Arg::with_name(options::OMIT_PROJECT_FILE) + .short("h") + .help("omit the user's project file in long format"), + ) + .arg( + Arg::with_name(options::OMIT_PLAN_FILE) + .short("p") + .help("omit the user's plan file in long format"), + ) + .arg( + Arg::with_name(options::SHORT_FORMAT) + .short("s") + .help("do short format output, this is the default"), + ) + .arg( + Arg::with_name(options::OMIT_HEADINGS) + .short("f") + .help("omit the line of column headings in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME) + .short("w") + .help("omit the user's full name in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME_HOST) + .short("i") + .help("omit the user's full name and remote host in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME_HOST_TIME) + .short("q") + .help("omit the user's full name, remote host and idle time in short format"), + ) + .arg( + Arg::with_name(options::USER) + .takes_value(true) + .multiple(true), + ) +} diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index d15730b3238..cb32b7f448a 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -19,26 +19,14 @@ use std::io::BufReader; use std::fs::File; use std::os::unix::fs::MetadataExt; -use clap::{crate_version, App, Arg}; use std::path::PathBuf; use uucore::InvalidEncodingHandling; -const BUFSIZE: usize = 1024; +use crate::app::{get_app, options}; -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"; -} +pub mod app; + +const BUFSIZE: usize = 1024; fn get_usage() -> String { format!("{0} [OPTION]... [USER]...", executable!()) @@ -60,62 +48,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) .after_help(&after_help[..]) - .arg( - Arg::with_name(options::LONG_FORMAT) - .short("l") - .requires(options::USER) - .help("produce long format output for the specified USERs"), - ) - .arg( - Arg::with_name(options::OMIT_HOME_DIR) - .short("b") - .help("omit the user's home directory and shell in long format"), - ) - .arg( - Arg::with_name(options::OMIT_PROJECT_FILE) - .short("h") - .help("omit the user's project file in long format"), - ) - .arg( - Arg::with_name(options::OMIT_PLAN_FILE) - .short("p") - .help("omit the user's plan file in long format"), - ) - .arg( - Arg::with_name(options::SHORT_FORMAT) - .short("s") - .help("do short format output, this is the default"), - ) - .arg( - Arg::with_name(options::OMIT_HEADINGS) - .short("f") - .help("omit the line of column headings in short format"), - ) - .arg( - Arg::with_name(options::OMIT_NAME) - .short("w") - .help("omit the user's full name in short format"), - ) - .arg( - Arg::with_name(options::OMIT_NAME_HOST) - .short("i") - .help("omit the user's full name and remote host in short format"), - ) - .arg( - Arg::with_name(options::OMIT_NAME_HOST_TIME) - .short("q") - .help("omit the user's full name, remote host and idle time in short format"), - ) - .arg( - Arg::with_name(options::USER) - .takes_value(true) - .multiple(true), - ) .get_matches_from(args); let users: Vec = matches diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index 6d9ec230406..de519161ad9 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/pr.rs" [dependencies] +clap = "2.33.3" uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["utmpx", "entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } getopts = "0.2.21" diff --git a/src/uu/pr/src/app.rs b/src/uu/pr/src/app.rs new file mode 100644 index 00000000000..bf1195ac723 --- /dev/null +++ b/src/uu/pr/src/app.rs @@ -0,0 +1,6 @@ +use clap::App; + +pub fn get_app(app_name: &str) -> App { + // TOOD: migrate to clap. + App::new(app_name) +} diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index 239a0970f9e..877c3d4f7b4 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -24,6 +24,8 @@ use std::io::{stdin, stdout, BufRead, BufReader, Lines, Read, Stdout, Write}; #[cfg(unix)] use std::os::unix::fs::FileTypeExt; +pub mod app; + type IOError = std::io::Error; const NAME: &str = "pr"; diff --git a/src/uu/printenv/src/app.rs b/src/uu/printenv/src/app.rs new file mode 100644 index 00000000000..66cfe41fb01 --- /dev/null +++ b/src/uu/printenv/src/app.rs @@ -0,0 +1,25 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Display the values of the specified environment VARIABLE(s), or (with no VARIABLE) display name and value pairs for them all."; + +pub const OPT_NULL: &str = "null"; + +pub const ARG_VARIABLES: &str = "variables"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_NULL) + .short("0") + .long(OPT_NULL) + .help("end each output line with 0 byte rather than newline"), + ) + .arg( + Arg::with_name(ARG_VARIABLES) + .multiple(true) + .takes_value(true) + .min_values(1), + ) +} diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 5c2594835fd..ac672a41ad3 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -10,14 +10,11 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::env; -static ABOUT: &str = "Display the values of the specified environment VARIABLE(s), or (with no VARIABLE) display name and value pairs for them all."; +use crate::app::{get_app, ARG_VARIABLES, OPT_NULL}; -static OPT_NULL: &str = "null"; - -static ARG_VARIABLES: &str = "variables"; +pub mod app; fn get_usage() -> String { format!("{0} [VARIABLE]... [OPTION]...", executable!()) @@ -25,23 +22,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_NULL) - .short("0") - .long(OPT_NULL) - .help("end each output line with 0 byte rather than newline"), - ) - .arg( - Arg::with_name(ARG_VARIABLES) - .multiple(true) - .takes_value(true) - .min_values(1), - ) .get_matches_from(args); let variables: Vec = matches diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index bc77d31bebc..3f7d63211d7 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -18,6 +18,7 @@ edition = "2018" path = "src/printf.rs" [dependencies] +clap = "2.33" itertools = "0.8.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/printf/src/app.rs b/src/uu/printf/src/app.rs new file mode 100644 index 00000000000..a469b2bf869 --- /dev/null +++ b/src/uu/printf/src/app.rs @@ -0,0 +1,10 @@ +use clap::{App, Arg}; + +const VERSION: &str = "version"; +const HELP: &str = "help"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .arg(Arg::with_name(VERSION).long(VERSION)) + .arg(Arg::with_name(HELP).long(HELP)) +} diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 88d18838de1..4352d8a6092 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -4,6 +4,7 @@ use uucore::InvalidEncodingHandling; +pub mod app; mod cli; mod memo; mod tokenize; diff --git a/src/uu/ptx/src/app.rs b/src/uu/ptx/src/app.rs new file mode 100644 index 00000000000..b572971e4f9 --- /dev/null +++ b/src/uu/ptx/src/app.rs @@ -0,0 +1,156 @@ +// spell-checker:ignore (ToDOs) Roff + +use clap::{crate_version, App, Arg}; + +const BRIEF: &str = "Usage: ptx [OPTION]... [INPUT]... (without -G) or: \ +ptx -G [OPTION]... [INPUT [OUTPUT]] \n Output a permuted index, \ +including context, of the words in the input files. \n\n Mandatory \ +arguments to long options are mandatory for short options too.\n +With no FILE, or when FILE is -, read standard input. \ +Default is '-F /'."; + +pub mod options { + pub const FILE: &str = "file"; + pub const AUTO_REFERENCE: &str = "auto-reference"; + pub const TRADITIONAL: &str = "traditional"; + pub const FLAG_TRUNCATION: &str = "flag-truncation"; + pub const MACRO_NAME: &str = "macro-name"; + pub const FORMAT_ROFF: &str = "format=roff"; + pub const RIGHT_SIDE_REFS: &str = "right-side-refs"; + pub const SENTENCE_REGEXP: &str = "sentence-regexp"; + pub const FORMAT_TEX: &str = "format=tex"; + pub const WORD_REGEXP: &str = "word-regexp"; + pub const BREAK_FILE: &str = "break-file"; + pub const IGNORE_CASE: &str = "ignore-case"; + pub const GAP_SIZE: &str = "gap-size"; + pub const IGNORE_FILE: &str = "ignore-file"; + pub const ONLY_FILE: &str = "only-file"; + pub const REFERENCES: &str = "references"; + pub const WIDTH: &str = "width"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .usage(BRIEF) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .arg( + Arg::with_name(options::AUTO_REFERENCE) + .short("A") + .long(options::AUTO_REFERENCE) + .help("output automatically generated references") + .takes_value(false), + ) + .arg( + Arg::with_name(options::TRADITIONAL) + .short("G") + .long(options::TRADITIONAL) + .help("behave more like System V 'ptx'"), + ) + .arg( + Arg::with_name(options::FLAG_TRUNCATION) + .short("F") + .long(options::FLAG_TRUNCATION) + .help("use STRING for flagging line truncations") + .value_name("STRING") + .takes_value(true), + ) + .arg( + Arg::with_name(options::MACRO_NAME) + .short("M") + .long(options::MACRO_NAME) + .help("macro name to use instead of 'xx'") + .value_name("STRING") + .takes_value(true), + ) + .arg( + Arg::with_name(options::FORMAT_ROFF) + .short("O") + .long(options::FORMAT_ROFF) + .help("generate output as roff directives"), + ) + .arg( + Arg::with_name(options::RIGHT_SIDE_REFS) + .short("R") + .long(options::RIGHT_SIDE_REFS) + .help("put references at right, not counted in -w") + .takes_value(false), + ) + .arg( + Arg::with_name(options::SENTENCE_REGEXP) + .short("S") + .long(options::SENTENCE_REGEXP) + .help("for end of lines or end of sentences") + .value_name("REGEXP") + .takes_value(true), + ) + .arg( + Arg::with_name(options::FORMAT_TEX) + .short("T") + .long(options::FORMAT_TEX) + .help("generate output as TeX directives"), + ) + .arg( + Arg::with_name(options::WORD_REGEXP) + .short("W") + .long(options::WORD_REGEXP) + .help("use REGEXP to match each keyword") + .value_name("REGEXP") + .takes_value(true), + ) + .arg( + Arg::with_name(options::BREAK_FILE) + .short("b") + .long(options::BREAK_FILE) + .help("word break characters in this FILE") + .value_name("FILE") + .takes_value(true), + ) + .arg( + Arg::with_name(options::IGNORE_CASE) + .short("f") + .long(options::IGNORE_CASE) + .help("fold lower case to upper case for sorting") + .takes_value(false), + ) + .arg( + Arg::with_name(options::GAP_SIZE) + .short("g") + .long(options::GAP_SIZE) + .help("gap size in columns between output fields") + .value_name("NUMBER") + .takes_value(true), + ) + .arg( + Arg::with_name(options::IGNORE_FILE) + .short("i") + .long(options::IGNORE_FILE) + .help("read ignore word list from FILE") + .value_name("FILE") + .takes_value(true), + ) + .arg( + Arg::with_name(options::ONLY_FILE) + .short("o") + .long(options::ONLY_FILE) + .help("read only word list from this FILE") + .value_name("FILE") + .takes_value(true), + ) + .arg( + Arg::with_name(options::REFERENCES) + .short("r") + .long(options::REFERENCES) + .help("first field of each line is a reference") + .value_name("FILE") + .takes_value(false), + ) + .arg( + Arg::with_name(options::WIDTH) + .short("w") + .long(options::WIDTH) + .help("output width in columns, reference excluded") + .value_name("NUMBER") + .takes_value(true), + ) +} diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 31da8f05d1b..4285299205a 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -10,7 +10,6 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use regex::Regex; use std::cmp; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -19,13 +18,9 @@ use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use uucore::InvalidEncodingHandling; -static NAME: &str = "ptx"; -static BRIEF: &str = "Usage: ptx [OPTION]... [INPUT]... (without -G) or: \ - ptx -G [OPTION]... [INPUT [OUTPUT]] \n Output a permuted index, \ - including context, of the words in the input files. \n\n Mandatory \ - arguments to long options are mandatory for short options too.\n - With no FILE, or when FILE is -, read standard input. \ - Default is '-F /'."; +use crate::app::{get_app, options}; + +pub mod app; #[derive(Debug)] enum OutFormat { @@ -612,157 +607,13 @@ fn write_traditional_output( } } -mod options { - pub static FILE: &str = "file"; - pub static AUTO_REFERENCE: &str = "auto-reference"; - pub static TRADITIONAL: &str = "traditional"; - pub static FLAG_TRUNCATION: &str = "flag-truncation"; - pub static MACRO_NAME: &str = "macro-name"; - pub static FORMAT_ROFF: &str = "format=roff"; - pub static RIGHT_SIDE_REFS: &str = "right-side-refs"; - pub static SENTENCE_REGEXP: &str = "sentence-regexp"; - pub static FORMAT_TEX: &str = "format=tex"; - pub static WORD_REGEXP: &str = "word-regexp"; - pub static BREAK_FILE: &str = "break-file"; - pub static IGNORE_CASE: &str = "ignore-case"; - pub static GAP_SIZE: &str = "gap-size"; - pub static IGNORE_FILE: &str = "ignore-file"; - pub static ONLY_FILE: &str = "only-file"; - pub static REFERENCES: &str = "references"; - pub static WIDTH: &str = "width"; -} - pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); // let mut opts = Options::new(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(BRIEF) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .arg( - Arg::with_name(options::AUTO_REFERENCE) - .short("A") - .long(options::AUTO_REFERENCE) - .help("output automatically generated references") - .takes_value(false), - ) - .arg( - Arg::with_name(options::TRADITIONAL) - .short("G") - .long(options::TRADITIONAL) - .help("behave more like System V 'ptx'"), - ) - .arg( - Arg::with_name(options::FLAG_TRUNCATION) - .short("F") - .long(options::FLAG_TRUNCATION) - .help("use STRING for flagging line truncations") - .value_name("STRING") - .takes_value(true), - ) - .arg( - Arg::with_name(options::MACRO_NAME) - .short("M") - .long(options::MACRO_NAME) - .help("macro name to use instead of 'xx'") - .value_name("STRING") - .takes_value(true), - ) - .arg( - Arg::with_name(options::FORMAT_ROFF) - .short("O") - .long(options::FORMAT_ROFF) - .help("generate output as roff directives"), - ) - .arg( - Arg::with_name(options::RIGHT_SIDE_REFS) - .short("R") - .long(options::RIGHT_SIDE_REFS) - .help("put references at right, not counted in -w") - .takes_value(false), - ) - .arg( - Arg::with_name(options::SENTENCE_REGEXP) - .short("S") - .long(options::SENTENCE_REGEXP) - .help("for end of lines or end of sentences") - .value_name("REGEXP") - .takes_value(true), - ) - .arg( - Arg::with_name(options::FORMAT_TEX) - .short("T") - .long(options::FORMAT_TEX) - .help("generate output as TeX directives"), - ) - .arg( - Arg::with_name(options::WORD_REGEXP) - .short("W") - .long(options::WORD_REGEXP) - .help("use REGEXP to match each keyword") - .value_name("REGEXP") - .takes_value(true), - ) - .arg( - Arg::with_name(options::BREAK_FILE) - .short("b") - .long(options::BREAK_FILE) - .help("word break characters in this FILE") - .value_name("FILE") - .takes_value(true), - ) - .arg( - Arg::with_name(options::IGNORE_CASE) - .short("f") - .long(options::IGNORE_CASE) - .help("fold lower case to upper case for sorting") - .takes_value(false), - ) - .arg( - Arg::with_name(options::GAP_SIZE) - .short("g") - .long(options::GAP_SIZE) - .help("gap size in columns between output fields") - .value_name("NUMBER") - .takes_value(true), - ) - .arg( - Arg::with_name(options::IGNORE_FILE) - .short("i") - .long(options::IGNORE_FILE) - .help("read ignore word list from FILE") - .value_name("FILE") - .takes_value(true), - ) - .arg( - Arg::with_name(options::ONLY_FILE) - .short("o") - .long(options::ONLY_FILE) - .help("read only word list from this FILE") - .value_name("FILE") - .takes_value(true), - ) - .arg( - Arg::with_name(options::REFERENCES) - .short("r") - .long(options::REFERENCES) - .help("first field of each line is a reference") - .value_name("FILE") - .takes_value(false), - ) - .arg( - Arg::with_name(options::WIDTH) - .short("w") - .long(options::WIDTH) - .help("output width in columns, reference excluded") - .value_name("NUMBER") - .takes_value(true), - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let input_files: Vec = match &matches.values_of(options::FILE) { Some(v) => v.clone().map(|v| v.to_owned()).collect(), diff --git a/src/uu/pwd/src/app.rs b/src/uu/pwd/src/app.rs new file mode 100644 index 00000000000..d43f78f6a64 --- /dev/null +++ b/src/uu/pwd/src/app.rs @@ -0,0 +1,23 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Display the full filename of the current working directory."; +pub const OPT_LOGICAL: &str = "logical"; +pub const OPT_PHYSICAL: &str = "physical"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_LOGICAL) + .short("L") + .long(OPT_LOGICAL) + .help("use PWD from environment, even if it contains symlinks"), + ) + .arg( + Arg::with_name(OPT_PHYSICAL) + .short("P") + .long(OPT_PHYSICAL) + .help("avoid all symlinks"), + ) +} diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 9b4e5c60000..23d545722ad 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -8,14 +8,14 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::env; use std::io; use std::path::{Path, PathBuf}; -static ABOUT: &str = "Display the full filename of the current working directory."; -static OPT_LOGICAL: &str = "logical"; -static OPT_PHYSICAL: &str = "physical"; +use crate::app::get_app; +use crate::app::OPT_LOGICAL; + +pub mod app; pub fn absolute_path(path: &Path) -> io::Result { let path_buf = path.canonicalize()?; @@ -39,22 +39,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_LOGICAL) - .short("L") - .long(OPT_LOGICAL) - .help("use PWD from environment, even if it contains symlinks"), - ) - .arg( - Arg::with_name(OPT_PHYSICAL) - .short("P") - .long(OPT_PHYSICAL) - .help("avoid all symlinks"), - ) .get_matches_from(args); match env::current_dir() { diff --git a/src/uu/readlink/src/app.rs b/src/uu/readlink/src/app.rs new file mode 100644 index 00000000000..b3b03d74259 --- /dev/null +++ b/src/uu/readlink/src/app.rs @@ -0,0 +1,77 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Print value of a symbolic link or canonical file name."; +pub const OPT_CANONICALIZE: &str = "canonicalize"; +pub const OPT_CANONICALIZE_MISSING: &str = "canonicalize-missing"; +pub const OPT_CANONICALIZE_EXISTING: &str = "canonicalize-existing"; +pub const OPT_NO_NEWLINE: &str = "no-newline"; +pub const OPT_QUIET: &str = "quiet"; +pub const OPT_SILENT: &str = "silent"; +pub const OPT_VERBOSE: &str = "verbose"; +pub const OPT_ZERO: &str = "zero"; + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_CANONICALIZE) + .short("f") + .long(OPT_CANONICALIZE) + .help( + "canonicalize by following every symlink in every component of the \ + given name recursively; all but the last component must exist", + ), + ) + .arg( + Arg::with_name(OPT_CANONICALIZE_EXISTING) + .short("e") + .long("canonicalize-existing") + .help( + "canonicalize by following every symlink in every component of the \ + given name recursively, all components must exist", + ), + ) + .arg( + Arg::with_name(OPT_CANONICALIZE_MISSING) + .short("m") + .long(OPT_CANONICALIZE_MISSING) + .help( + "canonicalize by following every symlink in every component of the \ + given name recursively, without requirements on components existence", + ), + ) + .arg( + Arg::with_name(OPT_NO_NEWLINE) + .short("n") + .long(OPT_NO_NEWLINE) + .help("do not output the trailing delimiter"), + ) + .arg( + Arg::with_name(OPT_QUIET) + .short("q") + .long(OPT_QUIET) + .help("suppress most error messages"), + ) + .arg( + Arg::with_name(OPT_SILENT) + .short("s") + .long(OPT_SILENT) + .help("suppress most error messages"), + ) + .arg( + Arg::with_name(OPT_VERBOSE) + .short("v") + .long(OPT_VERBOSE) + .help("report error message"), + ) + .arg( + Arg::with_name(OPT_ZERO) + .short("z") + .long(OPT_ZERO) + .help("separate output with NUL rather than newline"), + ) + .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) +} diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 02e2863152d..e1413923a93 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -10,24 +10,14 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::fs; use std::io::{stdout, Write}; use std::path::{Path, PathBuf}; use uucore::fs::{canonicalize, CanonicalizeMode}; -const NAME: &str = "readlink"; -const ABOUT: &str = "Print value of a symbolic link or canonical file name."; -const OPT_CANONICALIZE: &str = "canonicalize"; -const OPT_CANONICALIZE_MISSING: &str = "canonicalize-missing"; -const OPT_CANONICALIZE_EXISTING: &str = "canonicalize-existing"; -const OPT_NO_NEWLINE: &str = "no-newline"; -const OPT_QUIET: &str = "quiet"; -const OPT_SILENT: &str = "silent"; -const OPT_VERBOSE: &str = "verbose"; -const OPT_ZERO: &str = "zero"; +use crate::app::*; -const ARG_FILES: &str = "files"; +pub mod app; fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) @@ -35,68 +25,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_CANONICALIZE) - .short("f") - .long(OPT_CANONICALIZE) - .help( - "canonicalize by following every symlink in every component of the \ - given name recursively; all but the last component must exist", - ), - ) - .arg( - Arg::with_name(OPT_CANONICALIZE_EXISTING) - .short("e") - .long("canonicalize-existing") - .help( - "canonicalize by following every symlink in every component of the \ - given name recursively, all components must exist", - ), - ) - .arg( - Arg::with_name(OPT_CANONICALIZE_MISSING) - .short("m") - .long(OPT_CANONICALIZE_MISSING) - .help( - "canonicalize by following every symlink in every component of the \ - given name recursively, without requirements on components existence", - ), - ) - .arg( - Arg::with_name(OPT_NO_NEWLINE) - .short("n") - .long(OPT_NO_NEWLINE) - .help("do not output the trailing delimiter"), - ) - .arg( - Arg::with_name(OPT_QUIET) - .short("q") - .long(OPT_QUIET) - .help("suppress most error messages"), - ) - .arg( - Arg::with_name(OPT_SILENT) - .short("s") - .long(OPT_SILENT) - .help("suppress most error messages"), - ) - .arg( - Arg::with_name(OPT_VERBOSE) - .short("v") - .long(OPT_VERBOSE) - .help("report error message"), - ) - .arg( - Arg::with_name(OPT_ZERO) - .short("z") - .long(OPT_ZERO) - .help("separate output with NUL rather than newline"), - ) - .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) .get_matches_from(args); let mut no_newline = matches.is_present(OPT_NO_NEWLINE); @@ -122,12 +52,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!( 1, "missing operand\nTry {} --help for more information", - NAME + executable!() ); } if no_newline && files.len() > 1 && !silent { - eprintln!("{}: ignoring --no-newline with multiple arguments", NAME); + eprintln!( + "{}: ignoring --no-newline with multiple arguments", + executable!() + ); no_newline = false; } @@ -138,7 +71,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(path) => show(&path, no_newline, use_zero), Err(err) => { if verbose { - eprintln!("{}: {}: errno {}", NAME, f, err.raw_os_error().unwrap()); + eprintln!( + "{}: {}: errno {}", + executable!(), + f, + err.raw_os_error().unwrap() + ); } return 1; } @@ -148,7 +86,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(path) => show(&path, no_newline, use_zero), Err(err) => { if verbose { - eprintln!("{}: {}: errno {:?}", NAME, f, err.raw_os_error().unwrap()); + eprintln!( + "{}: {}: errno {:?}", + executable!(), + f, + err.raw_os_error().unwrap() + ); } return 1; } diff --git a/src/uu/realpath/src/app.rs b/src/uu/realpath/src/app.rs new file mode 100644 index 00000000000..31eeb3ce114 --- /dev/null +++ b/src/uu/realpath/src/app.rs @@ -0,0 +1,40 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "print the resolved path"; + +pub const OPT_QUIET: &str = "quiet"; +pub const OPT_STRIP: &str = "strip"; +pub const OPT_ZERO: &str = "zero"; + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_QUIET) + .short("q") + .long(OPT_QUIET) + .help("Do not print warnings for invalid paths"), + ) + .arg( + Arg::with_name(OPT_STRIP) + .short("s") + .long(OPT_STRIP) + .help("Only strip '.' and '..' components, but don't resolve symbolic links"), + ) + .arg( + Arg::with_name(OPT_ZERO) + .short("z") + .long(OPT_ZERO) + .help("Separate output filenames with \\0 rather than newline"), + ) + .arg( + Arg::with_name(ARG_FILES) + .multiple(true) + .takes_value(true) + .required(true) + .min_values(1), + ) +} diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 1a96b7f8076..208c6ff821b 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -10,17 +10,12 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::path::{Path, PathBuf}; use uucore::fs::{canonicalize, CanonicalizeMode}; -static ABOUT: &str = "print the resolved path"; +use crate::app::*; -static OPT_QUIET: &str = "quiet"; -static OPT_STRIP: &str = "strip"; -static OPT_ZERO: &str = "zero"; - -static ARG_FILES: &str = "files"; +pub mod app; fn get_usage() -> String { format!("{0} [OPTION]... FILE...", executable!()) @@ -29,35 +24,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_QUIET) - .short("q") - .long(OPT_QUIET) - .help("Do not print warnings for invalid paths"), - ) - .arg( - Arg::with_name(OPT_STRIP) - .short("s") - .long(OPT_STRIP) - .help("Only strip '.' and '..' components, but don't resolve symbolic links"), - ) - .arg( - Arg::with_name(OPT_ZERO) - .short("z") - .long(OPT_ZERO) - .help("Separate output filenames with \\0 rather than newline"), - ) - .arg( - Arg::with_name(ARG_FILES) - .multiple(true) - .takes_value(true) - .required(true) - .min_values(1), - ) .get_matches_from(args); /* the list of files */ diff --git a/src/uu/relpath/src/app.rs b/src/uu/relpath/src/app.rs new file mode 100644 index 00000000000..9e99de015a1 --- /dev/null +++ b/src/uu/relpath/src/app.rs @@ -0,0 +1,31 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Convert TO destination to the relative path from the FROM dir. +If FROM path is omitted, current working dir will be used."; + +pub mod options { + pub const DIR: &str = "DIR"; + pub const TO: &str = "TO"; + pub const FROM: &str = "FROM"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::DIR) + .short("d") + .takes_value(true) + .help("If any of FROM and TO is not subpath of DIR, output absolute path instead of relative"), + ) + .arg( + Arg::with_name(options::TO) + .required(true) + .takes_value(true), + ) + .arg( + Arg::with_name(options::FROM) + .takes_value(true), + ) +} diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index a997e1c5fa2..476d4628cc0 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -10,20 +10,14 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::env; use std::path::{Path, PathBuf}; use uucore::fs::{canonicalize, CanonicalizeMode}; use uucore::InvalidEncodingHandling; -static ABOUT: &str = "Convert TO destination to the relative path from the FROM dir. -If FROM path is omitted, current working dir will be used."; +use crate::app::{get_app, options}; -mod options { - pub const DIR: &str = "DIR"; - pub const TO: &str = "TO"; - pub const FROM: &str = "FROM"; -} +pub mod app; fn get_usage() -> String { format!("{} [-d DIR] TO [FROM]", executable!()) @@ -35,25 +29,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .accept_any(); let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(options::DIR) - .short("d") - .takes_value(true) - .help("If any of FROM and TO is not subpath of DIR, output absolute path instead of relative"), - ) - .arg( - Arg::with_name(options::TO) - .required(true) - .takes_value(true), - ) - .arg( - Arg::with_name(options::FROM) - .takes_value(true), - ) .get_matches_from(args); let to = Path::new(matches.value_of(options::TO).unwrap()).to_path_buf(); // required diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index d84756fd387..8d168e6fe94 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -23,7 +23,6 @@ winapi = { version="0.3", features=[] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } - [[bin]] name = "rm" path = "src/main.rs" diff --git a/src/uu/rm/src/app.rs b/src/uu/rm/src/app.rs new file mode 100644 index 00000000000..6ccdcd7cbe3 --- /dev/null +++ b/src/uu/rm/src/app.rs @@ -0,0 +1,92 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Remove (unlink) the FILE(s)"; + +pub const OPT_DIR: &str = "dir"; +pub const OPT_INTERACTIVE: &str = "interactive"; +pub const OPT_FORCE: &str = "force"; +pub const OPT_NO_PRESERVE_ROOT: &str = "no-preserve-root"; +pub const OPT_ONE_FILE_SYSTEM: &str = "one-file-system"; +pub const OPT_PRESERVE_ROOT: &str = "preserve-root"; +pub const OPT_PROMPT: &str = "prompt"; +pub const OPT_PROMPT_MORE: &str = "prompt-more"; +pub const OPT_RECURSIVE: &str = "recursive"; +pub const OPT_RECURSIVE_R: &str = "recursive_R"; +pub const OPT_VERBOSE: &str = "verbose"; + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + + .arg( + Arg::with_name(OPT_FORCE) + .short("f") + .long(OPT_FORCE) + .multiple(true) + .help("ignore nonexistent files and arguments, never prompt") + ) + .arg( + Arg::with_name(OPT_PROMPT) + .short("i") + .long("prompt before every removal") + ) + .arg( + Arg::with_name(OPT_PROMPT_MORE) + .short("I") + .help("prompt once before removing more than three files, or when removing recursively. Less intrusive than -i, while still giving some protection against most mistakes") + ) + .arg( + Arg::with_name(OPT_INTERACTIVE) + .long(OPT_INTERACTIVE) + .help("prompt according to WHEN: never, once (-I), or always (-i). Without WHEN, prompts always") + .value_name("WHEN") + .takes_value(true) + ) + .arg( + Arg::with_name(OPT_ONE_FILE_SYSTEM) + .long(OPT_ONE_FILE_SYSTEM) + .help("when removing a hierarchy recursively, skip any directory that is on a file system different from that of the corresponding command line argument (NOT IMPLEMENTED)") + ) + .arg( + Arg::with_name(OPT_NO_PRESERVE_ROOT) + .long(OPT_NO_PRESERVE_ROOT) + .help("do not treat '/' specially") + ) + .arg( + Arg::with_name(OPT_PRESERVE_ROOT) + .long(OPT_PRESERVE_ROOT) + .help("do not remove '/' (default)") + ) + .arg( + Arg::with_name(OPT_RECURSIVE).short("r") + .long(OPT_RECURSIVE) + .help("remove directories and their contents recursively") + ) + .arg( + // To mimic GNU's behavior we also want the '-R' flag. However, using clap's + // alias method 'visible_alias("R")' would result in a long '--R' flag. + Arg::with_name(OPT_RECURSIVE_R).short("R") + .help("Equivalent to -r") + ) + .arg( + Arg::with_name(OPT_DIR) + .short("d") + .long(OPT_DIR) + .help("remove empty directories") + ) + .arg( + Arg::with_name(OPT_VERBOSE) + .short("v") + .long(OPT_VERBOSE) + .help("explain what is being done") + ) + .arg( + Arg::with_name(ARG_FILES) + .multiple(true) + .takes_value(true) + .min_values(1) + ) +} diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 40a24cea735..d61cce828a9 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -10,7 +10,6 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use remove_dir_all::remove_dir_all; use std::collections::VecDeque; use std::fs; @@ -19,6 +18,10 @@ use std::ops::BitOr; use std::path::{Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; +use crate::app::*; + +pub mod app; + #[derive(Eq, PartialEq, Clone, Copy)] enum InteractiveMode { None, @@ -37,21 +40,6 @@ struct Options { verbose: bool, } -static ABOUT: &str = "Remove (unlink) the FILE(s)"; -static OPT_DIR: &str = "dir"; -static OPT_INTERACTIVE: &str = "interactive"; -static OPT_FORCE: &str = "force"; -static OPT_NO_PRESERVE_ROOT: &str = "no-preserve-root"; -static OPT_ONE_FILE_SYSTEM: &str = "one-file-system"; -static OPT_PRESERVE_ROOT: &str = "preserve-root"; -static OPT_PROMPT: &str = "prompt"; -static OPT_PROMPT_MORE: &str = "prompt-more"; -static OPT_RECURSIVE: &str = "recursive"; -static OPT_RECURSIVE_R: &str = "recursive_R"; -static OPT_VERBOSE: &str = "verbose"; - -static ARG_FILES: &str = "files"; - fn get_usage() -> String { format!("{0} [OPTION]... FILE...", executable!()) } @@ -77,80 +65,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let long_usage = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) .after_help(&long_usage[..]) - - .arg( - Arg::with_name(OPT_FORCE) - .short("f") - .long(OPT_FORCE) - .multiple(true) - .help("ignore nonexistent files and arguments, never prompt") - ) - .arg( - Arg::with_name(OPT_PROMPT) - .short("i") - .long("prompt before every removal") - ) - .arg( - Arg::with_name(OPT_PROMPT_MORE) - .short("I") - .help("prompt once before removing more than three files, or when removing recursively. Less intrusive than -i, while still giving some protection against most mistakes") - ) - .arg( - Arg::with_name(OPT_INTERACTIVE) - .long(OPT_INTERACTIVE) - .help("prompt according to WHEN: never, once (-I), or always (-i). Without WHEN, prompts always") - .value_name("WHEN") - .takes_value(true) - ) - .arg( - Arg::with_name(OPT_ONE_FILE_SYSTEM) - .long(OPT_ONE_FILE_SYSTEM) - .help("when removing a hierarchy recursively, skip any directory that is on a file system different from that of the corresponding command line argument (NOT IMPLEMENTED)") - ) - .arg( - Arg::with_name(OPT_NO_PRESERVE_ROOT) - .long(OPT_NO_PRESERVE_ROOT) - .help("do not treat '/' specially") - ) - .arg( - Arg::with_name(OPT_PRESERVE_ROOT) - .long(OPT_PRESERVE_ROOT) - .help("do not remove '/' (default)") - ) - .arg( - Arg::with_name(OPT_RECURSIVE).short("r") - .long(OPT_RECURSIVE) - .help("remove directories and their contents recursively") - ) - .arg( - // To mimic GNU's behavior we also want the '-R' flag. However, using clap's - // alias method 'visible_alias("R")' would result in a long '--R' flag. - Arg::with_name(OPT_RECURSIVE_R).short("R") - .help("Equivalent to -r") - ) - .arg( - Arg::with_name(OPT_DIR) - .short("d") - .long(OPT_DIR) - .help("remove empty directories") - ) - .arg( - Arg::with_name(OPT_VERBOSE) - .short("v") - .long(OPT_VERBOSE) - .help("explain what is being done") - ) - .arg( - Arg::with_name(ARG_FILES) - .multiple(true) - .takes_value(true) - .min_values(1) - ) .get_matches_from(args); let files: Vec = matches diff --git a/src/uu/rmdir/src/app.rs b/src/uu/rmdir/src/app.rs new file mode 100644 index 00000000000..65482e8f1d9 --- /dev/null +++ b/src/uu/rmdir/src/app.rs @@ -0,0 +1,42 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Remove the DIRECTORY(ies), if they are empty."; + +pub const OPT_IGNORE_FAIL_NON_EMPTY: &str = "ignore-fail-on-non-empty"; +pub const OPT_PARENTS: &str = "parents"; +pub const OPT_VERBOSE: &str = "verbose"; + +pub const ARG_DIRS: &str = "dirs"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_IGNORE_FAIL_NON_EMPTY) + .long(OPT_IGNORE_FAIL_NON_EMPTY) + .help("ignore each failure that is solely because a directory is non-empty"), + ) + .arg( + Arg::with_name(OPT_PARENTS) + .short("p") + .long(OPT_PARENTS) + .help( + "remove DIRECTORY and its ancestors; e.g., + 'rmdir -p a/b/c' is similar to rmdir a/b/c a/b a", + ), + ) + .arg( + Arg::with_name(OPT_VERBOSE) + .short("v") + .long(OPT_VERBOSE) + .help("output a diagnostic for every directory processed"), + ) + .arg( + Arg::with_name(ARG_DIRS) + .multiple(true) + .takes_value(true) + .min_values(1) + .required(true), + ) +} diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index fc22cca09bb..c4814224eff 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -10,16 +10,12 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::fs; use std::path::Path; -static ABOUT: &str = "Remove the DIRECTORY(ies), if they are empty."; -static OPT_IGNORE_FAIL_NON_EMPTY: &str = "ignore-fail-on-non-empty"; -static OPT_PARENTS: &str = "parents"; -static OPT_VERBOSE: &str = "verbose"; +use crate::app::*; -static ARG_DIRS: &str = "dirs"; +pub mod app; #[cfg(unix)] static ENOTDIR: i32 = 20; @@ -33,37 +29,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_IGNORE_FAIL_NON_EMPTY) - .long(OPT_IGNORE_FAIL_NON_EMPTY) - .help("ignore each failure that is solely because a directory is non-empty"), - ) - .arg( - Arg::with_name(OPT_PARENTS) - .short("p") - .long(OPT_PARENTS) - .help( - "remove DIRECTORY and its ancestors; e.g., - 'rmdir -p a/b/c' is similar to rmdir a/b/c a/b a", - ), - ) - .arg( - Arg::with_name(OPT_VERBOSE) - .short("v") - .long(OPT_VERBOSE) - .help("output a diagnostic for every directory processed"), - ) - .arg( - Arg::with_name(ARG_DIRS) - .multiple(true) - .takes_value(true) - .min_values(1) - .required(true), - ) .get_matches_from(args); let dirs: Vec = matches diff --git a/src/uu/seq/src/app.rs b/src/uu/seq/src/app.rs new file mode 100644 index 00000000000..329d9346c07 --- /dev/null +++ b/src/uu/seq/src/app.rs @@ -0,0 +1,46 @@ +use clap::{crate_version, App, AppSettings, Arg}; + +const ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT."; + +pub const OPT_SEPARATOR: &str = "separator"; +pub const OPT_TERMINATOR: &str = "terminator"; +pub const OPT_WIDTHS: &str = "widths"; + +pub const ARG_NUMBERS: &str = "numbers"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .setting(AppSettings::AllowLeadingHyphen) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(OPT_SEPARATOR) + .short("s") + .long("separator") + .help("Separator character (defaults to \\n)") + .takes_value(true) + .number_of_values(1), + ) + .arg( + Arg::with_name(OPT_TERMINATOR) + .short("t") + .long("terminator") + .help("Terminator character (defaults to \\n)") + .takes_value(true) + .number_of_values(1), + ) + .arg( + Arg::with_name(OPT_WIDTHS) + .short("w") + .long("widths") + .help("Equalize widths of all numbers by padding with zeros"), + ) + .arg( + Arg::with_name(ARG_NUMBERS) + .multiple(true) + .takes_value(true) + .allow_hyphen_values(true) + .max_values(3) + .required(true), + ) +} diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 954d15f2f54..b4caab13cdd 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -6,7 +6,6 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, AppSettings, Arg}; use num_bigint::BigInt; use num_traits::One; use num_traits::Zero; @@ -15,12 +14,9 @@ use std::cmp; use std::io::{stdout, Write}; use std::str::FromStr; -static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT."; -static OPT_SEPARATOR: &str = "separator"; -static OPT_TERMINATOR: &str = "terminator"; -static OPT_WIDTHS: &str = "widths"; +use crate::app::*; -static ARG_NUMBERS: &str = "numbers"; +pub mod app; fn get_usage() -> String { format!( @@ -87,41 +83,8 @@ impl FromStr for Number { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .setting(AppSettings::AllowLeadingHyphen) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(OPT_SEPARATOR) - .short("s") - .long("separator") - .help("Separator character (defaults to \\n)") - .takes_value(true) - .number_of_values(1), - ) - .arg( - Arg::with_name(OPT_TERMINATOR) - .short("t") - .long("terminator") - .help("Terminator character (defaults to \\n)") - .takes_value(true) - .number_of_values(1), - ) - .arg( - Arg::with_name(OPT_WIDTHS) - .short("w") - .long("widths") - .help("Equalize widths of all numbers by padding with zeros"), - ) - .arg( - Arg::with_name(ARG_NUMBERS) - .multiple(true) - .takes_value(true) - .allow_hyphen_values(true) - .max_values(3) - .required(true), - ) .get_matches_from(args); let numbers = matches.values_of(ARG_NUMBERS).unwrap().collect::>(); diff --git a/src/uu/shred/src/app.rs b/src/uu/shred/src/app.rs new file mode 100644 index 00000000000..e4177b47289 --- /dev/null +++ b/src/uu/shred/src/app.rs @@ -0,0 +1,113 @@ +// spell-checker:ignore (words) writeback + +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Overwrite the specified FILE(s) repeatedly, in order to make it harder\n\ +for even very expensive hardware probing to recover the data. +"; + +const AFTER_HELP: &str = + "Delete FILE(s) if --remove (-u) is specified. The default is not to remove\n\ + the files because it is common to operate on device files like /dev/hda,\n\ + and those files usually should not be removed.\n\ + \n\ + CAUTION: Note that shred relies on a very important assumption:\n\ + that the file system overwrites data in place. This is the traditional\n\ + way to do things, but many modern file system designs do not satisfy this\n\ + assumption. The following are examples of file systems on which shred is\n\ + not effective, or is not guaranteed to be effective in all file system modes:\n\ + \n\ + * log-structured or journal file systems, such as those supplied with\n\ + AIX and Solaris (and JFS, ReiserFS, XFS, Ext3, etc.)\n\ + \n\ + * file systems that write redundant data and carry on even if some writes\n\ + fail, such as RAID-based file systems\n\ + \n\ + * file systems that make snapshots, such as Network Appliance's NFS server\n\ + \n\ + * file systems that cache in temporary locations, such as NFS\n\ + version 3 clients\n\ + \n\ + * compressed file systems\n\ + \n\ + In the case of ext3 file systems, the above disclaimer applies\n\ + and shred is thus of limited effectiveness) only in data=journal mode,\n\ + which journals file data in addition to just metadata. In both the\n\ + data=ordered (default) and data=writeback modes, shred works as usual.\n\ + Ext3 journal modes can be changed by adding the data=something option\n\ + to the mount options for a particular file system in the /etc/fstab file,\n\ + as documented in the mount man page (man mount).\n\ + \n\ + In addition, file system backups and remote mirrors may contain copies\n\ + of the file that cannot be removed, and that will allow a shredded file\n\ + to be recovered later.\n\ + "; + +pub mod options { + pub const FORCE: &str = "force"; + pub const FILE: &str = "file"; + pub const ITERATIONS: &str = "iterations"; + pub const SIZE: &str = "size"; + pub const REMOVE: &str = "remove"; + pub const VERBOSE: &str = "verbose"; + pub const EXACT: &str = "exact"; + pub const ZERO: &str = "zero"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .after_help(AFTER_HELP) + .arg( + Arg::with_name(options::FORCE) + .long(options::FORCE) + .short("f") + .help("change permissions to allow writing if necessary"), + ) + .arg( + Arg::with_name(options::ITERATIONS) + .long(options::ITERATIONS) + .short("n") + .help("overwrite N times instead of the default (3)") + .value_name("NUMBER") + .default_value("3"), + ) + .arg( + Arg::with_name(options::SIZE) + .long(options::SIZE) + .short("s") + .takes_value(true) + .value_name("N") + .help("shred this many bytes (suffixes like K, M, G accepted)"), + ) + .arg( + Arg::with_name(options::REMOVE) + .short("u") + .long(options::REMOVE) + .help("truncate and remove file after overwriting; See below"), + ) + .arg( + Arg::with_name(options::VERBOSE) + .long(options::VERBOSE) + .short("v") + .help("show progress"), + ) + .arg( + Arg::with_name(options::EXACT) + .long(options::EXACT) + .short("x") + .help( + "do not round file sizes up to the next full block;\n\ + this is the default for non-regular files", + ), + ) + .arg( + Arg::with_name(options::ZERO) + .long(options::ZERO) + .short("z") + .help("add a final overwrite with zeros to hide shredding"), + ) + // Positional arguments + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) +} diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 177143811c7..55edc0512d1 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -6,9 +6,8 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore (words) writeback wipesync +// spell-checker:ignore (words) wipesync -use clap::{crate_version, App, Arg}; use rand::{Rng, ThreadRng}; use std::cell::{Cell, RefCell}; use std::fs; @@ -19,6 +18,10 @@ use std::io::SeekFrom; use std::path::{Path, PathBuf}; use uucore::InvalidEncodingHandling; +use crate::app::{get_app, options}; + +pub mod app; + #[macro_use] extern crate uucore; @@ -209,62 +212,10 @@ impl<'a> BytesGenerator<'a> { } } -static ABOUT: &str = "Overwrite the specified FILE(s) repeatedly, in order to make it harder\n\ -for even very expensive hardware probing to recover the data. -"; - fn get_usage() -> String { format!("{} [OPTION]... FILE...", executable!()) } -static AFTER_HELP: &str = - "Delete FILE(s) if --remove (-u) is specified. The default is not to remove\n\ - the files because it is common to operate on device files like /dev/hda,\n\ - and those files usually should not be removed.\n\ - \n\ - CAUTION: Note that shred relies on a very important assumption:\n\ - that the file system overwrites data in place. This is the traditional\n\ - way to do things, but many modern file system designs do not satisfy this\n\ - assumption. The following are examples of file systems on which shred is\n\ - not effective, or is not guaranteed to be effective in all file system modes:\n\ - \n\ - * log-structured or journal file systems, such as those supplied with\n\ - AIX and Solaris (and JFS, ReiserFS, XFS, Ext3, etc.)\n\ - \n\ - * file systems that write redundant data and carry on even if some writes\n\ - fail, such as RAID-based file systems\n\ - \n\ - * file systems that make snapshots, such as Network Appliance's NFS server\n\ - \n\ - * file systems that cache in temporary locations, such as NFS\n\ - version 3 clients\n\ - \n\ - * compressed file systems\n\ - \n\ - In the case of ext3 file systems, the above disclaimer applies\n\ - and shred is thus of limited effectiveness) only in data=journal mode,\n\ - which journals file data in addition to just metadata. In both the\n\ - data=ordered (default) and data=writeback modes, shred works as usual.\n\ - Ext3 journal modes can be changed by adding the data=something option\n\ - to the mount options for a particular file system in the /etc/fstab file,\n\ - as documented in the mount man page (man mount).\n\ - \n\ - In addition, file system backups and remote mirrors may contain copies\n\ - of the file that cannot be removed, and that will allow a shredded file\n\ - to be recovered later.\n\ - "; - -pub mod options { - pub const FORCE: &str = "force"; - pub const FILE: &str = "file"; - pub const ITERATIONS: &str = "iterations"; - pub const SIZE: &str = "size"; - pub const REMOVE: &str = "remove"; - pub const VERBOSE: &str = "verbose"; - pub const EXACT: &str = "exact"; - pub const ZERO: &str = "zero"; -} - pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) @@ -272,62 +223,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let app = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .usage(&usage[..]) - .arg( - Arg::with_name(options::FORCE) - .long(options::FORCE) - .short("f") - .help("change permissions to allow writing if necessary"), - ) - .arg( - Arg::with_name(options::ITERATIONS) - .long(options::ITERATIONS) - .short("n") - .help("overwrite N times instead of the default (3)") - .value_name("NUMBER") - .default_value("3"), - ) - .arg( - Arg::with_name(options::SIZE) - .long(options::SIZE) - .short("s") - .takes_value(true) - .value_name("N") - .help("shred this many bytes (suffixes like K, M, G accepted)"), - ) - .arg( - Arg::with_name(options::REMOVE) - .short("u") - .long(options::REMOVE) - .help("truncate and remove file after overwriting; See below"), - ) - .arg( - Arg::with_name(options::VERBOSE) - .long(options::VERBOSE) - .short("v") - .help("show progress"), - ) - .arg( - Arg::with_name(options::EXACT) - .long(options::EXACT) - .short("x") - .help( - "do not round file sizes up to the next full block;\n\ - this is the default for non-regular files", - ), - ) - .arg( - Arg::with_name(options::ZERO) - .long(options::ZERO) - .short("z") - .help("add a final overwrite with zeros to hide shredding"), - ) - // Positional arguments - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)); + let app = get_app(executable!()).usage(&usage[..]); let matches = app.get_matches_from(args); diff --git a/src/uu/shuf/src/app.rs b/src/uu/shuf/src/app.rs new file mode 100644 index 00000000000..490833bce2d --- /dev/null +++ b/src/uu/shuf/src/app.rs @@ -0,0 +1,85 @@ +use clap::{crate_version, App, Arg}; + +pub mod options { + pub const ECHO: &str = "echo"; + pub const INPUT_RANGE: &str = "input-range"; + pub const HEAD_COUNT: &str = "head-count"; + pub const OUTPUT: &str = "output"; + pub const RANDOM_SOURCE: &str = "random-source"; + pub const REPEAT: &str = "repeat"; + pub const ZERO_TERMINATED: &str = "zero-terminated"; + pub const FILE: &str = "file"; +} + +const USAGE: &str = r#"shuf [OPTION]... [FILE] + or: shuf -e [OPTION]... [ARG]... + or: shuf -i LO-HI [OPTION]... +Write a random permutation of the input lines to standard output. + +With no FILE, or when FILE is -, read standard input. +"#; +const TEMPLATE: &str = "Usage: {usage}\nMandatory arguments to long options are mandatory for short options too.\n{unified}"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .template(TEMPLATE) + .usage(USAGE) + .arg( + Arg::with_name(options::ECHO) + .short("e") + .long(options::ECHO) + .takes_value(true) + .value_name("ARG") + .help("treat each ARG as an input line") + .multiple(true) + .use_delimiter(false) + .min_values(0) + .conflicts_with(options::INPUT_RANGE), + ) + .arg( + Arg::with_name(options::INPUT_RANGE) + .short("i") + .long(options::INPUT_RANGE) + .takes_value(true) + .value_name("LO-HI") + .help("treat each number LO through HI as an input line") + .conflicts_with(options::FILE), + ) + .arg( + Arg::with_name(options::HEAD_COUNT) + .short("n") + .long(options::HEAD_COUNT) + .takes_value(true) + .value_name("COUNT") + .help("output at most COUNT lines"), + ) + .arg( + Arg::with_name(options::OUTPUT) + .short("o") + .long(options::OUTPUT) + .takes_value(true) + .value_name("FILE") + .help("write result to FILE instead of standard output"), + ) + .arg( + Arg::with_name(options::RANDOM_SOURCE) + .long(options::RANDOM_SOURCE) + .takes_value(true) + .value_name("FILE") + .help("get random bytes from FILE"), + ) + .arg( + Arg::with_name(options::REPEAT) + .short("r") + .long(options::REPEAT) + .help("output lines can be repeated"), + ) + .arg( + Arg::with_name(options::ZERO_TERMINATED) + .short("z") + .long(options::ZERO_TERMINATED) + .help("line delimiter is NUL, not newline"), + ) + .arg(Arg::with_name(options::FILE).takes_value(true)) +} diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 2d1f558de5b..78a94b5945a 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -10,28 +10,21 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use rand::Rng; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use uucore::InvalidEncodingHandling; +use crate::app::{get_app, options}; + +pub mod app; + enum Mode { Default(String), Echo(Vec), InputRange((usize, usize)), } -static NAME: &str = "shuf"; -static USAGE: &str = r#"shuf [OPTION]... [FILE] - or: shuf -e [OPTION]... [ARG]... - or: shuf -i LO-HI [OPTION]... -Write a random permutation of the input lines to standard output. - -With no FILE, or when FILE is -, read standard input. -"#; -static TEMPLATE: &str = "Usage: {usage}\nMandatory arguments to long options are mandatory for short options too.\n{unified}"; - struct Options { head_count: usize, output: Option, @@ -40,85 +33,12 @@ struct Options { sep: u8, } -mod options { - pub static ECHO: &str = "echo"; - pub static INPUT_RANGE: &str = "input-range"; - pub static HEAD_COUNT: &str = "head-count"; - pub static OUTPUT: &str = "output"; - pub static RANDOM_SOURCE: &str = "random-source"; - pub static REPEAT: &str = "repeat"; - pub static ZERO_TERMINATED: &str = "zero-terminated"; - pub static FILE: &str = "file"; -} - pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .template(TEMPLATE) - .usage(USAGE) - .arg( - Arg::with_name(options::ECHO) - .short("e") - .long(options::ECHO) - .takes_value(true) - .value_name("ARG") - .help("treat each ARG as an input line") - .multiple(true) - .use_delimiter(false) - .min_values(0) - .conflicts_with(options::INPUT_RANGE), - ) - .arg( - Arg::with_name(options::INPUT_RANGE) - .short("i") - .long(options::INPUT_RANGE) - .takes_value(true) - .value_name("LO-HI") - .help("treat each number LO through HI as an input line") - .conflicts_with(options::FILE), - ) - .arg( - Arg::with_name(options::HEAD_COUNT) - .short("n") - .long(options::HEAD_COUNT) - .takes_value(true) - .value_name("COUNT") - .help("output at most COUNT lines"), - ) - .arg( - Arg::with_name(options::OUTPUT) - .short("o") - .long(options::OUTPUT) - .takes_value(true) - .value_name("FILE") - .help("write result to FILE instead of standard output"), - ) - .arg( - Arg::with_name(options::RANDOM_SOURCE) - .long(options::RANDOM_SOURCE) - .takes_value(true) - .value_name("FILE") - .help("get random bytes from FILE"), - ) - .arg( - Arg::with_name(options::REPEAT) - .short("r") - .long(options::REPEAT) - .help("output lines can be repeated"), - ) - .arg( - Arg::with_name(options::ZERO_TERMINATED) - .short("z") - .long(options::ZERO_TERMINATED) - .help("line delimiter is NUL, not newline"), - ) - .arg(Arg::with_name(options::FILE).takes_value(true)) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let mode = if let Some(args) = matches.values_of(options::ECHO) { Mode::Echo(args.map(String::from).collect()) diff --git a/src/uu/sleep/src/app.rs b/src/uu/sleep/src/app.rs new file mode 100644 index 00000000000..9e0c5e3271d --- /dev/null +++ b/src/uu/sleep/src/app.rs @@ -0,0 +1,28 @@ +use clap::{crate_version, App, Arg}; + +static ABOUT: &str = "Pause for NUMBER seconds."; +static LONG_HELP: &str = "Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default), +'m' for minutes, 'h' for hours or 'd' for days. Unlike most implementations +that require NUMBER be an integer, here NUMBER may be an arbitrary floating +point number. Given two or more arguments, pause for the amount of time +specified by the sum of their values."; + +pub mod options { + pub const NUMBER: &str = "NUMBER"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::NUMBER) + .long(options::NUMBER) + .help("pause for NUMBER seconds") + .value_name(options::NUMBER) + .index(1) + .multiple(true) + .required(true), + ) +} diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index c78c1cfc9f8..4785d6a7a60 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -11,18 +11,10 @@ extern crate uucore; use std::thread; use std::time::Duration; -use clap::{crate_version, App, Arg}; +use crate::app::get_app; +use crate::app::options; -static ABOUT: &str = "Pause for NUMBER seconds."; -static LONG_HELP: &str = "Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default), -'m' for minutes, 'h' for hours or 'd' for days. Unlike most implementations -that require NUMBER be an integer, here NUMBER may be an arbitrary floating -point number. Given two or more arguments, pause for the amount of time -specified by the sum of their values."; - -mod options { - pub const NUMBER: &str = "NUMBER"; -} +pub mod app; fn get_usage() -> String { format!( @@ -35,20 +27,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .after_help(LONG_HELP) - .arg( - Arg::with_name(options::NUMBER) - .long(options::NUMBER) - .help("pause for NUMBER seconds") - .value_name(options::NUMBER) - .index(1) - .multiple(true) - .required(true), - ) .get_matches_from(args); if let Some(values) = matches.values_of(options::NUMBER) { diff --git a/src/uu/sort/src/app.rs b/src/uu/sort/src/app.rs new file mode 100644 index 00000000000..ada2020b4f6 --- /dev/null +++ b/src/uu/sort/src/app.rs @@ -0,0 +1,305 @@ +// spell-checker:ignore (misc) Mbdfhn + +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Display sorted concatenation of all FILE(s)."; + +const LONG_HELP_KEYS: &str = "The key format is FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]][OPTIONS]. + +Fields by default are separated by the first whitespace after a non-whitespace character. Use -t to specify a custom separator. +In the default case, whitespace is appended at the beginning of each field. Custom separators however are not included in fields. + +FIELD and CHAR both start at 1 (i.e. they are 1-indexed). If there is no end specified after a comma, the end will be the end of the line. +If CHAR is set 0, it means the end of the field. CHAR defaults to 1 for the start position and to 0 for the end position. + +Valid options are: MbdfhnRrV. They override the global options for this key."; + +pub mod options { + pub mod modes { + pub const SORT: &str = "sort"; + + pub const HUMAN_NUMERIC: &str = "human-numeric-sort"; + pub const MONTH: &str = "month-sort"; + pub const NUMERIC: &str = "numeric-sort"; + pub const GENERAL_NUMERIC: &str = "general-numeric-sort"; + pub const VERSION: &str = "version-sort"; + pub const RANDOM: &str = "random-sort"; + + pub const ALL_SORT_MODES: [&str; 6] = [ + GENERAL_NUMERIC, + HUMAN_NUMERIC, + MONTH, + NUMERIC, + VERSION, + RANDOM, + ]; + } + + pub mod check { + pub const CHECK: &str = "check"; + pub const CHECK_SILENT: &str = "check-silent"; + pub const SILENT: &str = "silent"; + pub const QUIET: &str = "quiet"; + pub const DIAGNOSE_FIRST: &str = "diagnose-first"; + } + + pub const DICTIONARY_ORDER: &str = "dictionary-order"; + pub const MERGE: &str = "merge"; + pub const DEBUG: &str = "debug"; + pub const IGNORE_CASE: &str = "ignore-case"; + pub const IGNORE_LEADING_BLANKS: &str = "ignore-leading-blanks"; + pub const IGNORE_NONPRINTING: &str = "ignore-nonprinting"; + pub const OUTPUT: &str = "output"; + pub const REVERSE: &str = "reverse"; + pub const STABLE: &str = "stable"; + pub const UNIQUE: &str = "unique"; + pub const KEY: &str = "key"; + pub const SEPARATOR: &str = "field-separator"; + pub const ZERO_TERMINATED: &str = "zero-terminated"; + pub const PARALLEL: &str = "parallel"; + pub const FILES0_FROM: &str = "files0-from"; + pub const BUF_SIZE: &str = "buffer-size"; + pub const TMP_DIR: &str = "temporary-directory"; + pub const COMPRESS_PROG: &str = "compress-program"; + pub const BATCH_SIZE: &str = "batch-size"; + + pub const FILES: &str = "files"; +} + +/// Creates an `Arg` that conflicts with all other sort modes. +fn make_sort_mode_arg<'a, 'b>(mode: &'a str, short: &'b str, help: &'b str) -> Arg<'a, 'b> { + let mut arg = Arg::with_name(mode).short(short).long(mode).help(help); + for possible_mode in &options::modes::ALL_SORT_MODES { + if *possible_mode != mode { + arg = arg.conflicts_with(possible_mode); + } + } + arg +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::modes::SORT) + .long(options::modes::SORT) + .takes_value(true) + .possible_values( + &[ + "general-numeric", + "human-numeric", + "month", + "numeric", + "version", + "random", + ] + ) + .conflicts_with_all(&options::modes::ALL_SORT_MODES) + ) + .arg( + make_sort_mode_arg( + options::modes::HUMAN_NUMERIC, + "h", + "compare according to human readable sizes, eg 1M > 100k" + ), + ) + .arg( + make_sort_mode_arg( + options::modes::MONTH, + "M", + "compare according to month name abbreviation" + ), + ) + .arg( + make_sort_mode_arg( + options::modes::NUMERIC, + "n", + "compare according to string numerical value" + ), + ) + .arg( + make_sort_mode_arg( + options::modes::GENERAL_NUMERIC, + "g", + "compare according to string general numerical value" + ), + ) + .arg( + make_sort_mode_arg( + options::modes::VERSION, + "V", + "Sort by SemVer version number, eg 1.12.2 > 1.1.2", + ), + ) + .arg( + make_sort_mode_arg( + options::modes::RANDOM, + "R", + "shuffle in random order", + ), + ) + .arg( + Arg::with_name(options::DICTIONARY_ORDER) + .short("d") + .long(options::DICTIONARY_ORDER) + .help("consider only blanks and alphanumeric characters") + .conflicts_with_all( + &[ + options::modes::NUMERIC, + options::modes::GENERAL_NUMERIC, + options::modes::HUMAN_NUMERIC, + options::modes::MONTH, + ] + ), + ) + .arg( + Arg::with_name(options::MERGE) + .short("m") + .long(options::MERGE) + .help("merge already sorted files; do not sort"), + ) + .arg( + Arg::with_name(options::check::CHECK) + .short("c") + .long(options::check::CHECK) + .takes_value(true) + .require_equals(true) + .min_values(0) + .possible_values(&[ + options::check::SILENT, + options::check::QUIET, + options::check::DIAGNOSE_FIRST, + ]) + .help("check for sorted input; do not sort"), + ) + .arg( + Arg::with_name(options::check::CHECK_SILENT) + .short("C") + .long(options::check::CHECK_SILENT) + .help("exit successfully if the given file is already sorted, and exit with status 1 otherwise."), + ) + .arg( + Arg::with_name(options::IGNORE_CASE) + .short("f") + .long(options::IGNORE_CASE) + .help("fold lower case to upper case characters"), + ) + .arg( + Arg::with_name(options::IGNORE_NONPRINTING) + .short("i") + .long(options::IGNORE_NONPRINTING) + .help("ignore nonprinting characters") + .conflicts_with_all( + &[ + options::modes::NUMERIC, + options::modes::GENERAL_NUMERIC, + options::modes::HUMAN_NUMERIC, + options::modes::MONTH + ] + ), + ) + .arg( + Arg::with_name(options::IGNORE_LEADING_BLANKS) + .short("b") + .long(options::IGNORE_LEADING_BLANKS) + .help("ignore leading blanks when finding sort keys in each line"), + ) + .arg( + Arg::with_name(options::OUTPUT) + .short("o") + .long(options::OUTPUT) + .help("write output to FILENAME instead of stdout") + .takes_value(true) + .value_name("FILENAME"), + ) + .arg( + Arg::with_name(options::REVERSE) + .short("r") + .long(options::REVERSE) + .help("reverse the output"), + ) + .arg( + Arg::with_name(options::STABLE) + .short("s") + .long(options::STABLE) + .help("stabilize sort by disabling last-resort comparison"), + ) + .arg( + Arg::with_name(options::UNIQUE) + .short("u") + .long(options::UNIQUE) + .help("output only the first of an equal run"), + ) + .arg( + Arg::with_name(options::KEY) + .short("k") + .long(options::KEY) + .help("sort by a key") + .long_help(LONG_HELP_KEYS) + .multiple(true) + .takes_value(true), + ) + .arg( + Arg::with_name(options::SEPARATOR) + .short("t") + .long(options::SEPARATOR) + .help("custom separator for -k") + .takes_value(true)) + .arg( + Arg::with_name(options::ZERO_TERMINATED) + .short("z") + .long(options::ZERO_TERMINATED) + .help("line delimiter is NUL, not newline"), + ) + .arg( + Arg::with_name(options::PARALLEL) + .long(options::PARALLEL) + .help("change the number of threads running concurrently to NUM_THREADS") + .takes_value(true) + .value_name("NUM_THREADS"), + ) + .arg( + Arg::with_name(options::BUF_SIZE) + .short("S") + .long(options::BUF_SIZE) + .help("sets the maximum SIZE of each segment in number of sorted items") + .takes_value(true) + .value_name("SIZE"), + ) + .arg( + Arg::with_name(options::TMP_DIR) + .short("T") + .long(options::TMP_DIR) + .help("use DIR for temporaries, not $TMPDIR or /tmp") + .takes_value(true) + .value_name("DIR"), + ) + .arg( + Arg::with_name(options::COMPRESS_PROG) + .long(options::COMPRESS_PROG) + .help("compress temporary files with PROG, decompress with PROG -d") + .long_help("PROG has to take input from stdin and output to stdout") + .value_name("PROG") + ) + .arg( + Arg::with_name(options::BATCH_SIZE) + .long(options::BATCH_SIZE) + .help("Merge at most N_MERGE inputs at once.") + .value_name("N_MERGE") + ) + .arg( + Arg::with_name(options::FILES0_FROM) + .long(options::FILES0_FROM) + .help("read input from the files specified by NUL-terminated NUL_FILES") + .takes_value(true) + .value_name("NUL_FILES") + .multiple(true), + ) + .arg( + Arg::with_name(options::DEBUG) + .long(options::DEBUG) + .help("underline the parts of the line that are actually used for sorting"), + ) + .arg(Arg::with_name(options::FILES).multiple(true).takes_value(true)) +} diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 006664193be..313a5933392 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -16,6 +16,7 @@ #[macro_use] extern crate uucore; +pub mod app; mod check; mod chunks; mod custom_str_cmp; @@ -23,7 +24,7 @@ mod ext_sort; mod merge; mod numeric_str_cmp; -use clap::{crate_version, App, Arg}; +use app::{get_app, options}; use custom_str_cmp::custom_str_cmp; use ext_sort::ext_sort; use fnv::FnvHasher; @@ -46,71 +47,6 @@ use unicode_width::UnicodeWidthStr; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::InvalidEncodingHandling; -const NAME: &str = "sort"; -const ABOUT: &str = "Display sorted concatenation of all FILE(s)."; - -const LONG_HELP_KEYS: &str = "The key format is FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]][OPTIONS]. - -Fields by default are separated by the first whitespace after a non-whitespace character. Use -t to specify a custom separator. -In the default case, whitespace is appended at the beginning of each field. Custom separators however are not included in fields. - -FIELD and CHAR both start at 1 (i.e. they are 1-indexed). If there is no end specified after a comma, the end will be the end of the line. -If CHAR is set 0, it means the end of the field. CHAR defaults to 1 for the start position and to 0 for the end position. - -Valid options are: MbdfhnRrV. They override the global options for this key."; - -mod options { - pub mod modes { - pub const SORT: &str = "sort"; - - pub const HUMAN_NUMERIC: &str = "human-numeric-sort"; - pub const MONTH: &str = "month-sort"; - pub const NUMERIC: &str = "numeric-sort"; - pub const GENERAL_NUMERIC: &str = "general-numeric-sort"; - pub const VERSION: &str = "version-sort"; - pub const RANDOM: &str = "random-sort"; - - pub const ALL_SORT_MODES: [&str; 6] = [ - GENERAL_NUMERIC, - HUMAN_NUMERIC, - MONTH, - NUMERIC, - VERSION, - RANDOM, - ]; - } - - pub mod check { - pub const CHECK: &str = "check"; - pub const CHECK_SILENT: &str = "check-silent"; - pub const SILENT: &str = "silent"; - pub const QUIET: &str = "quiet"; - pub const DIAGNOSE_FIRST: &str = "diagnose-first"; - } - - pub const DICTIONARY_ORDER: &str = "dictionary-order"; - pub const MERGE: &str = "merge"; - pub const DEBUG: &str = "debug"; - pub const IGNORE_CASE: &str = "ignore-case"; - pub const IGNORE_LEADING_BLANKS: &str = "ignore-leading-blanks"; - pub const IGNORE_NONPRINTING: &str = "ignore-nonprinting"; - pub const OUTPUT: &str = "output"; - pub const REVERSE: &str = "reverse"; - pub const STABLE: &str = "stable"; - pub const UNIQUE: &str = "unique"; - pub const KEY: &str = "key"; - pub const SEPARATOR: &str = "field-separator"; - pub const ZERO_TERMINATED: &str = "zero-terminated"; - pub const PARALLEL: &str = "parallel"; - pub const FILES0_FROM: &str = "files0-from"; - pub const BUF_SIZE: &str = "buffer-size"; - pub const TMP_DIR: &str = "temporary-directory"; - pub const COMPRESS_PROG: &str = "compress-program"; - pub const BATCH_SIZE: &str = "batch-size"; - - pub const FILES: &str = "files"; -} - const DECIMAL_PT: char = '.'; const NEGATIVE: char = '-'; @@ -889,27 +825,14 @@ impl FieldSelector { fn get_usage() -> String { format!( - "{0} -Usage: - {0} [OPTION]... [FILE]... + "{0} [OPTION]... [FILE]... Write the sorted concatenation of all FILE(s) to standard output. Mandatory arguments for long options are mandatory for short options too. With no FILE, or when FILE is -, read standard input.", - NAME + executable!() ) } -/// Creates an `Arg` that conflicts with all other sort modes. -fn make_sort_mode_arg<'a, 'b>(mode: &'a str, short: &'b str, help: &'b str) -> Arg<'a, 'b> { - let mut arg = Arg::with_name(mode).short(short).long(mode).help(help); - for possible_mode in &options::modes::ALL_SORT_MODES { - if *possible_mode != mode { - arg = arg.conflicts_with(possible_mode); - } - } - arg -} - pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) @@ -917,231 +840,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let mut settings: GlobalSettings = Default::default(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) - .usage(&usage[..]) - .arg( - Arg::with_name(options::modes::SORT) - .long(options::modes::SORT) - .takes_value(true) - .possible_values( - &[ - "general-numeric", - "human-numeric", - "month", - "numeric", - "version", - "random", - ] - ) - .conflicts_with_all(&options::modes::ALL_SORT_MODES) - ) - .arg( - make_sort_mode_arg( - options::modes::HUMAN_NUMERIC, - "h", - "compare according to human readable sizes, eg 1M > 100k" - ), - ) - .arg( - make_sort_mode_arg( - options::modes::MONTH, - "M", - "compare according to month name abbreviation" - ), - ) - .arg( - make_sort_mode_arg( - options::modes::NUMERIC, - "n", - "compare according to string numerical value" - ), - ) - .arg( - make_sort_mode_arg( - options::modes::GENERAL_NUMERIC, - "g", - "compare according to string general numerical value" - ), - ) - .arg( - make_sort_mode_arg( - options::modes::VERSION, - "V", - "Sort by SemVer version number, eg 1.12.2 > 1.1.2", - ), - ) - .arg( - make_sort_mode_arg( - options::modes::RANDOM, - "R", - "shuffle in random order", - ), - ) - .arg( - Arg::with_name(options::DICTIONARY_ORDER) - .short("d") - .long(options::DICTIONARY_ORDER) - .help("consider only blanks and alphanumeric characters") - .conflicts_with_all( - &[ - options::modes::NUMERIC, - options::modes::GENERAL_NUMERIC, - options::modes::HUMAN_NUMERIC, - options::modes::MONTH, - ] - ), - ) - .arg( - Arg::with_name(options::MERGE) - .short("m") - .long(options::MERGE) - .help("merge already sorted files; do not sort"), - ) - .arg( - Arg::with_name(options::check::CHECK) - .short("c") - .long(options::check::CHECK) - .takes_value(true) - .require_equals(true) - .min_values(0) - .possible_values(&[ - options::check::SILENT, - options::check::QUIET, - options::check::DIAGNOSE_FIRST, - ]) - .help("check for sorted input; do not sort"), - ) - .arg( - Arg::with_name(options::check::CHECK_SILENT) - .short("C") - .long(options::check::CHECK_SILENT) - .help("exit successfully if the given file is already sorted, and exit with status 1 otherwise."), - ) - .arg( - Arg::with_name(options::IGNORE_CASE) - .short("f") - .long(options::IGNORE_CASE) - .help("fold lower case to upper case characters"), - ) - .arg( - Arg::with_name(options::IGNORE_NONPRINTING) - .short("i") - .long(options::IGNORE_NONPRINTING) - .help("ignore nonprinting characters") - .conflicts_with_all( - &[ - options::modes::NUMERIC, - options::modes::GENERAL_NUMERIC, - options::modes::HUMAN_NUMERIC, - options::modes::MONTH - ] - ), - ) - .arg( - Arg::with_name(options::IGNORE_LEADING_BLANKS) - .short("b") - .long(options::IGNORE_LEADING_BLANKS) - .help("ignore leading blanks when finding sort keys in each line"), - ) - .arg( - Arg::with_name(options::OUTPUT) - .short("o") - .long(options::OUTPUT) - .help("write output to FILENAME instead of stdout") - .takes_value(true) - .value_name("FILENAME"), - ) - .arg( - Arg::with_name(options::REVERSE) - .short("r") - .long(options::REVERSE) - .help("reverse the output"), - ) - .arg( - Arg::with_name(options::STABLE) - .short("s") - .long(options::STABLE) - .help("stabilize sort by disabling last-resort comparison"), - ) - .arg( - Arg::with_name(options::UNIQUE) - .short("u") - .long(options::UNIQUE) - .help("output only the first of an equal run"), - ) - .arg( - Arg::with_name(options::KEY) - .short("k") - .long(options::KEY) - .help("sort by a key") - .long_help(LONG_HELP_KEYS) - .multiple(true) - .takes_value(true), - ) - .arg( - Arg::with_name(options::SEPARATOR) - .short("t") - .long(options::SEPARATOR) - .help("custom separator for -k") - .takes_value(true)) - .arg( - Arg::with_name(options::ZERO_TERMINATED) - .short("z") - .long(options::ZERO_TERMINATED) - .help("line delimiter is NUL, not newline"), - ) - .arg( - Arg::with_name(options::PARALLEL) - .long(options::PARALLEL) - .help("change the number of threads running concurrently to NUM_THREADS") - .takes_value(true) - .value_name("NUM_THREADS"), - ) - .arg( - Arg::with_name(options::BUF_SIZE) - .short("S") - .long(options::BUF_SIZE) - .help("sets the maximum SIZE of each segment in number of sorted items") - .takes_value(true) - .value_name("SIZE"), - ) - .arg( - Arg::with_name(options::TMP_DIR) - .short("T") - .long(options::TMP_DIR) - .help("use DIR for temporaries, not $TMPDIR or /tmp") - .takes_value(true) - .value_name("DIR"), - ) - .arg( - Arg::with_name(options::COMPRESS_PROG) - .long(options::COMPRESS_PROG) - .help("compress temporary files with PROG, decompress with PROG -d") - .long_help("PROG has to take input from stdin and output to stdout") - .value_name("PROG") - ) - .arg( - Arg::with_name(options::BATCH_SIZE) - .long(options::BATCH_SIZE) - .help("Merge at most N_MERGE inputs at once.") - .value_name("N_MERGE") - ) - .arg( - Arg::with_name(options::FILES0_FROM) - .long(options::FILES0_FROM) - .help("read input from the files specified by NUL-terminated NUL_FILES") - .takes_value(true) - .value_name("NUL_FILES") - .multiple(true), - ) - .arg( - Arg::with_name(options::DEBUG) - .long(options::DEBUG) - .help("underline the parts of the line that are actually used for sorting"), - ) - .arg(Arg::with_name(options::FILES).multiple(true).takes_value(true)) + let matches = get_app(executable!()) + .usage(usage.as_str()) .get_matches_from(args); settings.debug = matches.is_present(options::DEBUG); diff --git a/src/uu/split/src/app.rs b/src/uu/split/src/app.rs new file mode 100644 index 00000000000..6896a9030df --- /dev/null +++ b/src/uu/split/src/app.rs @@ -0,0 +1,92 @@ +use clap::{crate_version, App, Arg}; + +pub const OPT_BYTES: &str = "bytes"; +pub const OPT_LINE_BYTES: &str = "line-bytes"; +pub const OPT_LINES: &str = "lines"; +pub const OPT_ADDITIONAL_SUFFIX: &str = "additional-suffix"; +pub const OPT_FILTER: &str = "filter"; +pub const OPT_NUMERIC_SUFFIXES: &str = "numeric-suffixes"; +pub const OPT_SUFFIX_LENGTH: &str = "suffix-length"; +pub const OPT_DEFAULT_SUFFIX_LENGTH: &str = "2"; +pub const OPT_VERBOSE: &str = "verbose"; + +pub const ARG_INPUT: &str = "input"; +pub const ARG_PREFIX: &str = "prefix"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about("Create output files containing consecutive or interleaved sections of input") + // strategy (mutually exclusive) + .arg( + Arg::with_name(OPT_BYTES) + .short("b") + .long(OPT_BYTES) + .takes_value(true) + .default_value("2") + .help("use suffixes of length N (default 2)"), + ) + .arg( + Arg::with_name(OPT_LINE_BYTES) + .short("C") + .long(OPT_LINE_BYTES) + .takes_value(true) + .default_value("2") + .help("put at most SIZE bytes of lines per output file"), + ) + .arg( + Arg::with_name(OPT_LINES) + .short("l") + .long(OPT_LINES) + .takes_value(true) + .default_value("1000") + .help("write to shell COMMAND file name is $FILE (Currently not implemented for Windows)"), + ) + // rest of the arguments + .arg( + Arg::with_name(OPT_ADDITIONAL_SUFFIX) + .long(OPT_ADDITIONAL_SUFFIX) + .takes_value(true) + .default_value("") + .help("additional suffix to append to output file names"), + ) + .arg( + Arg::with_name(OPT_FILTER) + .long(OPT_FILTER) + .takes_value(true) + .help("write to shell COMMAND file name is $FILE (Currently not implemented for Windows)"), + ) + .arg( + Arg::with_name(OPT_NUMERIC_SUFFIXES) + .short("d") + .long(OPT_NUMERIC_SUFFIXES) + .takes_value(true) + .default_value("0") + .help("use numeric suffixes instead of alphabetic"), + ) + .arg( + Arg::with_name(OPT_SUFFIX_LENGTH) + .short("a") + .long(OPT_SUFFIX_LENGTH) + .takes_value(true) + .default_value(OPT_DEFAULT_SUFFIX_LENGTH) + .help("use suffixes of length N (default 2)"), + ) + .arg( + Arg::with_name(OPT_VERBOSE) + .long(OPT_VERBOSE) + .help("print a diagnostic just before each output file is opened"), + ) + .arg( + Arg::with_name(ARG_INPUT) + .takes_value(true) + .default_value("-") + .index(1) + ) + .arg( + Arg::with_name(ARG_PREFIX) + .takes_value(true) + .default_value("x") + .index(2) + ) +} diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 0d5543d8b88..d1c2e2052b7 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -12,32 +12,19 @@ extern crate uucore; mod platform; -use clap::{crate_version, App, Arg}; use std::convert::TryFrom; -use std::env; use std::fs::File; use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write}; use std::path::Path; use std::{char, fs::remove_file}; use uucore::parse_size::parse_size; -static NAME: &str = "split"; +use crate::app::*; -static OPT_BYTES: &str = "bytes"; -static OPT_LINE_BYTES: &str = "line-bytes"; -static OPT_LINES: &str = "lines"; -static OPT_ADDITIONAL_SUFFIX: &str = "additional-suffix"; -static OPT_FILTER: &str = "filter"; -static OPT_NUMERIC_SUFFIXES: &str = "numeric-suffixes"; -static OPT_SUFFIX_LENGTH: &str = "suffix-length"; -static OPT_DEFAULT_SUFFIX_LENGTH: usize = 2; -static OPT_VERBOSE: &str = "verbose"; - -static ARG_INPUT: &str = "input"; -static ARG_PREFIX: &str = "prefix"; +pub mod app; fn get_usage() -> String { - format!("{0} [OPTION]... [INPUT [PREFIX]]", NAME) + format!("{0} [OPTION]... [INPUT [PREFIX]]", executable!()) } fn get_long_usage() -> String { format!( @@ -54,85 +41,10 @@ size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let long_usage = get_long_usage(); - let default_suffix_length_str = OPT_DEFAULT_SUFFIX_LENGTH.to_string(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about("Create output files containing consecutive or interleaved sections of input") + let matches = get_app(executable!()) .usage(&usage[..]) .after_help(&long_usage[..]) - // strategy (mutually exclusive) - .arg( - Arg::with_name(OPT_BYTES) - .short("b") - .long(OPT_BYTES) - .takes_value(true) - .default_value("2") - .help("use suffixes of length N (default 2)"), - ) - .arg( - Arg::with_name(OPT_LINE_BYTES) - .short("C") - .long(OPT_LINE_BYTES) - .takes_value(true) - .default_value("2") - .help("put at most SIZE bytes of lines per output file"), - ) - .arg( - Arg::with_name(OPT_LINES) - .short("l") - .long(OPT_LINES) - .takes_value(true) - .default_value("1000") - .help("write to shell COMMAND file name is $FILE (Currently not implemented for Windows)"), - ) - // rest of the arguments - .arg( - Arg::with_name(OPT_ADDITIONAL_SUFFIX) - .long(OPT_ADDITIONAL_SUFFIX) - .takes_value(true) - .default_value("") - .help("additional suffix to append to output file names"), - ) - .arg( - Arg::with_name(OPT_FILTER) - .long(OPT_FILTER) - .takes_value(true) - .help("write to shell COMMAND file name is $FILE (Currently not implemented for Windows)"), - ) - .arg( - Arg::with_name(OPT_NUMERIC_SUFFIXES) - .short("d") - .long(OPT_NUMERIC_SUFFIXES) - .takes_value(true) - .default_value("0") - .help("use numeric suffixes instead of alphabetic"), - ) - .arg( - Arg::with_name(OPT_SUFFIX_LENGTH) - .short("a") - .long(OPT_SUFFIX_LENGTH) - .takes_value(true) - .default_value(default_suffix_length_str.as_str()) - .help("use suffixes of length N (default 2)"), - ) - .arg( - Arg::with_name(OPT_VERBOSE) - .long(OPT_VERBOSE) - .help("print a diagnostic just before each output file is opened"), - ) - .arg( - Arg::with_name(ARG_INPUT) - .takes_value(true) - .default_value("-") - .index(1) - ) - .arg( - Arg::with_name(ARG_PREFIX) - .takes_value(true) - .default_value("x") - .index(2) - ) .get_matches_from(args); let mut settings = Settings { diff --git a/src/uu/stat/src/app.rs b/src/uu/stat/src/app.rs new file mode 100644 index 00000000000..37920ea4127 --- /dev/null +++ b/src/uu/stat/src/app.rs @@ -0,0 +1,63 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Display file or file system status."; + +pub mod options { + pub const DEREFERENCE: &str = "dereference"; + pub const FILE_SYSTEM: &str = "file-system"; + pub const FORMAT: &str = "format"; + pub const PRINTF: &str = "printf"; + pub const TERSE: &str = "terse"; +} + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::DEREFERENCE) + .short("L") + .long(options::DEREFERENCE) + .help("follow links"), + ) + .arg( + Arg::with_name(options::FILE_SYSTEM) + .short("f") + .long(options::FILE_SYSTEM) + .help("display file system status instead of file status"), + ) + .arg( + Arg::with_name(options::TERSE) + .short("t") + .long(options::TERSE) + .help("print the information in terse form"), + ) + .arg( + Arg::with_name(options::FORMAT) + .short("c") + .long(options::FORMAT) + .help( + "use the specified FORMAT instead of the default; +output a newline after each use of FORMAT", + ) + .value_name("FORMAT"), + ) + .arg( + Arg::with_name(options::PRINTF) + .long(options::PRINTF) + .value_name("FORMAT") + .help( + "like --format, but interpret backslash escapes, + and do not output a mandatory trailing newline; + if you want a newline, include \n in FORMAT", + ), + ) + .arg( + Arg::with_name(ARG_FILES) + .multiple(true) + .takes_value(true) + .min_values(1), + ) +} diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 4e1d9d2c9eb..37cb165a608 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -7,6 +7,8 @@ #[macro_use] extern crate uucore; +use app::options; +use app::ARG_FILES; use uucore::entries; use uucore::fs::display_permissions; use uucore::fsext::{ @@ -14,13 +16,17 @@ use uucore::fsext::{ }; use uucore::libc::mode_t; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::ArgMatches; use std::borrow::Cow; use std::convert::AsRef; use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::path::Path; use std::{cmp, fs, iter}; +use crate::app::get_app; + +pub mod app; + macro_rules! check_bound { ($str: ident, $bound:expr, $beg: expr, $end: expr) => { if $end >= $bound { @@ -81,18 +87,6 @@ macro_rules! print_adjusted { }; } -static ABOUT: &str = "Display file or file system status."; - -pub mod options { - pub static DEREFERENCE: &str = "dereference"; - pub static FILE_SYSTEM: &str = "file-system"; - pub static FORMAT: &str = "format"; - pub static PRINTF: &str = "printf"; - pub static TERSE: &str = "terse"; -} - -static ARG_FILES: &str = "files"; - pub const F_ALTER: u8 = 1; pub const F_ZERO: u8 = 1 << 1; pub const F_LEFT: u8 = 1 << 2; @@ -947,55 +941,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let long_usage = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) .after_help(&long_usage[..]) - .arg( - Arg::with_name(options::DEREFERENCE) - .short("L") - .long(options::DEREFERENCE) - .help("follow links"), - ) - .arg( - Arg::with_name(options::FILE_SYSTEM) - .short("f") - .long(options::FILE_SYSTEM) - .help("display file system status instead of file status"), - ) - .arg( - Arg::with_name(options::TERSE) - .short("t") - .long(options::TERSE) - .help("print the information in terse form"), - ) - .arg( - Arg::with_name(options::FORMAT) - .short("c") - .long(options::FORMAT) - .help( - "use the specified FORMAT instead of the default; - output a newline after each use of FORMAT", - ) - .value_name("FORMAT"), - ) - .arg( - Arg::with_name(options::PRINTF) - .long(options::PRINTF) - .value_name("FORMAT") - .help( - "like --format, but interpret backslash escapes, - and do not output a mandatory trailing newline; - if you want a newline, include \n in FORMAT", - ), - ) - .arg( - Arg::with_name(ARG_FILES) - .multiple(true) - .takes_value(true) - .min_values(1), - ) .get_matches_from(args); match Stater::new(matches) { diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 884a9878551..8fe03814884 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -22,6 +22,7 @@ uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_p [build-dependencies] libstdbuf = { version="0.0.6", package="uu_stdbuf_libstdbuf", path="src/libstdbuf" } +clap = "2.33" [[bin]] name = "stdbuf" diff --git a/src/uu/stdbuf/src/app.rs b/src/uu/stdbuf/src/app.rs new file mode 100644 index 00000000000..acb1951f9cb --- /dev/null +++ b/src/uu/stdbuf/src/app.rs @@ -0,0 +1,64 @@ +use clap::{crate_version, App, AppSettings, Arg}; + +const ABOUT: &str = "Run COMMAND, with modified buffering operations for its standard streams.\n\n\ + Mandatory arguments to long options are mandatory for short options too."; +const LONG_HELP: &str = "If MODE is 'L' the corresponding stream will be line buffered.\n\ + This option is invalid with standard input.\n\n\ + If MODE is '0' the corresponding stream will be unbuffered.\n\n\ + Otherwise MODE is a number which may be followed by one of the following:\n\n\ + KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\ + In this case the corresponding stream will be fully buffered with the buffer size set to \ + MODE bytes.\n\n\ + NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for e.g.) then \ + that will override corresponding settings changed by 'stdbuf'.\n\ + Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O, \ + and are thus unaffected by 'stdbuf' settings.\n"; + +pub mod options { + pub const INPUT: &str = "input"; + pub const INPUT_SHORT: &str = "i"; + pub const OUTPUT: &str = "output"; + pub const OUTPUT_SHORT: &str = "o"; + pub const ERROR: &str = "error"; + pub const ERROR_SHORT: &str = "e"; + pub const COMMAND: &str = "command"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .after_help(LONG_HELP) + .setting(AppSettings::TrailingVarArg) + .arg( + Arg::with_name(options::INPUT) + .long(options::INPUT) + .short(options::INPUT_SHORT) + .help("adjust standard input stream buffering") + .value_name("MODE") + .required_unless_one(&[options::OUTPUT, options::ERROR]), + ) + .arg( + Arg::with_name(options::OUTPUT) + .long(options::OUTPUT) + .short(options::OUTPUT_SHORT) + .help("adjust standard output stream buffering") + .value_name("MODE") + .required_unless_one(&[options::INPUT, options::ERROR]), + ) + .arg( + Arg::with_name(options::ERROR) + .long(options::ERROR) + .short(options::ERROR_SHORT) + .help("adjust standard error stream buffering") + .value_name("MODE") + .required_unless_one(&[options::INPUT, options::OUTPUT]), + ) + .arg( + Arg::with_name(options::COMMAND) + .multiple(true) + .takes_value(true) + .hidden(true) + .required(true), + ) +} diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index fc0c83ec807..8afcce86d37 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -10,7 +10,7 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; +use clap::ArgMatches; use std::convert::TryFrom; use std::fs::File; use std::io::{self, Write}; @@ -22,30 +22,9 @@ use tempfile::TempDir; use uucore::parse_size::parse_size; use uucore::InvalidEncodingHandling; -static ABOUT: &str = - "Run COMMAND, with modified buffering operations for its standard streams.\n\n\ - Mandatory arguments to long options are mandatory for short options too."; -static LONG_HELP: &str = "If MODE is 'L' the corresponding stream will be line buffered.\n\ - This option is invalid with standard input.\n\n\ - If MODE is '0' the corresponding stream will be unbuffered.\n\n\ - Otherwise MODE is a number which may be followed by one of the following:\n\n\ - KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\ - In this case the corresponding stream will be fully buffered with the buffer size set to \ - MODE bytes.\n\n\ - NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for e.g.) then \ - that will override corresponding settings changed by 'stdbuf'.\n\ - Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O, \ - and are thus unaffected by 'stdbuf' settings.\n"; - -mod options { - pub const INPUT: &str = "input"; - pub const INPUT_SHORT: &str = "i"; - pub const OUTPUT: &str = "output"; - pub const OUTPUT_SHORT: &str = "o"; - pub const ERROR: &str = "error"; - pub const ERROR_SHORT: &str = "e"; - pub const COMMAND: &str = "command"; -} +use crate::app::{get_app, options}; + +pub mod app; fn get_usage() -> String { format!("{0} OPTION... COMMAND", executable!()) @@ -154,43 +133,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .accept_any(); let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .after_help(LONG_HELP) - .setting(AppSettings::TrailingVarArg) - .arg( - Arg::with_name(options::INPUT) - .long(options::INPUT) - .short(options::INPUT_SHORT) - .help("adjust standard input stream buffering") - .value_name("MODE") - .required_unless_one(&[options::OUTPUT, options::ERROR]), - ) - .arg( - Arg::with_name(options::OUTPUT) - .long(options::OUTPUT) - .short(options::OUTPUT_SHORT) - .help("adjust standard output stream buffering") - .value_name("MODE") - .required_unless_one(&[options::INPUT, options::ERROR]), - ) - .arg( - Arg::with_name(options::ERROR) - .long(options::ERROR) - .short(options::ERROR_SHORT) - .help("adjust standard error stream buffering") - .value_name("MODE") - .required_unless_one(&[options::INPUT, options::OUTPUT]), - ) - .arg( - Arg::with_name(options::COMMAND) - .multiple(true) - .takes_value(true) - .hidden(true) - .required(true), - ) .get_matches_from(args); let options = ProgramOptions::try_from(&matches) diff --git a/src/uu/sum/src/app.rs b/src/uu/sum/src/app.rs new file mode 100644 index 00000000000..0ee4f42c54f --- /dev/null +++ b/src/uu/sum/src/app.rs @@ -0,0 +1,29 @@ +use clap::{crate_version, App, Arg}; + +const USAGE: &str = "[OPTION]... [FILE]...\nWith no FILE, or when FILE is -, read standard input."; +const SUMMARY: &str = "Checksum and count the blocks in a file."; + +pub mod options { + pub const FILE: &str = "file"; + pub const BSD_COMPATIBLE: &str = "r"; + pub const SYSTEM_V_COMPATIBLE: &str = "sysv"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .usage(USAGE) + .about(SUMMARY) + .arg(Arg::with_name(options::FILE).multiple(true).hidden(true)) + .arg( + Arg::with_name(options::BSD_COMPATIBLE) + .short(options::BSD_COMPATIBLE) + .help("use the BSD sum algorithm, use 1K blocks (default)"), + ) + .arg( + Arg::with_name(options::SYSTEM_V_COMPATIBLE) + .short("s") + .long(options::SYSTEM_V_COMPATIBLE) + .help("use System V sum algorithm, use 512 bytes blocks"), + ) +} diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 4d42d7a9762..a188892f233 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -10,16 +10,14 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{stdin, Read, Result}; use std::path::Path; use uucore::InvalidEncodingHandling; -static NAME: &str = "sum"; -static USAGE: &str = - "[OPTION]... [FILE]...\nWith no FILE, or when FILE is -, read standard input."; -static SUMMARY: &str = "Checksum and count the blocks in a file."; +use crate::app::{get_app, options}; + +pub mod app; fn bsd_sum(mut reader: Box) -> (usize, u16) { let mut buf = [0; 1024]; @@ -87,35 +85,12 @@ fn open(name: &str) -> Result> { } } -mod options { - pub static FILE: &str = "file"; - pub static BSD_COMPATIBLE: &str = "r"; - pub static SYSTEM_V_COMPATIBLE: &str = "sysv"; -} - pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(USAGE) - .about(SUMMARY) - .arg(Arg::with_name(options::FILE).multiple(true).hidden(true)) - .arg( - Arg::with_name(options::BSD_COMPATIBLE) - .short(options::BSD_COMPATIBLE) - .help("use the BSD sum algorithm, use 1K blocks (default)"), - ) - .arg( - Arg::with_name(options::SYSTEM_V_COMPATIBLE) - .short("s") - .long(options::SYSTEM_V_COMPATIBLE) - .help("use System V sum algorithm, use 512 bytes blocks"), - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let files: Vec = match matches.values_of(options::FILE) { Some(v) => v.clone().map(|v| v.to_owned()).collect(), diff --git a/src/uu/sync/src/app.rs b/src/uu/sync/src/app.rs new file mode 100644 index 00000000000..7485e0755d0 --- /dev/null +++ b/src/uu/sync/src/app.rs @@ -0,0 +1,30 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Synchronize cached writes to persistent storage"; +pub mod options { + pub const FILE_SYSTEM: &str = "file-system"; + pub const DATA: &str = "data"; +} + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::FILE_SYSTEM) + .short("f") + .long(options::FILE_SYSTEM) + .conflicts_with(options::DATA) + .help("sync the file systems that contain the files (Linux and Windows only)"), + ) + .arg( + Arg::with_name(options::DATA) + .short("d") + .long(options::DATA) + .conflicts_with(options::FILE_SYSTEM) + .help("sync only file data, no unneeded metadata (Linux only)"), + ) + .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) +} diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 53d1a570191..0045aadd66b 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -12,18 +12,13 @@ extern crate libc; #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::path::Path; -static EXIT_ERR: i32 = 1; +use crate::app::{get_app, options, ARG_FILES}; -static ABOUT: &str = "Synchronize cached writes to persistent storage"; -pub mod options { - pub static FILE_SYSTEM: &str = "file-system"; - pub static DATA: &str = "data"; -} +pub mod app; -static ARG_FILES: &str = "files"; +static EXIT_ERR: i32 = 1; #[cfg(unix)] mod platform { @@ -166,25 +161,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(options::FILE_SYSTEM) - .short("f") - .long(options::FILE_SYSTEM) - .conflicts_with(options::DATA) - .help("sync the file systems that contain the files (Linux and Windows only)"), - ) - .arg( - Arg::with_name(options::DATA) - .short("d") - .long(options::DATA) - .conflicts_with(options::FILE_SYSTEM) - .help("sync only file data, no unneeded metadata (Linux only)"), - ) - .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) .get_matches_from(args); let files: Vec = matches diff --git a/src/uu/tac/src/app.rs b/src/uu/tac/src/app.rs new file mode 100644 index 00000000000..dd3c19e8528 --- /dev/null +++ b/src/uu/tac/src/app.rs @@ -0,0 +1,40 @@ +use clap::{crate_version, App, Arg}; + +const USAGE: &str = "[OPTION]... [FILE]..."; +const SUMMARY: &str = "Write each file to standard output, last line first."; + +pub mod options { + pub const BEFORE: &str = "before"; + pub const REGEX: &str = "regex"; + pub const SEPARATOR: &str = "separator"; + pub const FILE: &str = "file"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .usage(USAGE) + .about(SUMMARY) + .arg( + Arg::with_name(options::BEFORE) + .short("b") + .long(options::BEFORE) + .help("attach the separator before instead of after") + .takes_value(false), + ) + .arg( + Arg::with_name(options::REGEX) + .short("r") + .long(options::REGEX) + .help("interpret the sequence as a regular expression (NOT IMPLEMENTED)") + .takes_value(false), + ) + .arg( + Arg::with_name(options::SEPARATOR) + .short("s") + .long(options::SEPARATOR) + .help("use STRING as the separator instead of newline") + .takes_value(true), + ) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) +} diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index be1852ec58d..71e166fc691 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -10,55 +10,20 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::io::{stdin, stdout, BufReader, Read, Stdout, Write}; use std::{fs::File, path::Path}; use uucore::InvalidEncodingHandling; -static NAME: &str = "tac"; -static USAGE: &str = "[OPTION]... [FILE]..."; -static SUMMARY: &str = "Write each file to standard output, last line first."; +use crate::app::{get_app, options}; -mod options { - pub static BEFORE: &str = "before"; - pub static REGEX: &str = "regex"; - pub static SEPARATOR: &str = "separator"; - pub static FILE: &str = "file"; -} +pub mod app; pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(USAGE) - .about(SUMMARY) - .arg( - Arg::with_name(options::BEFORE) - .short("b") - .long(options::BEFORE) - .help("attach the separator before instead of after") - .takes_value(false), - ) - .arg( - Arg::with_name(options::REGEX) - .short("r") - .long(options::REGEX) - .help("interpret the sequence as a regular expression (NOT IMPLEMENTED)") - .takes_value(false), - ) - .arg( - Arg::with_name(options::SEPARATOR) - .short("s") - .long(options::SEPARATOR) - .help("use STRING as the separator instead of newline") - .takes_value(true), - ) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let before = matches.is_present(options::BEFORE); let regex = matches.is_present(options::REGEX); diff --git a/src/uu/tail/src/app.rs b/src/uu/tail/src/app.rs new file mode 100644 index 00000000000..06c26b669ec --- /dev/null +++ b/src/uu/tail/src/app.rs @@ -0,0 +1,86 @@ +use clap::{crate_version, App, Arg}; + +pub mod options { + pub mod verbosity { + pub static QUIET: &str = "quiet"; + pub static VERBOSE: &str = "verbose"; + } + pub static BYTES: &str = "bytes"; + pub static FOLLOW: &str = "follow"; + pub static LINES: &str = "lines"; + pub static PID: &str = "pid"; + pub static SLEEP_INT: &str = "sleep-interval"; + pub static ZERO_TERM: &str = "zero-terminated"; + pub static ARG_FILES: &str = "files"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about("output the last part of files") + // TODO: add usage + .arg( + Arg::with_name(options::BYTES) + .short("c") + .long(options::BYTES) + .takes_value(true) + .allow_hyphen_values(true) + .overrides_with_all(&[options::BYTES, options::LINES]) + .help("Number of bytes to print"), + ) + .arg( + Arg::with_name(options::FOLLOW) + .short("f") + .long(options::FOLLOW) + .help("Print the file as it grows"), + ) + .arg( + Arg::with_name(options::LINES) + .short("n") + .long(options::LINES) + .takes_value(true) + .allow_hyphen_values(true) + .overrides_with_all(&[options::BYTES, options::LINES]) + .help("Number of lines to print"), + ) + .arg( + Arg::with_name(options::PID) + .long(options::PID) + .takes_value(true) + .help("with -f, terminate after process ID, PID dies"), + ) + .arg( + Arg::with_name(options::verbosity::QUIET) + .short("q") + .long(options::verbosity::QUIET) + .visible_alias("silent") + .overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE]) + .help("never output headers giving file names"), + ) + .arg( + Arg::with_name(options::SLEEP_INT) + .short("s") + .takes_value(true) + .long(options::SLEEP_INT) + .help("Number or seconds to sleep between polling the file when running with -f"), + ) + .arg( + Arg::with_name(options::verbosity::VERBOSE) + .short("v") + .long(options::verbosity::VERBOSE) + .overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE]) + .help("always output headers giving file names"), + ) + .arg( + Arg::with_name(options::ZERO_TERM) + .short("z") + .long(options::ZERO_TERM) + .help("Line delimiter is NUL, not newline"), + ) + .arg( + Arg::with_name(options::ARG_FILES) + .multiple(true) + .takes_value(true) + .min_values(1), + ) +} diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 8950886a228..b005cdc3cc7 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -7,10 +7,6 @@ // * file that was distributed with this source code. // spell-checker:ignore (ToDO) seekable seek'd tail'ing ringbuffer ringbuf - -#[macro_use] -extern crate clap; - #[macro_use] extern crate uucore; @@ -18,7 +14,6 @@ mod chunks; mod platform; use chunks::ReverseChunks; -use clap::{App, Arg}; use std::collections::VecDeque; use std::fmt; use std::fs::File; @@ -29,19 +24,10 @@ use std::time::Duration; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::ringbuffer::RingBuffer; -pub mod options { - pub mod verbosity { - pub static QUIET: &str = "quiet"; - pub static VERBOSE: &str = "verbose"; - } - pub static BYTES: &str = "bytes"; - pub static FOLLOW: &str = "follow"; - pub static LINES: &str = "lines"; - pub static PID: &str = "pid"; - pub static SLEEP_INT: &str = "sleep-interval"; - pub static ZERO_TERM: &str = "zero-terminated"; - pub static ARG_FILES: &str = "files"; -} +use crate::app::get_app; +use crate::app::options; + +pub mod app; enum FilterMode { Bytes(usize), @@ -72,74 +58,7 @@ impl Default for Settings { pub fn uumain(args: impl uucore::Args) -> i32 { let mut settings: Settings = Default::default(); - let app = App::new(executable!()) - .version(crate_version!()) - .about("output the last part of files") - // TODO: add usage - .arg( - Arg::with_name(options::BYTES) - .short("c") - .long(options::BYTES) - .takes_value(true) - .allow_hyphen_values(true) - .overrides_with_all(&[options::BYTES, options::LINES]) - .help("Number of bytes to print"), - ) - .arg( - Arg::with_name(options::FOLLOW) - .short("f") - .long(options::FOLLOW) - .help("Print the file as it grows"), - ) - .arg( - Arg::with_name(options::LINES) - .short("n") - .long(options::LINES) - .takes_value(true) - .allow_hyphen_values(true) - .overrides_with_all(&[options::BYTES, options::LINES]) - .help("Number of lines to print"), - ) - .arg( - Arg::with_name(options::PID) - .long(options::PID) - .takes_value(true) - .help("with -f, terminate after process ID, PID dies"), - ) - .arg( - Arg::with_name(options::verbosity::QUIET) - .short("q") - .long(options::verbosity::QUIET) - .visible_alias("silent") - .overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE]) - .help("never output headers giving file names"), - ) - .arg( - Arg::with_name(options::SLEEP_INT) - .short("s") - .takes_value(true) - .long(options::SLEEP_INT) - .help("Number or seconds to sleep between polling the file when running with -f"), - ) - .arg( - Arg::with_name(options::verbosity::VERBOSE) - .short("v") - .long(options::verbosity::VERBOSE) - .overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE]) - .help("always output headers giving file names"), - ) - .arg( - Arg::with_name(options::ZERO_TERM) - .short("z") - .long(options::ZERO_TERM) - .help("Line delimiter is NUL, not newline"), - ) - .arg( - Arg::with_name(options::ARG_FILES) - .multiple(true) - .takes_value(true) - .min_values(1), - ); + let app = get_app(executable!()); let matches = app.get_matches_from(args); diff --git a/src/uu/tee/src/app.rs b/src/uu/tee/src/app.rs new file mode 100644 index 00000000000..dbc69244df4 --- /dev/null +++ b/src/uu/tee/src/app.rs @@ -0,0 +1,29 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Copy standard input to each FILE, and also to standard output."; + +pub mod options { + pub const APPEND: &str = "append"; + pub const IGNORE_INTERRUPTS: &str = "ignore-interrupts"; + pub const FILE: &str = "file"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .after_help("If a FILE is -, it refers to a file named - .") + .arg( + Arg::with_name(options::APPEND) + .long(options::APPEND) + .short("a") + .help("append to the given FILEs, do not overwrite"), + ) + .arg( + Arg::with_name(options::IGNORE_INTERRUPTS) + .long(options::IGNORE_INTERRUPTS) + .short("i") + .help("ignore interrupt signals (ignored on non-Unix platforms)"), + ) + .arg(Arg::with_name(options::FILE).multiple(true)) +} diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index f5f24d94436..86ca2f5b815 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -8,22 +8,17 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use retain_mut::RetainMut; use std::fs::OpenOptions; use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write}; use std::path::{Path, PathBuf}; +pub mod app; + #[cfg(unix)] use uucore::libc; -static ABOUT: &str = "Copy standard input to each FILE, and also to standard output."; - -mod options { - pub const APPEND: &str = "append"; - pub const IGNORE_INTERRUPTS: &str = "ignore-interrupts"; - pub const FILE: &str = "file"; -} +use crate::app::{get_app, options}; #[allow(dead_code)] struct Options { @@ -39,24 +34,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .after_help("If a FILE is -, it refers to a file named - .") - .arg( - Arg::with_name(options::APPEND) - .long(options::APPEND) - .short("a") - .help("append to the given FILEs, do not overwrite"), - ) - .arg( - Arg::with_name(options::IGNORE_INTERRUPTS) - .long(options::IGNORE_INTERRUPTS) - .short("i") - .help("ignore interrupt signals (ignored on non-Unix platforms)"), - ) - .arg(Arg::with_name(options::FILE).multiple(true)) .get_matches_from(args); let options = Options { diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index e1f6e62e7a6..2d94d8ff162 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/test.rs" [dependencies] +clap = "2.33" libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/test/src/app.rs b/src/uu/test/src/app.rs new file mode 100644 index 00000000000..ac7e0c2d7da --- /dev/null +++ b/src/uu/test/src/app.rs @@ -0,0 +1,7 @@ +use clap::{App, AppSettings}; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .setting(AppSettings::DisableHelpFlags) + .setting(AppSettings::DisableVersion) +} diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index acf0f7eca89..fe0df15e0b7 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -7,7 +7,7 @@ // file that was distributed with this source code. // spell-checker:ignore (vars) FiletestOp StrlenOp - +pub mod app; mod parser; use parser::{parse, Symbol}; diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index a46b029bdc5..74330c8d732 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -20,7 +20,6 @@ libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["process", "signals"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } - [[bin]] name = "timeout" path = "src/main.rs" diff --git a/src/uu/timeout/src/app.rs b/src/uu/timeout/src/app.rs new file mode 100644 index 00000000000..ecc8cdc071e --- /dev/null +++ b/src/uu/timeout/src/app.rs @@ -0,0 +1,56 @@ +use clap::{crate_version, App, AppSettings, Arg}; + +const ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; + +pub mod options { + pub const FOREGROUND: &str = "foreground"; + pub const KILL_AFTER: &str = "kill-after"; + pub const SIGNAL: &str = "signal"; + pub const PRESERVE_STATUS: &str = "preserve-status"; + + // Positional args. + pub const DURATION: &str = "duration"; + pub const COMMAND: &str = "command"; + pub const ARGS: &str = "args"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::FOREGROUND) + .long(options::FOREGROUND) + .help("when not running timeout directly from a shell prompt, allow COMMAND to read from the TTY and get TTY signals; in this mode, children of COMMAND will not be timed out") + ) + .arg( + Arg::with_name(options::KILL_AFTER) + .short("k") + .takes_value(true)) + .arg( + Arg::with_name(options::PRESERVE_STATUS) + .long(options::PRESERVE_STATUS) + .help("exit with the same status as COMMAND, even when the command times out") + ) + .arg( + Arg::with_name(options::SIGNAL) + .short("s") + .long(options::SIGNAL) + .help("specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals") + .takes_value(true) + ) + .arg( + Arg::with_name(options::DURATION) + .index(1) + .required(true) + ) + .arg( + Arg::with_name(options::COMMAND) + .index(2) + .required(true) + ) + .arg( + Arg::with_name(options::ARGS).multiple(true) + ) + .setting(AppSettings::TrailingVarArg) +} diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index afe560ee579..3307c301426 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -12,7 +12,7 @@ extern crate uucore; extern crate clap; -use clap::{crate_version, App, AppSettings, Arg}; +use app::get_app; use std::io::ErrorKind; use std::process::{Command, Stdio}; use std::time::Duration; @@ -20,7 +20,9 @@ use uucore::process::ChildExt; use uucore::signals::signal_by_name_or_value; use uucore::InvalidEncodingHandling; -static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; +use crate::app::options; + +pub mod app; fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) @@ -28,18 +30,6 @@ fn get_usage() -> String { const ERR_EXIT_STATUS: i32 = 125; -pub mod options { - pub static FOREGROUND: &str = "foreground"; - pub static KILL_AFTER: &str = "kill-after"; - pub static SIGNAL: &str = "signal"; - pub static PRESERVE_STATUS: &str = "preserve-status"; - - // Positional args. - pub static DURATION: &str = "duration"; - pub static COMMAND: &str = "command"; - pub static ARGS: &str = "args"; -} - struct Config { foreground: bool, kill_after: Duration, @@ -103,45 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let app = App::new("timeout") - .version(crate_version!()) - .usage(&usage[..]) - .about(ABOUT) - .arg( - Arg::with_name(options::FOREGROUND) - .long(options::FOREGROUND) - .help("when not running timeout directly from a shell prompt, allow COMMAND to read from the TTY and get TTY signals; in this mode, children of COMMAND will not be timed out") - ) - .arg( - Arg::with_name(options::KILL_AFTER) - .short("k") - .takes_value(true)) - .arg( - Arg::with_name(options::PRESERVE_STATUS) - .long(options::PRESERVE_STATUS) - .help("exit with the same status as COMMAND, even when the command times out") - ) - .arg( - Arg::with_name(options::SIGNAL) - .short("s") - .long(options::SIGNAL) - .help("specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals") - .takes_value(true) - ) - .arg( - Arg::with_name(options::DURATION) - .index(1) - .required(true) - ) - .arg( - Arg::with_name(options::COMMAND) - .index(2) - .required(true) - ) - .arg( - Arg::with_name(options::ARGS).multiple(true) - ) - .setting(AppSettings::TrailingVarArg); + let app = get_app(executable!()).usage(&usage[..]); let matches = app.get_matches_from(args); diff --git a/src/uu/touch/src/app.rs b/src/uu/touch/src/app.rs new file mode 100644 index 00000000000..49d9355e3ba --- /dev/null +++ b/src/uu/touch/src/app.rs @@ -0,0 +1,96 @@ +// spell-checker:ignore (ToDO) MMDDhhmm + +use clap::{crate_version, App, Arg, ArgGroup}; + +const ABOUT: &str = "Update the access and modification times of each FILE to the current time."; +pub mod options { + // Both SOURCES and sources are needed as we need to be able to refer to the ArgGroup. + pub const SOURCES: &str = "sources"; + pub mod sources { + pub const DATE: &str = "date"; + pub const REFERENCE: &str = "reference"; + pub const CURRENT: &str = "current"; + } + pub const ACCESS: &str = "access"; + pub const MODIFICATION: &str = "modification"; + pub const NO_CREATE: &str = "no-create"; + pub const NO_DEREF: &str = "no-dereference"; + pub const TIME: &str = "time"; +} + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::ACCESS) + .short("a") + .help("change only the access time"), + ) + .arg( + Arg::with_name(options::sources::CURRENT) + .short("t") + .help("use [[CC]YY]MMDDhhmm[.ss] instead of the current time") + .value_name("STAMP") + .takes_value(true), + ) + .arg( + Arg::with_name(options::sources::DATE) + .short("d") + .long(options::sources::DATE) + .help("parse argument and use it instead of current time") + .value_name("STRING"), + ) + .arg( + Arg::with_name(options::MODIFICATION) + .short("m") + .help("change only the modification time"), + ) + .arg( + Arg::with_name(options::NO_CREATE) + .short("c") + .long(options::NO_CREATE) + .help("do not create any files"), + ) + .arg( + Arg::with_name(options::NO_DEREF) + .short("h") + .long(options::NO_DEREF) + .help( + "affect each symbolic link instead of any referenced file \ + (only for systems that can change the timestamps of a symlink)", + ), + ) + .arg( + Arg::with_name(options::sources::REFERENCE) + .short("r") + .long(options::sources::REFERENCE) + .help("use this file's times instead of the current time") + .value_name("FILE"), + ) + .arg( + Arg::with_name(options::TIME) + .long(options::TIME) + .help( + "change only the specified time: \"access\", \"atime\", or \ + \"use\" are equivalent to -a; \"modify\" or \"mtime\" are \ + equivalent to -m", + ) + .value_name("WORD") + .possible_values(&["access", "atime", "use"]) + .takes_value(true), + ) + .arg( + Arg::with_name(ARG_FILES) + .multiple(true) + .takes_value(true) + .min_values(1), + ) + .group(ArgGroup::with_name(options::SOURCES).args(&[ + options::sources::CURRENT, + options::sources::DATE, + options::sources::REFERENCE, + ])) +} diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index be4e510411a..35c1854edea 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -6,37 +6,22 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm +// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime pub extern crate filetime; #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg, ArgGroup}; use filetime::*; use std::fs::{self, File}; use std::io::Error; use std::path::Path; use std::process; -static ABOUT: &str = "Update the access and modification times of each FILE to the current time."; -pub mod options { - // Both SOURCES and sources are needed as we need to be able to refer to the ArgGroup. - pub static SOURCES: &str = "sources"; - pub mod sources { - pub static DATE: &str = "date"; - pub static REFERENCE: &str = "reference"; - pub static CURRENT: &str = "current"; - } - pub static ACCESS: &str = "access"; - pub static MODIFICATION: &str = "modification"; - pub static NO_CREATE: &str = "no-create"; - pub static NO_DEREF: &str = "no-dereference"; - pub static TIME: &str = "time"; -} +use crate::app::{get_app, options, ARG_FILES}; -static ARG_FILES: &str = "files"; +pub mod app; fn to_local(mut tm: time::Tm) -> time::Tm { tm.tm_utcoff = time::now().tm_utcoff; @@ -55,79 +40,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(options::ACCESS) - .short("a") - .help("change only the access time"), - ) - .arg( - Arg::with_name(options::sources::CURRENT) - .short("t") - .help("use [[CC]YY]MMDDhhmm[.ss] instead of the current time") - .value_name("STAMP") - .takes_value(true), - ) - .arg( - Arg::with_name(options::sources::DATE) - .short("d") - .long(options::sources::DATE) - .help("parse argument and use it instead of current time") - .value_name("STRING"), - ) - .arg( - Arg::with_name(options::MODIFICATION) - .short("m") - .help("change only the modification time"), - ) - .arg( - Arg::with_name(options::NO_CREATE) - .short("c") - .long(options::NO_CREATE) - .help("do not create any files"), - ) - .arg( - Arg::with_name(options::NO_DEREF) - .short("h") - .long(options::NO_DEREF) - .help( - "affect each symbolic link instead of any referenced file \ - (only for systems that can change the timestamps of a symlink)", - ), - ) - .arg( - Arg::with_name(options::sources::REFERENCE) - .short("r") - .long(options::sources::REFERENCE) - .help("use this file's times instead of the current time") - .value_name("FILE"), - ) - .arg( - Arg::with_name(options::TIME) - .long(options::TIME) - .help( - "change only the specified time: \"access\", \"atime\", or \ - \"use\" are equivalent to -a; \"modify\" or \"mtime\" are \ - equivalent to -m", - ) - .value_name("WORD") - .possible_values(&["access", "atime", "use"]) - .takes_value(true), - ) - .arg( - Arg::with_name(ARG_FILES) - .multiple(true) - .takes_value(true) - .min_values(1), - ) - .group(ArgGroup::with_name(options::SOURCES).args(&[ - options::sources::CURRENT, - options::sources::DATE, - options::sources::REFERENCE, - ])) .get_matches_from(args); let files: Vec = matches diff --git a/src/uu/tr/src/app.rs b/src/uu/tr/src/app.rs new file mode 100644 index 00000000000..2caa76e308e --- /dev/null +++ b/src/uu/tr/src/app.rs @@ -0,0 +1,52 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "translate or delete characters"; + +pub mod options { + pub const COMPLEMENT: &str = "complement"; + pub const DELETE: &str = "delete"; + pub const SQUEEZE: &str = "squeeze-repeats"; + pub const TRUNCATE: &str = "truncate"; + pub const SETS: &str = "sets"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::COMPLEMENT) + // .visible_short_alias('C') // TODO: requires clap "3.0.0-beta.2" + .short("c") + .long(options::COMPLEMENT) + .help("use the complement of SET1"), + ) + .arg( + Arg::with_name("C") // work around for `Arg::visible_short_alias` + .short("C") + .help("same as -c"), + ) + .arg( + Arg::with_name(options::DELETE) + .short("d") + .long(options::DELETE) + .help("delete characters in SET1, do not translate"), + ) + .arg( + Arg::with_name(options::SQUEEZE) + .long(options::SQUEEZE) + .short("s") + .help( + "replace each sequence of a repeated character that is +listed in the last specified SET, with a single occurrence +of that character", + ), + ) + .arg( + Arg::with_name(options::TRUNCATE) + .long(options::TRUNCATE) + .short("t") + .help("first truncate SET1 to length of SET2"), + ) + .arg(Arg::with_name(options::SETS).multiple(true)) +} diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 3c362dcec13..d23856d0978 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -16,25 +16,19 @@ extern crate uucore; mod expand; use bit_set::BitSet; -use clap::{crate_version, App, Arg}; use fnv::FnvHashMap; use std::io::{stdin, stdout, BufRead, BufWriter, Write}; -use crate::expand::ExpandSet; +use crate::{ + app::{get_app, options}, + expand::ExpandSet, +}; use uucore::InvalidEncodingHandling; -static ABOUT: &str = "translate or delete characters"; +pub mod app; const BUFFER_LEN: usize = 1024; -mod options { - pub const COMPLEMENT: &str = "complement"; - pub const DELETE: &str = "delete"; - pub const SQUEEZE: &str = "squeeze-repeats"; - pub const TRUNCATE: &str = "truncate"; - pub const SETS: &str = "sets"; -} - trait SymbolTranslator { fn translate(&self, c: char, prev_c: char) -> Option; } @@ -249,46 +243,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) .after_help(&after_help[..]) - .arg( - Arg::with_name(options::COMPLEMENT) - // .visible_short_alias('C') // TODO: requires clap "3.0.0-beta.2" - .short("c") - .long(options::COMPLEMENT) - .help("use the complement of SET1"), - ) - .arg( - Arg::with_name("C") // work around for `Arg::visible_short_alias` - .short("C") - .help("same as -c"), - ) - .arg( - Arg::with_name(options::DELETE) - .short("d") - .long(options::DELETE) - .help("delete characters in SET1, do not translate"), - ) - .arg( - Arg::with_name(options::SQUEEZE) - .long(options::SQUEEZE) - .short("s") - .help( - "replace each sequence of a repeated character that is - listed in the last specified SET, with a single occurrence - of that character", - ), - ) - .arg( - Arg::with_name(options::TRUNCATE) - .long(options::TRUNCATE) - .short("t") - .help("first truncate SET1 to length of SET2"), - ) - .arg(Arg::with_name(options::SETS).multiple(true)) .get_matches_from(args); let delete_flag = matches.is_present(options::DELETE); diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index 9f13318fd47..f121d56dee6 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/true.rs" [dependencies] +clap = "2.33.3" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/true/src/app.rs b/src/uu/true/src/app.rs new file mode 100644 index 00000000000..ac7e0c2d7da --- /dev/null +++ b/src/uu/true/src/app.rs @@ -0,0 +1,7 @@ +use clap::{App, AppSettings}; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .setting(AppSettings::DisableHelpFlags) + .setting(AppSettings::DisableVersion) +} diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index 7cb23f62157..b04f50f004b 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -5,6 +5,8 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +pub mod app; + pub fn uumain(_: impl uucore::Args) -> i32 { 0 } diff --git a/src/uu/truncate/src/app.rs b/src/uu/truncate/src/app.rs new file mode 100644 index 00000000000..ac27d712062 --- /dev/null +++ b/src/uu/truncate/src/app.rs @@ -0,0 +1,51 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Shrink or extend the size of each file to the specified size."; + +pub mod options { + pub const IO_BLOCKS: &str = "io-blocks"; + pub const NO_CREATE: &str = "no-create"; + pub const REFERENCE: &str = "reference"; + pub const SIZE: &str = "size"; + pub const ARG_FILES: &str = "files"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::IO_BLOCKS) + .short("o") + .long(options::IO_BLOCKS) + .help("treat SIZE as the number of I/O blocks of the file rather than bytes (NOT IMPLEMENTED)") + ) + .arg( + Arg::with_name(options::NO_CREATE) + .short("c") + .long(options::NO_CREATE) + .help("do not create files that do not exist") + ) + .arg( + Arg::with_name(options::REFERENCE) + .short("r") + .long(options::REFERENCE) + .required_unless(options::SIZE) + .help("base the size of each file on the size of RFILE") + .value_name("RFILE") + ) + .arg( + Arg::with_name(options::SIZE) + .short("s") + .long(options::SIZE) + .required_unless(options::REFERENCE) + .help("set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified") + .value_name("SIZE") + ) + .arg(Arg::with_name(options::ARG_FILES) + .value_name("FILE") + .multiple(true) + .takes_value(true) + .required(true) + .min_values(1)) +} diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index f81a95ab227..bbe95e34282 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -10,13 +10,16 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::convert::TryFrom; use std::fs::{metadata, OpenOptions}; use std::io::ErrorKind; use std::path::Path; use uucore::parse_size::{parse_size, ParseSizeError}; +use crate::app::{get_app, options}; + +pub mod app; + #[derive(Debug, Eq, PartialEq)] enum TruncateMode { Absolute(usize), @@ -53,16 +56,6 @@ impl TruncateMode { } } -static ABOUT: &str = "Shrink or extend the size of each file to the specified size."; - -pub mod options { - pub static IO_BLOCKS: &str = "io-blocks"; - pub static NO_CREATE: &str = "no-create"; - pub static REFERENCE: &str = "reference"; - pub static SIZE: &str = "size"; - pub static ARG_FILES: &str = "files"; -} - fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } @@ -93,45 +86,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let long_usage = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) .after_help(&long_usage[..]) - .arg( - Arg::with_name(options::IO_BLOCKS) - .short("o") - .long(options::IO_BLOCKS) - .help("treat SIZE as the number of I/O blocks of the file rather than bytes (NOT IMPLEMENTED)") - ) - .arg( - Arg::with_name(options::NO_CREATE) - .short("c") - .long(options::NO_CREATE) - .help("do not create files that do not exist") - ) - .arg( - Arg::with_name(options::REFERENCE) - .short("r") - .long(options::REFERENCE) - .required_unless(options::SIZE) - .help("base the size of each file on the size of RFILE") - .value_name("RFILE") - ) - .arg( - Arg::with_name(options::SIZE) - .short("s") - .long(options::SIZE) - .required_unless(options::REFERENCE) - .help("set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified") - .value_name("SIZE") - ) - .arg(Arg::with_name(options::ARG_FILES) - .value_name("FILE") - .multiple(true) - .takes_value(true) - .required(true) - .min_values(1)) .get_matches_from(args); let files: Vec = matches diff --git a/src/uu/tsort/src/app.rs b/src/uu/tsort/src/app.rs new file mode 100644 index 00000000000..52fbd30282a --- /dev/null +++ b/src/uu/tsort/src/app.rs @@ -0,0 +1,22 @@ +use clap::{crate_version, App, Arg}; + +const SUMMARY: &str = "Topological sort the strings in FILE. +Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). +If FILE is not passed in, stdin is used instead."; +const USAGE: &str = "tsort [OPTIONS] FILE"; + +pub mod options { + pub const FILE: &str = "file"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .usage(USAGE) + .about(SUMMARY) + .arg( + Arg::with_name(options::FILE) + .default_value("-") + .hidden(true), + ) +} diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 8bd6dabef5d..7d92b9c501a 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -9,37 +9,22 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; use uucore::InvalidEncodingHandling; -static SUMMARY: &str = "Topological sort the strings in FILE. -Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). -If FILE is not passed in, stdin is used instead."; -static USAGE: &str = "tsort [OPTIONS] FILE"; +use crate::app::{get_app, options}; -mod options { - pub const FILE: &str = "file"; -} +pub mod app; pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .usage(USAGE) - .about(SUMMARY) - .arg( - Arg::with_name(options::FILE) - .default_value("-") - .hidden(true), - ) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); let input = matches .value_of(options::FILE) diff --git a/src/uu/tty/src/app.rs b/src/uu/tty/src/app.rs new file mode 100644 index 00000000000..3fc1c007507 --- /dev/null +++ b/src/uu/tty/src/app.rs @@ -0,0 +1,21 @@ +use clap::{crate_version, App, Arg}; + +pub static ABOUT: &str = "Print the file name of the terminal connected to standard input."; + +pub mod options { + pub const SILENT: &str = "silent"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::SILENT) + .long(options::SILENT) + .visible_alias("quiet") + .short("s") + .help("print nothing, only return an exit status") + .required(false), + ) +} diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index edcdf091efc..54116d84f43 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -12,15 +12,12 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::ffi::CStr; use uucore::InvalidEncodingHandling; -static ABOUT: &str = "Print the file name of the terminal connected to standard input."; +use crate::app::{get_app, options}; -mod options { - pub const SILENT: &str = "silent"; -} +pub mod app; fn get_usage() -> String { format!("{0} [OPTION]...", executable!()) @@ -32,18 +29,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(options::SILENT) - .long(options::SILENT) - .visible_alias("quiet") - .short("s") - .help("print nothing, only return an exit status") - .required(false), - ) .get_matches_from(args); let silent = matches.is_present(options::SILENT); diff --git a/src/uu/uname/src/app.rs b/src/uu/uname/src/app.rs new file mode 100644 index 00000000000..4860ac41ee7 --- /dev/null +++ b/src/uu/uname/src/app.rs @@ -0,0 +1,60 @@ +// spell-checker:ignore (ToDO) nodename kernelname kernelrelease kernelversion sysname hwplatform mnrsv +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Print certain system information. With no OPTION, same as -s."; + +pub mod options { + pub const ALL: &str = "all"; + pub const KERNELNAME: &str = "kernel-name"; + pub const NODENAME: &str = "nodename"; + pub const KERNELVERSION: &str = "kernel-version"; + pub const KERNELRELEASE: &str = "kernel-release"; + pub const MACHINE: &str = "machine"; + pub const PROCESSOR: &str = "processor"; + pub const HWPLATFORM: &str = "hardware-platform"; + pub const OS: &str = "operating-system"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg(Arg::with_name(options::ALL) + .short("a") + .long(options::ALL) + .help("Behave as though all of the options -mnrsv were specified.")) + .arg(Arg::with_name(options::KERNELNAME) + .short("s") + .long(options::KERNELNAME) + .alias("sysname") // Obsolescent option in GNU uname + .help("print the kernel name.")) + .arg(Arg::with_name(options::NODENAME) + .short("n") + .long(options::NODENAME) + .help("print the nodename (the nodename may be a name that the system is known by to a communications network).")) + .arg(Arg::with_name(options::KERNELRELEASE) + .short("r") + .long(options::KERNELRELEASE) + .alias("release") // Obsolescent option in GNU uname + .help("print the operating system release.")) + .arg(Arg::with_name(options::KERNELVERSION) + .short("v") + .long(options::KERNELVERSION) + .help("print the operating system version.")) + .arg(Arg::with_name(options::HWPLATFORM) + .short("i") + .long(options::HWPLATFORM) + .help("print the hardware platform (non-portable)")) + .arg(Arg::with_name(options::MACHINE) + .short("m") + .long(options::MACHINE) + .help("print the machine hardware name.")) + .arg(Arg::with_name(options::PROCESSOR) + .short("p") + .long(options::PROCESSOR) + .help("print the processor type (non-portable)")) + .arg(Arg::with_name(options::OS) + .short("o") + .long(options::OS) + .help("print the operating system name.")) +} diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index aa591ee1818..4189bcd462b 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -13,22 +13,11 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use platform_info::*; -const ABOUT: &str = "Print certain system information. With no OPTION, same as -s."; +use crate::app::{get_app, options}; -pub mod options { - pub static ALL: &str = "all"; - pub static KERNELNAME: &str = "kernel-name"; - pub static NODENAME: &str = "nodename"; - pub static KERNELVERSION: &str = "kernel-version"; - pub static KERNELRELEASE: &str = "kernel-release"; - pub static MACHINE: &str = "machine"; - pub static PROCESSOR: &str = "processor"; - pub static HWPLATFORM: &str = "hardware-platform"; - pub static OS: &str = "operating-system"; -} +pub mod app; #[cfg(target_os = "linux")] const HOST_OS: &str = "GNU/Linux"; @@ -47,48 +36,8 @@ const HOST_OS: &str = "Redox"; pub fn uumain(args: impl uucore::Args) -> i32 { let usage = format!("{} [OPTION]...", executable!()); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg(Arg::with_name(options::ALL) - .short("a") - .long(options::ALL) - .help("Behave as though all of the options -mnrsv were specified.")) - .arg(Arg::with_name(options::KERNELNAME) - .short("s") - .long(options::KERNELNAME) - .alias("sysname") // Obsolescent option in GNU uname - .help("print the kernel name.")) - .arg(Arg::with_name(options::NODENAME) - .short("n") - .long(options::NODENAME) - .help("print the nodename (the nodename may be a name that the system is known by to a communications network).")) - .arg(Arg::with_name(options::KERNELRELEASE) - .short("r") - .long(options::KERNELRELEASE) - .alias("release") // Obsolescent option in GNU uname - .help("print the operating system release.")) - .arg(Arg::with_name(options::KERNELVERSION) - .short("v") - .long(options::KERNELVERSION) - .help("print the operating system version.")) - .arg(Arg::with_name(options::HWPLATFORM) - .short("i") - .long(options::HWPLATFORM) - .help("print the hardware platform (non-portable)")) - .arg(Arg::with_name(options::MACHINE) - .short("m") - .long(options::MACHINE) - .help("print the machine hardware name.")) - .arg(Arg::with_name(options::PROCESSOR) - .short("p") - .long(options::PROCESSOR) - .help("print the processor type (non-portable)")) - .arg(Arg::with_name(options::OS) - .short("o") - .long(options::OS) - .help("print the operating system name.")) .get_matches_from(args); let uname = return_if_err!(1, PlatformInfo::new()); diff --git a/src/uu/unexpand/src/app.rs b/src/uu/unexpand/src/app.rs new file mode 100644 index 00000000000..f1413334f01 --- /dev/null +++ b/src/uu/unexpand/src/app.rs @@ -0,0 +1,49 @@ +use clap::{crate_version, App, Arg}; + +const NAME: &str = "unexpand"; +const USAGE: &str = "unexpand [OPTION]... [FILE]..."; +const SUMMARY: &str = "Convert blanks in each FILE to tabs, writing to standard output.\n + With no FILE, or when FILE is -, read standard input."; + +pub mod options { + pub const FILE: &str = "file"; + pub const ALL: &str = "all"; + pub const FIRST_ONLY: &str = "first-only"; + pub const TABS: &str = "tabs"; + pub const NO_UTF8: &str = "no-utf8"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .name(NAME) + .version(crate_version!()) + .usage(USAGE) + .about(SUMMARY) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .arg( + Arg::with_name(options::ALL) + .short("a") + .long(options::ALL) + .help("convert all blanks, instead of just initial blanks") + .takes_value(false), + ) + .arg( + Arg::with_name(options::FIRST_ONLY) + .long(options::FIRST_ONLY) + .help("convert only leading sequences of blanks (overrides -a)") + .takes_value(false), + ) + .arg( + Arg::with_name(options::TABS) + .short("t") + .long(options::TABS) + .long_help("use comma separated LIST of tab positions or have tabs N characters apart instead of 8 (enables -a)") + .takes_value(true) + ) + .arg( + Arg::with_name(options::NO_UTF8) + .short("U") + .long(options::NO_UTF8) + .takes_value(false) + .help("interpret input file as 8-bit ASCII rather than UTF-8")) +} diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 92b3c7520e2..62b070404be 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -11,17 +11,15 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Stdout, Write}; use std::str::from_utf8; use unicode_width::UnicodeWidthChar; use uucore::InvalidEncodingHandling; -static NAME: &str = "unexpand"; -static USAGE: &str = "unexpand [OPTION]... [FILE]..."; -static SUMMARY: &str = "Convert blanks in each FILE to tabs, writing to standard output.\n - With no FILE, or when FILE is -, read standard input."; +use crate::app::{get_app, options}; + +pub mod app; const DEFAULT_TABSTOP: usize = 8; @@ -49,14 +47,6 @@ fn tabstops_parse(s: String) -> Vec { nums } -mod options { - pub const FILE: &str = "file"; - pub const ALL: &str = "all"; - pub const FIRST_ONLY: &str = "first-only"; - pub const TABS: &str = "tabs"; - pub const NO_UTF8: &str = "no-utf8"; -} - struct Options { files: Vec, tabstops: Vec, @@ -94,39 +84,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = App::new(executable!()) - .name(NAME) - .version(crate_version!()) - .usage(USAGE) - .about(SUMMARY) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .arg( - Arg::with_name(options::ALL) - .short("a") - .long(options::ALL) - .help("convert all blanks, instead of just initial blanks") - .takes_value(false), - ) - .arg( - Arg::with_name(options::FIRST_ONLY) - .long(options::FIRST_ONLY) - .help("convert only leading sequences of blanks (overrides -a)") - .takes_value(false), - ) - .arg( - Arg::with_name(options::TABS) - .short("t") - .long(options::TABS) - .long_help("use comma separated LIST of tab positions or have tabs N characters apart instead of 8 (enables -a)") - .takes_value(true) - ) - .arg( - Arg::with_name(options::NO_UTF8) - .short("U") - .long(options::NO_UTF8) - .takes_value(false) - .help("interpret input file as 8-bit ASCII rather than UTF-8")) - .get_matches_from(args); + let matches = get_app(executable!()).get_matches_from(args); unexpand(Options::new(matches)); diff --git a/src/uu/uniq/src/app.rs b/src/uu/uniq/src/app.rs new file mode 100644 index 00000000000..e95c46a74c2 --- /dev/null +++ b/src/uu/uniq/src/app.rs @@ -0,0 +1,120 @@ +use clap::{crate_version, App, Arg}; +use strum_macros::{AsRefStr, EnumString}; + +static ABOUT: &str = "Report or omit repeated lines."; +pub mod options { + pub static ALL_REPEATED: &str = "all-repeated"; + pub static CHECK_CHARS: &str = "check-chars"; + pub static COUNT: &str = "count"; + pub static IGNORE_CASE: &str = "ignore-case"; + pub static REPEATED: &str = "repeated"; + pub static SKIP_FIELDS: &str = "skip-fields"; + pub static SKIP_CHARS: &str = "skip-chars"; + pub static UNIQUE: &str = "unique"; + pub static ZERO_TERMINATED: &str = "zero-terminated"; + pub static GROUP: &str = "group"; +} + +pub const ARG_FILES: &str = "files"; + +#[derive(PartialEq, Clone, Copy, AsRefStr, EnumString)] +#[strum(serialize_all = "snake_case")] +pub enum Delimiters { + Append, + Prepend, + Separate, + Both, + None, +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::ALL_REPEATED) + .short("D") + .long(options::ALL_REPEATED) + .possible_values(&[ + Delimiters::None.as_ref(), Delimiters::Prepend.as_ref(), Delimiters::Separate.as_ref() + ]) + .help("print all duplicate lines. Delimiting is done with blank lines. [default: none]") + .value_name("delimit-method") + .min_values(0) + .max_values(1), + ) + .arg( + Arg::with_name(options::GROUP) + .long(options::GROUP) + .possible_values(&[ + Delimiters::Separate.as_ref(), Delimiters::Prepend.as_ref(), + Delimiters::Append.as_ref(), Delimiters::Both.as_ref() + ]) + .help("show all items, separating groups with an empty line. [default: separate]") + .value_name("group-method") + .min_values(0) + .max_values(1) + .conflicts_with_all(&[ + options::REPEATED, + options::ALL_REPEATED, + options::UNIQUE, + ]), + ) + .arg( + Arg::with_name(options::CHECK_CHARS) + .short("w") + .long(options::CHECK_CHARS) + .help("compare no more than N characters in lines") + .value_name("N"), + ) + .arg( + Arg::with_name(options::COUNT) + .short("c") + .long(options::COUNT) + .help("prefix lines by the number of occurrences"), + ) + .arg( + Arg::with_name(options::IGNORE_CASE) + .short("i") + .long(options::IGNORE_CASE) + .help("ignore differences in case when comparing"), + ) + .arg( + Arg::with_name(options::REPEATED) + .short("d") + .long(options::REPEATED) + .help("only print duplicate lines"), + ) + .arg( + Arg::with_name(options::SKIP_CHARS) + .short("s") + .long(options::SKIP_CHARS) + .help("avoid comparing the first N characters") + .value_name("N"), + ) + .arg( + Arg::with_name(options::SKIP_FIELDS) + .short("f") + .long(options::SKIP_FIELDS) + .help("avoid comparing the first N fields") + .value_name("N"), + ) + .arg( + Arg::with_name(options::UNIQUE) + .short("u") + .long(options::UNIQUE) + .help("only print unique lines"), + ) + .arg( + Arg::with_name(options::ZERO_TERMINATED) + .short("z") + .long(options::ZERO_TERMINATED) + .help("end lines with 0 byte, not newline"), + ) + .arg( + Arg::with_name(ARG_FILES) + .multiple(true) + .takes_value(true) + .max_values(2), + ) +} diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index aee024dd49c..806f926932b 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -8,38 +8,15 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::ArgMatches; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Result, Write}; use std::path::Path; use std::str::FromStr; -use strum_macros::{AsRefStr, EnumString}; - -static ABOUT: &str = "Report or omit repeated lines."; -pub mod options { - pub static ALL_REPEATED: &str = "all-repeated"; - pub static CHECK_CHARS: &str = "check-chars"; - pub static COUNT: &str = "count"; - pub static IGNORE_CASE: &str = "ignore-case"; - pub static REPEATED: &str = "repeated"; - pub static SKIP_FIELDS: &str = "skip-fields"; - pub static SKIP_CHARS: &str = "skip-chars"; - pub static UNIQUE: &str = "unique"; - pub static ZERO_TERMINATED: &str = "zero-terminated"; - pub static GROUP: &str = "group"; -} -static ARG_FILES: &str = "files"; +use crate::app::{get_app, options, Delimiters, ARG_FILES}; -#[derive(PartialEq, Clone, Copy, AsRefStr, EnumString)] -#[strum(serialize_all = "snake_case")] -enum Delimiters { - Append, - Prepend, - Separate, - Both, - None, -} +pub mod app; struct Uniq { repeats_only: bool, @@ -238,97 +215,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let long_usage = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) .after_help(&long_usage[..]) - .arg( - Arg::with_name(options::ALL_REPEATED) - .short("D") - .long(options::ALL_REPEATED) - .possible_values(&[ - Delimiters::None.as_ref(), Delimiters::Prepend.as_ref(), Delimiters::Separate.as_ref() - ]) - .help("print all duplicate lines. Delimiting is done with blank lines. [default: none]") - .value_name("delimit-method") - .min_values(0) - .max_values(1), - ) - .arg( - Arg::with_name(options::GROUP) - .long(options::GROUP) - .possible_values(&[ - Delimiters::Separate.as_ref(), Delimiters::Prepend.as_ref(), - Delimiters::Append.as_ref(), Delimiters::Both.as_ref() - ]) - .help("show all items, separating groups with an empty line. [default: separate]") - .value_name("group-method") - .min_values(0) - .max_values(1) - .conflicts_with_all(&[ - options::REPEATED, - options::ALL_REPEATED, - options::UNIQUE, - ]), - ) - .arg( - Arg::with_name(options::CHECK_CHARS) - .short("w") - .long(options::CHECK_CHARS) - .help("compare no more than N characters in lines") - .value_name("N"), - ) - .arg( - Arg::with_name(options::COUNT) - .short("c") - .long(options::COUNT) - .help("prefix lines by the number of occurrences"), - ) - .arg( - Arg::with_name(options::IGNORE_CASE) - .short("i") - .long(options::IGNORE_CASE) - .help("ignore differences in case when comparing"), - ) - .arg( - Arg::with_name(options::REPEATED) - .short("d") - .long(options::REPEATED) - .help("only print duplicate lines"), - ) - .arg( - Arg::with_name(options::SKIP_CHARS) - .short("s") - .long(options::SKIP_CHARS) - .help("avoid comparing the first N characters") - .value_name("N"), - ) - .arg( - Arg::with_name(options::SKIP_FIELDS) - .short("f") - .long(options::SKIP_FIELDS) - .help("avoid comparing the first N fields") - .value_name("N"), - ) - .arg( - Arg::with_name(options::UNIQUE) - .short("u") - .long(options::UNIQUE) - .help("only print unique lines"), - ) - .arg( - Arg::with_name(options::ZERO_TERMINATED) - .short("z") - .long(options::ZERO_TERMINATED) - .help("end lines with 0 byte, not newline"), - ) - .arg( - Arg::with_name(ARG_FILES) - .multiple(true) - .takes_value(true) - .max_values(2), - ) .get_matches_from(args); let files: Vec = matches diff --git a/src/uu/unlink/src/app.rs b/src/uu/unlink/src/app.rs new file mode 100644 index 00000000000..27a55c0f56d --- /dev/null +++ b/src/uu/unlink/src/app.rs @@ -0,0 +1,11 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Unlink the file at [FILE]."; +pub const OPT_PATH: &str = "FILE"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg(Arg::with_name(OPT_PATH).hidden(true).multiple(true)) +} diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 343f2653fbc..6401b09a0f1 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -12,16 +12,15 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use libc::{lstat, stat, unlink}; use libc::{S_IFLNK, S_IFMT, S_IFREG}; use std::ffi::CString; use std::io::{Error, ErrorKind}; use uucore::InvalidEncodingHandling; -static ABOUT: &str = "Unlink the file at [FILE]."; -static OPT_PATH: &str = "FILE"; +use crate::app::{get_app, OPT_PATH}; +pub mod app; fn get_usage() -> String { format!("{} [OPTION]... FILE", executable!()) } @@ -33,11 +32,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg(Arg::with_name(OPT_PATH).hidden(true).multiple(true)) .get_matches_from(args); let paths: Vec = matches diff --git a/src/uu/uptime/src/app.rs b/src/uu/uptime/src/app.rs new file mode 100644 index 00000000000..b0aeab18410 --- /dev/null +++ b/src/uu/uptime/src/app.rs @@ -0,0 +1,20 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Display the current time, the length of time the system has been up,\n\ + the number of users on the system, and the average number of jobs\n\ + in the run queue over the last 1, 5 and 15 minutes."; +pub mod options { + pub const SINCE: &str = "since"; +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::SINCE) + .short("s") + .long(options::SINCE) + .help("system up since"), + ) +} diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 3683a4de0e1..266c24ee083 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -9,7 +9,6 @@ // spell-checker:ignore (ToDO) getloadavg upsecs updays nusers loadavg boottime uphours upmins use chrono::{Local, TimeZone, Utc}; -use clap::{crate_version, App, Arg}; #[macro_use] extern crate uucore; @@ -17,16 +16,13 @@ extern crate uucore; pub use uucore::libc; use uucore::libc::time_t; -static ABOUT: &str = "Display the current time, the length of time the system has been up,\n\ - the number of users on the system, and the average number of jobs\n\ - in the run queue over the last 1, 5 and 15 minutes."; -pub mod options { - pub static SINCE: &str = "since"; -} +pub mod app; #[cfg(unix)] use uucore::libc::getloadavg; +use crate::app::{get_app, options}; + #[cfg(windows)] extern "C" { fn GetTickCount() -> uucore::libc::uint32_t; @@ -38,16 +34,8 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(options::SINCE) - .short("s") - .long(options::SINCE) - .help("system up since"), - ) .get_matches_from(args); let (boot_time, user_count) = process_utmpx(); diff --git a/src/uu/users/src/app.rs b/src/uu/users/src/app.rs new file mode 100644 index 00000000000..ef2f34b49bd --- /dev/null +++ b/src/uu/users/src/app.rs @@ -0,0 +1,12 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Print the user names of users currently logged in to the current host"; + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg(Arg::with_name(ARG_FILES).takes_value(true).max_values(1)) +} diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 5b1f1c03778..72acb4d8371 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -11,12 +11,11 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; use uucore::utmpx::{self, Utmpx}; -static ABOUT: &str = "Print the user names of users currently logged in to the current host"; +use crate::app::{get_app, ARG_FILES}; -static ARG_FILES: &str = "files"; +pub mod app; fn get_usage() -> String { format!("{0} [FILE]", executable!()) @@ -34,12 +33,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) .after_help(&after_help[..]) - .arg(Arg::with_name(ARG_FILES).takes_value(true).max_values(1)) .get_matches_from(args); let files: Vec = matches diff --git a/src/uu/wc/src/app.rs b/src/uu/wc/src/app.rs new file mode 100644 index 00000000000..ae1290d8be5 --- /dev/null +++ b/src/uu/wc/src/app.rs @@ -0,0 +1,51 @@ +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Display newline, word, and byte counts for each FILE, and a total line if +more than one FILE is specified."; + +pub mod options { + pub const BYTES: &str = "bytes"; + pub const CHAR: &str = "chars"; + pub const LINES: &str = "lines"; + pub const MAX_LINE_LENGTH: &str = "max-line-length"; + pub const WORDS: &str = "words"; +} + +pub const ARG_FILES: &str = "files"; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::BYTES) + .short("c") + .long(options::BYTES) + .help("print the byte counts"), + ) + .arg( + Arg::with_name(options::CHAR) + .short("m") + .long(options::CHAR) + .help("print the character counts"), + ) + .arg( + Arg::with_name(options::LINES) + .short("l") + .long(options::LINES) + .help("print the newline counts"), + ) + .arg( + Arg::with_name(options::MAX_LINE_LENGTH) + .short("L") + .long(options::MAX_LINE_LENGTH) + .help("print the length of the longest line"), + ) + .arg( + Arg::with_name(options::WORDS) + .short("w") + .long(options::WORDS) + .help("print the word counts"), + ) + .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) +} diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index d1e1f75ca7e..2d84387dd65 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -11,17 +11,22 @@ extern crate uucore; mod count_bytes; mod countable; mod word_count; +use app::options; use count_bytes::count_bytes_fast; use countable::WordCountable; use word_count::{TitledWordCount, WordCount}; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::ArgMatches; use thiserror::Error; use std::fs::{self, File}; use std::io::{self, ErrorKind, Write}; use std::path::Path; +use crate::app::{get_app, ARG_FILES}; + +pub mod app; + /// The minimum character width for formatting counts when reading from stdin. const MINIMUM_WIDTH: usize = 7; @@ -82,19 +87,6 @@ impl Settings { } } -static ABOUT: &str = "Display newline, word, and byte counts for each FILE, and a total line if -more than one FILE is specified."; - -pub mod options { - pub static BYTES: &str = "bytes"; - pub static CHAR: &str = "chars"; - pub static LINES: &str = "lines"; - pub static MAX_LINE_LENGTH: &str = "max-line-length"; - pub static WORDS: &str = "words"; -} - -static ARG_FILES: &str = "files"; - fn get_usage() -> String { format!( "{0} [OPTION]... [FILE]... @@ -134,41 +126,8 @@ impl Input { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) - .arg( - Arg::with_name(options::BYTES) - .short("c") - .long(options::BYTES) - .help("print the byte counts"), - ) - .arg( - Arg::with_name(options::CHAR) - .short("m") - .long(options::CHAR) - .help("print the character counts"), - ) - .arg( - Arg::with_name(options::LINES) - .short("l") - .long(options::LINES) - .help("print the newline counts"), - ) - .arg( - Arg::with_name(options::MAX_LINE_LENGTH) - .short("L") - .long(options::MAX_LINE_LENGTH) - .help("print the length of the longest line"), - ) - .arg( - Arg::with_name(options::WORDS) - .short("w") - .long(options::WORDS) - .help("print the word counts"), - ) - .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) .get_matches_from(args); let mut inputs: Vec = matches diff --git a/src/uu/who/src/app.rs b/src/uu/who/src/app.rs new file mode 100644 index 00000000000..72a11f66b52 --- /dev/null +++ b/src/uu/who/src/app.rs @@ -0,0 +1,129 @@ +// spell-checker:ignore (ToDO) hostnames runlevel mesg + +use clap::{crate_version, App, Arg}; + +const ABOUT: &str = "Print information about users who are currently logged in."; + +#[cfg(any(target_os = "linux"))] +const RUNLEVEL_HELP: &str = "print current runlevel"; +#[cfg(not(target_os = "linux"))] +const RUNLEVEL_HELP: &str = "print current runlevel (This is meaningless on non Linux)"; + +pub 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"; + 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 +} + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .version(crate_version!()) + .about(ABOUT) + .arg( + Arg::with_name(options::ALL) + .long(options::ALL) + .short("a") + .help("same as -b -d --login -p -r -t -T -u"), + ) + .arg( + Arg::with_name(options::BOOT) + .long(options::BOOT) + .short("b") + .help("time of last system boot"), + ) + .arg( + Arg::with_name(options::DEAD) + .long(options::DEAD) + .short("d") + .help("print dead processes"), + ) + .arg( + Arg::with_name(options::HEADING) + .long(options::HEADING) + .short("H") + .help("print line of column headings"), + ) + .arg( + Arg::with_name(options::LOGIN) + .long(options::LOGIN) + .short("l") + .help("print system login processes"), + ) + .arg( + Arg::with_name(options::LOOKUP) + .long(options::LOOKUP) + .help("attempt to canonicalize hostnames via DNS"), + ) + .arg( + Arg::with_name(options::ONLY_HOSTNAME_USER) + .short("m") + .help("only hostname and user associated with stdin"), + ) + .arg( + Arg::with_name(options::PROCESS) + .long(options::PROCESS) + .short("p") + .help("print active processes spawned by init"), + ) + .arg( + Arg::with_name(options::COUNT) + .long(options::COUNT) + .short("q") + .help("all login names and number of users logged on"), + ) + .arg( + Arg::with_name(options::RUNLEVEL) + .long(options::RUNLEVEL) + .short("r") + .help(RUNLEVEL_HELP), + ) + .arg( + Arg::with_name(options::SHORT) + .long(options::SHORT) + .short("s") + .help("print only name, line, and time (default)"), + ) + .arg( + Arg::with_name(options::TIME) + .long(options::TIME) + .short("t") + .help("print last system clock change"), + ) + .arg( + Arg::with_name(options::USERS) + .long(options::USERS) + .short("u") + .help("list users logged in"), + ) + .arg( + Arg::with_name(options::MESG) + .long(options::MESG) + .short("T") + // .visible_short_alias('w') // TODO: requires clap "3.0.0-beta.2" + .visible_aliases(&["message", "writable"]) + .help("add user's message status as +, - or ?"), + ) + .arg( + 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), + ) +} diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 44f5654383e..b265025a6ac 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -12,37 +12,15 @@ extern crate uucore; use uucore::libc::{ttyname, STDIN_FILENO, S_IWGRP}; use uucore::utmpx::{self, time, Utmpx}; -use clap::{crate_version, App, Arg}; use std::borrow::Cow; use std::ffi::CStr; use std::os::unix::fs::MetadataExt; use std::path::PathBuf; use uucore::InvalidEncodingHandling; -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"; - 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 -} - -static ABOUT: &str = "Print information about users who are currently logged in."; +use crate::app::{get_app, options}; -#[cfg(any(target_os = "linux"))] -static RUNLEVEL_HELP: &str = "print current runlevel"; -#[cfg(not(target_os = "linux"))] -static RUNLEVEL_HELP: &str = "print current runlevel (This is meaningless on non Linux)"; +pub mod app; fn get_usage() -> String { format!("{0} [OPTION]... [ FILE | ARG1 ARG2 ]", executable!()) @@ -64,106 +42,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let after_help = get_long_usage(); - let matches = App::new(executable!()) - .version(crate_version!()) - .about(ABOUT) + let matches = get_app(executable!()) .usage(&usage[..]) .after_help(&after_help[..]) - .arg( - Arg::with_name(options::ALL) - .long(options::ALL) - .short("a") - .help("same as -b -d --login -p -r -t -T -u"), - ) - .arg( - Arg::with_name(options::BOOT) - .long(options::BOOT) - .short("b") - .help("time of last system boot"), - ) - .arg( - Arg::with_name(options::DEAD) - .long(options::DEAD) - .short("d") - .help("print dead processes"), - ) - .arg( - Arg::with_name(options::HEADING) - .long(options::HEADING) - .short("H") - .help("print line of column headings"), - ) - .arg( - Arg::with_name(options::LOGIN) - .long(options::LOGIN) - .short("l") - .help("print system login processes"), - ) - .arg( - Arg::with_name(options::LOOKUP) - .long(options::LOOKUP) - .help("attempt to canonicalize hostnames via DNS"), - ) - .arg( - Arg::with_name(options::ONLY_HOSTNAME_USER) - .short("m") - .help("only hostname and user associated with stdin"), - ) - .arg( - Arg::with_name(options::PROCESS) - .long(options::PROCESS) - .short("p") - .help("print active processes spawned by init"), - ) - .arg( - Arg::with_name(options::COUNT) - .long(options::COUNT) - .short("q") - .help("all login names and number of users logged on"), - ) - .arg( - Arg::with_name(options::RUNLEVEL) - .long(options::RUNLEVEL) - .short("r") - .help(RUNLEVEL_HELP), - ) - .arg( - Arg::with_name(options::SHORT) - .long(options::SHORT) - .short("s") - .help("print only name, line, and time (default)"), - ) - .arg( - Arg::with_name(options::TIME) - .long(options::TIME) - .short("t") - .help("print last system clock change"), - ) - .arg( - Arg::with_name(options::USERS) - .long(options::USERS) - .short("u") - .help("list users logged in"), - ) - .arg( - Arg::with_name(options::MESG) - .long(options::MESG) - .short("T") - // .visible_short_alias('w') // TODO: requires clap "3.0.0-beta.2" - .visible_aliases(&["message", "writable"]) - .help("add user's message status as +, - or ?"), - ) - .arg( - 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), - ) .get_matches_from(args); let files: Vec = matches diff --git a/src/uu/whoami/src/app.rs b/src/uu/whoami/src/app.rs new file mode 100644 index 00000000000..f7be27e2e64 --- /dev/null +++ b/src/uu/whoami/src/app.rs @@ -0,0 +1,7 @@ +use clap::{crate_description, crate_version, App}; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .about(crate_description!()) + .version(crate_version!()) +} diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index 383fb40b50a..dae00f1e199 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -7,15 +7,16 @@ /* last synced with: whoami (GNU coreutils) 8.21 */ -#[macro_use] -extern crate clap; #[macro_use] extern crate uucore; +use crate::app::get_app; + +pub mod app; mod platform; pub fn uumain(args: impl uucore::Args) -> i32 { - let app = app_from_crate!(); + let app = get_app(executable!()); if let Err(err) = app.get_matches_from_safe(args) { if err.kind == clap::ErrorKind::HelpDisplayed diff --git a/src/uu/yes/src/app.rs b/src/uu/yes/src/app.rs new file mode 100644 index 00000000000..101cafcbc4d --- /dev/null +++ b/src/uu/yes/src/app.rs @@ -0,0 +1,8 @@ +use clap::{crate_description, crate_version, App, Arg}; + +pub fn get_app(app_name: &str) -> App { + App::new(app_name) + .about(crate_description!()) + .version(crate_version!()) + .arg(Arg::with_name("STRING").index(1).multiple(true)) +} diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index 1fc2d92bced..deae802a55b 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -7,22 +7,23 @@ /* last synced with: yes (GNU coreutils) 8.13 */ -#[macro_use] -extern crate clap; #[macro_use] extern crate uucore; -use clap::Arg; use std::borrow::Cow; use std::io::{self, Write}; use uucore::zero_copy::ZeroCopyWriter; +use crate::app::get_app; + +pub mod app; + // it's possible that using a smaller or larger buffer might provide better performance on some // systems, but honestly this is good enough const BUF_SIZE: usize = 16 * 1024; pub fn uumain(args: impl uucore::Args) -> i32 { - let app = app_from_crate!().arg(Arg::with_name("STRING").index(1).multiple(true)); + let app = get_app(executable!()); let matches = match app.get_matches_from_safe(args) { Ok(m) => m,