From cfc3d52be40958d7042c0f56368212a41c0e8dcf Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci Date: Sat, 3 Apr 2021 20:19:30 +0300 Subject: [PATCH 1/4] cut: move to clap --- Cargo.lock | 1 + src/uu/cut/Cargo.toml | 1 + src/uu/cut/src/cut.rs | 150 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 124 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea1ee53ae..15f1a606d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1679,6 +1679,7 @@ dependencies = [ name = "uu_cut" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index 9cc852d2e..d892ddeb5 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/cut.rs" [dependencies] +clap = "2.33" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 95411e3fb..2b65a3347 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -10,6 +10,7 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, Read, Stdout, Write}; use std::path::Path; @@ -20,6 +21,8 @@ use uucore::ranges::Range; mod buffer; mod searcher; +static NAME: &str = "cut"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); static SYNTAX: &str = "[-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+"; static SUMMARY: &str = @@ -422,34 +425,119 @@ 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 LEGACY_OPTION: &str = "legacy-option"; + 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(); - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optopt("b", "bytes", "filter byte columns from the input source", "sequence") - .optopt("c", "characters", "alias for character mode", "sequence") - .optopt("d", "delimiter", "specify the delimiter character that separates fields in the input source. Defaults to Tab.", "delimiter") - .optopt("f", "fields", "filter field columns from the input source", "sequence") - .optflag("n", "", "legacy option - has no effect.") - .optflag("", "complement", "invert the filter - instead of displaying only the filtered columns, display all but those columns") - .optflag("s", "only-delimited", "in field mode, only print lines which contain the delimiter") - .optflag("z", "zero-terminated", "instead of filtering columns based on line, filter columns based on \\0 (NULL character)") - .optopt("", "output-delimiter", "in field mode, replace the delimiter in output lines with this option's argument", "new delimiter") - .parse(args); - let complement = matches.opt_present("complement"); + let matches = App::new(executable!()) + .name(NAME) + .version(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"), + ) + .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"), + ) + .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"), + ) + .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"), + ) + .arg( + Arg::with_name(options::LEGACY_OPTION) + .short("n") + .long(options::LEGACY_OPTION) + .help("legacy option - has no effect.") + .takes_value(false) + ) + .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) + + ) + .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) + ) + .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) + ) + .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") + ) + .arg( + Arg::with_name(options::FILE) + .hidden(true) + .multiple(true) + ) + .get_matches_from(args); + + let complement = matches.is_present("complement"); let mode_parse = match ( - matches.opt_str("bytes"), - matches.opt_str("characters"), - matches.opt_str("fields"), + matches.value_of(options::BYTES), + matches.value_of(options::CHARACTERS), + matches.value_of(options::FIELDS), ) { (Some(byte_ranges), None, None) => { list_to_ranges(&byte_ranges[..], complement).map(|ranges| { Mode::Bytes( ranges, Options { - out_delim: matches.opt_str("output-delimiter"), - zero_terminated: matches.opt_present("zero-terminated"), + out_delim: Some(matches.value_of(options::OUTPUT_DELIMITER).unwrap_or_default().to_owned()), + zero_terminated: matches.is_present(options::ZERO_TERMINATED), }, ) }) @@ -459,29 +547,29 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Mode::Characters( ranges, Options { - out_delim: matches.opt_str("output-delimiter"), - zero_terminated: matches.opt_present("zero-terminated"), + out_delim: Some(matches.value_of(options::OUTPUT_DELIMITER).unwrap_or_default().to_owned()), + zero_terminated: matches.is_present(options::ZERO_TERMINATED), }, ) }) } (None, None, Some(field_ranges)) => { list_to_ranges(&field_ranges[..], complement).and_then(|ranges| { - let out_delim = match matches.opt_str("output-delimiter") { + let out_delim = match matches.value_of(options::OUTPUT_DELIMITER) { Some(s) => { if s.is_empty() { Some("\0".to_owned()) } else { - Some(s) + Some(s.to_owned()) } } None => None, }; - let only_delimited = matches.opt_present("only-delimited"); - let zero_terminated = matches.opt_present("zero-terminated"); + let only_delimited = matches.is_present(options::ONLY_DELIMITED); + let zero_terminated = matches.is_present(options::ZERO_TERMINATED); - match matches.opt_str("delimiter") { + match matches.value_of(options::DELIMITER) { Some(delim) => { if delim.chars().count() > 1 { Err(msg_opt_invalid_should_be!( @@ -494,7 +582,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let delim = if delim.is_empty() { "\0".to_owned() } else { - delim + delim.to_owned() }; Ok(Mode::Fields( @@ -533,10 +621,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mode_parse = match mode_parse { Err(_) => mode_parse, Ok(mode) => match mode { - Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("delimiter") => Err( + Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.is_present("delimiter") => Err( msg_opt_only_usable_if!("printing a sequence of fields", "--delimiter", "-d"), ), - Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("only-delimited") => { + Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.is_present("only-delimited") => { Err(msg_opt_only_usable_if!( "printing a sequence of fields", "--only-delimited", @@ -547,8 +635,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }, }; + let files: Vec = matches + .values_of(options::FILE) + .unwrap_or_default() + .map(str::to_owned) + .collect(); + match mode_parse { - Ok(mode) => cut_files(matches.free, mode), + Ok(mode) => cut_files(files, mode), Err(err_msg) => { show_error!("{}", err_msg); 1 From 7e677b3e6c2b03203efe9039d05163434ac0cbcd Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci Date: Sat, 3 Apr 2021 20:21:57 +0300 Subject: [PATCH 2/4] cut: fix formatting, use constant values --- src/uu/cut/src/cut.rs | 33 +++++++++++++++++++++++++-------- src/uu/stdbuf/src/stdbuf.rs | 3 +-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 2b65a3347..3781f0c9f 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -494,7 +494,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::COMPLEMENT) .help("invert the filter - instead of displaying only the filtered columns, display all but those columns") .takes_value(false) - ) .arg( Arg::with_name(options::ONLY_DELIMITED) @@ -524,7 +523,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) .get_matches_from(args); - let complement = matches.is_present("complement"); + let complement = matches.is_present(options::COMPLEMENT); let mode_parse = match ( matches.value_of(options::BYTES), @@ -536,7 +535,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Mode::Bytes( ranges, Options { - out_delim: Some(matches.value_of(options::OUTPUT_DELIMITER).unwrap_or_default().to_owned()), + out_delim: Some( + matches + .value_of(options::OUTPUT_DELIMITER) + .unwrap_or_default() + .to_owned(), + ), zero_terminated: matches.is_present(options::ZERO_TERMINATED), }, ) @@ -547,7 +551,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Mode::Characters( ranges, Options { - out_delim: Some(matches.value_of(options::OUTPUT_DELIMITER).unwrap_or_default().to_owned()), + out_delim: Some( + matches + .value_of(options::OUTPUT_DELIMITER) + .unwrap_or_default() + .to_owned(), + ), zero_terminated: matches.is_present(options::ZERO_TERMINATED), }, ) @@ -621,10 +630,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mode_parse = match mode_parse { Err(_) => mode_parse, Ok(mode) => match mode { - Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.is_present("delimiter") => Err( - msg_opt_only_usable_if!("printing a sequence of fields", "--delimiter", "-d"), - ), - Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.is_present("only-delimited") => { + Mode::Bytes(_, _) | Mode::Characters(_, _) + if matches.is_present(options::DELIMITER) => + { + Err(msg_opt_only_usable_if!( + "printing a sequence of fields", + "--delimiter", + "-d" + )) + } + Mode::Bytes(_, _) | Mode::Characters(_, _) + if matches.is_present(options::ONLY_DELIMITED) => + { Err(msg_opt_only_usable_if!( "printing a sequence of fields", "--only-delimited", diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index a61ba967b..67ed9a838 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -80,8 +80,7 @@ fn print_version() { fn print_usage(opts: &Options) { let brief = "Run COMMAND, with modified buffering operations for its standard streams\n \ Mandatory arguments to long options are mandatory for short options too."; - let explanation = - "If MODE is 'L' the corresponding stream will be line buffered.\n \ + let explanation = "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 \ From e84b60b7d5464db90a727b432150d90324f2b000 Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci Date: Sat, 3 Apr 2021 20:30:28 +0300 Subject: [PATCH 3/4] cut: add display order --- src/uu/cut/src/cut.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 3781f0c9f..ddedc4727 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -430,7 +430,6 @@ mod options { pub const CHARACTERS: &str = "characters"; pub const DELIMITER: &str = "delimiter"; pub const FIELDS: &str = "fields"; - pub const LEGACY_OPTION: &str = "legacy-option"; pub const ZERO_TERMINATED: &str = "zero-terminated"; pub const ONLY_DELIMITED: &str = "only-delimited"; pub const OUTPUT_DELIMITER: &str = "output-delimiter"; @@ -454,7 +453,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .help("filter byte columns from the input source") .allow_hyphen_values(true) - .value_name("LIST"), + .value_name("LIST") + .display_order(1), ) .arg( Arg::with_name(options::CHARACTERS) @@ -463,7 +463,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("alias for character mode") .takes_value(true) .allow_hyphen_values(true) - .value_name("LIST"), + .value_name("LIST") + .display_order(2), ) .arg( Arg::with_name(options::DELIMITER) @@ -471,7 +472,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::DELIMITER) .help("specify the delimiter character that separates fields in the input source. Defaults to Tab.") .takes_value(true) - .value_name("DELIM"), + .value_name("DELIM") + .display_order(3), ) .arg( Arg::with_name(options::FIELDS) @@ -480,20 +482,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("filter field columns from the input source") .takes_value(true) .allow_hyphen_values(true) - .value_name("LIST"), - ) - .arg( - Arg::with_name(options::LEGACY_OPTION) - .short("n") - .long(options::LEGACY_OPTION) - .help("legacy option - has no effect.") - .takes_value(false) + .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) @@ -501,6 +498,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .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) @@ -508,6 +506,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .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) @@ -515,6 +514,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .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) From f47345ec9bb452068eaa8bf2af51a1bae3a5148b Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci Date: Sat, 3 Apr 2021 20:55:10 +0300 Subject: [PATCH 4/4] cut: add gnu compatability to error messages --- src/uu/cut/src/cut.rs | 9 +++++++-- tests/by-util/test_cut.rs | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index ddedc4727..6b09b91d9 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -401,8 +401,13 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { } else { let path = Path::new(&filename[..]); - if !path.exists() { - show_error!("{}", msg_args_nonexistent_file!(filename)); + if path.is_dir() { + show_error!("{}: Is a directory", filename); + continue; + } + + if !path.metadata().is_ok() { + show_error!("{}: No such file or directory", filename); continue; } diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index fc0f1b1f9..875317721 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -139,3 +139,21 @@ fn test_zero_terminated_only_delimited() { .succeeds() .stdout_only("82\n7\0"); } + +#[test] +fn test_directory_and_no_such_file() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("some"); + + ucmd.arg("-b1") + .arg("some") + .run() + .stderr_is("cut: error: some: Is a directory\n"); + + new_ucmd!() + .arg("-b1") + .arg("some") + .run() + .stderr_is("cut: error: some: No such file or directory\n"); +}