From c91fd511979fadb44b2aad92806073df4b1b4919 Mon Sep 17 00:00:00 2001 From: Nathan Ross Date: Mon, 8 Aug 2016 03:05:36 -0400 Subject: [PATCH 1/4] uucore: add message templates --- src/uucore/macros.rs | 109 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/uucore/macros.rs b/src/uucore/macros.rs index 21691dc5c..f9a57c884 100644 --- a/src/uucore/macros.rs +++ b/src/uucore/macros.rs @@ -239,3 +239,112 @@ macro_rules! safe_unwrap( } ) ); + +//-- message templates + +//-- message templates : general + +#[macro_export] +macro_rules! snippet_list_join_oxford { + ($conjunction:expr, $valOne:expr, $valTwo:expr) => ( + format!("{}, {} {}", $valOne, $conjunction, $valTwo) + ); + ($conjunction:expr, $valOne:expr, $valTwo:expr $(, $remainingVals:expr)*) => ( + format!("{}, {}", $valOne, snippet_list_join_inner!($conjunction, $valTwo $(, $remainingVals)*)) + ); +} + +#[macro_export] +macro_rules! snippet_list_join_or { + ($valOne:expr, $valTwo:expr) => ( + format!("{} or {}", $valOne, $valTwo) + ); + ($valOne:expr, $valTwo:expr $(, $remainingVals:expr)*) => ( + format!("{}, {}", $valOne, snippet_list_join_oxford!("or", $valTwo $(, $remainingVals)*)) + ); +} + +//-- message templates : help and version + +#[macro_export] +macro_rules! msg_version { + () => ( + format!("{} {}", executable!(), env!("CARGO_PKG_VERSION")) + ) +} + +//-- message templates : invalid input + +#[macro_export] +macro_rules! msg_invalid_input { ($reason: expr) => ( + format!("invalid input: {}", $reason) ); } + +#[macro_export] +macro_rules! snippet_no_file_at_path { ($path:expr) => ( + format!("nonexistent path {}", $path) ); } + +// -- message templates : invalid input : flag + +#[macro_export] +macro_rules! msg_invalid_opt_use { + ($about:expr, $flag:expr) => ( + msg_invalid_input!(format!("The '{}' option {}", $flag, $about)) + ); + ($about:expr, $longflag:expr, $shortflag:expr) => { + msg_invalid_input!(format!("The '{}' ('{}') option {}", $longflag, $shortflag, $about)) + }; +} + +#[macro_export] +macro_rules! msg_opt_only_usable_if { + ($clause:expr, $flag:expr) => ( + msg_invalid_opt_use!(format!("only usable if {}", $clause), $flag) + ); + ($clause:expr, $longflag:expr, $shortflag:expr) => ( + msg_invalid_opt_use!(format!("only usable if {}", $clause), $longflag, $shortflag) + ); +} + +#[macro_export] +macro_rules! msg_opt_invalid_should_be { + ($expects:expr, $received:expr, $flag:expr) => ( + msg_invalid_opt_use!(format!("expects {}, but was provided {}", $expects, $received), $flag) + ); + ($expects:expr, $received:expr, $longflag:expr, $shortflag:expr) => ( + msg_invalid_opt_use!(format!("expects {}, but was provided {}", $expects, $received), $longflag, $shortflag) + ); +} + +// -- message templates : invalid input : args + +#[macro_export] +macro_rules! msg_arg_invalid_value { ($expects:expr, $received:expr) => ( + msg_invalid_input!(format!("expects its argument to be {}, but was provided {}", $expects, $received)) ); } + +#[macro_export] +macro_rules! msg_args_invalid_value { ($expects:expr, $received:expr) => ( + msg_invalid_input!(format!("expects its arguments to be {}, but was provided {}", $expects, $received)) ); } + +#[macro_export] +macro_rules! msg_args_nonexistent_file { ($received:expr) => ( + msg_args_invalid_value!("paths to files", snippet_no_file_at_path!($received)));} + +#[macro_export] +macro_rules! msg_wrong_number_of_arguments { ($received:expr) => ( + msg_args_invalid_value!("wrong number of arguments") ); } + +// -- message templates : invalid input : input combinations + +#[macro_export] +macro_rules! msg_expects_one_of { + ($valOne:expr $(, $remainingVals:expr)*) => ( + msg_invalid_input!(format!("expects one of {}", snippet_list_join_or!($valOne $(, $remainingVals)*))) + ); +} + +#[macro_export] +macro_rules! msg_expects_no_more_than_one_of { + ($valOne:expr $(, $remainingVals:expr)*) => ( + msg_invalid_input!(format!("expects no more than one of {}", snippet_list_join_or!($valOne $(, $remainingVals)*))) ; + ); +} From 3eb9bbf4b5a1c3e8ec6a03ea3b458e8987b0682c Mon Sep 17 00:00:00 2001 From: Nathan Ross Date: Mon, 8 Aug 2016 03:27:06 -0400 Subject: [PATCH 2/4] uucore: CoreOptions error and version message templates --- src/uucore/Cargo.toml | 1 + src/uucore/coreopts.rs | 53 ++++++++++++++++++++++++++++++++++++++++++ src/uucore/lib.rs | 1 + 3 files changed, 55 insertions(+) create mode 100644 src/uucore/coreopts.rs diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index aeab77705..1689c734e 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -6,6 +6,7 @@ authors = [] [dependencies] libc = "*" winapi = "*" +getopts = "*" data-encoding = "^1.1" [lib] diff --git a/src/uucore/coreopts.rs b/src/uucore/coreopts.rs new file mode 100644 index 000000000..fa2f31fc2 --- /dev/null +++ b/src/uucore/coreopts.rs @@ -0,0 +1,53 @@ +extern crate getopts; +use std::io::Write; + +pub struct CoreOptions { + pub options : getopts::Options, + longhelp : Option +} + +impl<'a> CoreOptions { + pub fn new() -> Self { + let mut ret = CoreOptions { + options : getopts::Options::new(), + longhelp : None + }; + ret.options + .optflag("", "help", "print usage information") + .optflag("", "version", "print name and version number"); + ret + } + pub fn optopt(&mut self, short_name: &str, long_name: &str, desc: &str, hint: &str) -> &mut CoreOptions { + self.options.optopt(short_name, long_name, desc, hint); + self + } + pub fn optflag(&mut self, short_name: &str, long_name: &str, desc: &str) -> &mut CoreOptions { + self.options.optflag(short_name, long_name, desc); + self + } + pub fn help>(&mut self, longhelp : T) -> &mut CoreOptions { + self.longhelp = Some(longhelp.into()); + self + } + pub fn usage(&self, summary : &str) -> String { + self.options.usage(summary) + } + pub fn parse(&mut self, args : Vec) -> getopts::Matches { + let matches = match self.options.parse(&args[1..]) { + Ok(m) => { Some(m) }, + Err(f) => { + crash!(1, "{}", msg_invalid_input!(format!("{}", f))); + } + }.unwrap(); + if matches.opt_present("help") { + exit!(match self.longhelp { + Some(ref lhelp) => { print!("{}", lhelp); 0} + None => 1 + }); + } else if matches.opt_present("version") { + print!("{}", msg_version!()); + exit!(0); + } + matches + } +} \ No newline at end of file diff --git a/src/uucore/lib.rs b/src/uucore/lib.rs index d61d93516..f3cd5bebc 100644 --- a/src/uucore/lib.rs +++ b/src/uucore/lib.rs @@ -8,6 +8,7 @@ pub mod fs; pub mod parse_time; pub mod utf8; pub mod encoding; +pub mod coreopts; #[cfg(unix)] pub mod c_types; #[cfg(unix)] pub mod process; From d56389aca0a8bb5d38a1b97c2da8a4d3ef405436 Mon Sep 17 00:00:00 2001 From: Nathan Ross Date: Mon, 8 Aug 2016 03:35:53 -0400 Subject: [PATCH 3/4] cut: use message templates for consistent error UX --- src/cut/Cargo.toml | 1 - src/cut/cut.rs | 44 ++++++++++++-------------------------------- 2 files changed, 12 insertions(+), 33 deletions(-) diff --git a/src/cut/Cargo.toml b/src/cut/Cargo.toml index 037570996..5f976f7ab 100644 --- a/src/cut/Cargo.toml +++ b/src/cut/Cargo.toml @@ -8,7 +8,6 @@ name = "uu_cut" path = "cut.rs" [dependencies] -getopts = "*" libc = "*" uucore = { path="../uucore" } diff --git a/src/cut/cut.rs b/src/cut/cut.rs index c8540a35a..5a51ee957 100644 --- a/src/cut/cut.rs +++ b/src/cut/cut.rs @@ -9,7 +9,6 @@ * file that was distributed with this source code. */ -extern crate getopts; extern crate libc; #[macro_use] @@ -379,7 +378,7 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { let path = Path::new(&filename[..]); if !path.exists() { - show_error!("{}: No such file or directory", filename); + show_error!("{}", msg_args_nonexistent_file!(filename)); continue } @@ -403,7 +402,7 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { } pub fn uumain(args: Vec) -> i32 { - let mut opts = getopts::Options::new(); + let mut opts = uucore::coreopts::CoreOptions::new(); opts.optopt("b", "bytes", "filter byte columns from the input source", "sequence"); opts.optopt("c", "characters", "alias for character mode", "sequence"); @@ -413,19 +412,8 @@ pub fn uumain(args: Vec) -> i32 { opts.optflag("", "complement", "invert the filter - instead of displaying only the filtered columns, display all but those columns"); opts.optflag("s", "only-delimited", "in field mode, only print lines which contain the delimiter"); opts.optopt("", "output-delimiter", "in field mode, replace the delimiter in output lines with this option's argument", "new delimiter"); - opts.optflag("", "help", "print usage information"); - opts.optflag("", "version", "print version information"); - - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => { - show_error!("Invalid options\n{}", f); - return 1; - } - }; - - if matches.opt_present("help") { - print!(" + let usage = opts.usage("Prints specified byte or field columns from each line of stdin or the input files"); + opts.help(format!(" {0} {1} {0} [-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+ @@ -501,14 +489,8 @@ pub fn uumain(args: Vec) -> i32 { 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) -", NAME, VERSION, opts.usage("Prints specified byte or field columns from each line of stdin or the input files")); - return 0; - } - - if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - return 0; - } +", NAME, VERSION, usage)); + let matches = opts.parse(args); let complement = matches.opt_present("complement"); @@ -542,7 +524,7 @@ pub fn uumain(args: Vec) -> i32 { match matches.opt_str("delimiter") { Some(delim) => { if delim.chars().count() > 1 { - Err("the delimiter must be a single character, or the empty string for null".to_owned()) + Err(msg_opt_invalid_should_be!("empty or 1 character long", "a value 2 characters or longer", "--delimiter", "-d").to_owned()) } else { let delim = if delim.is_empty() { "\0".to_owned() @@ -569,9 +551,9 @@ pub fn uumain(args: Vec) -> i32 { ) } (ref b, ref c, ref f) if b.is_some() || c.is_some() || f.is_some() => { - Err("only one type of list may be specified".to_owned()) + Err(msg_expects_no_more_than_one_of!("--fields (-f)", "--chars (-c)", "--bytes (-b)").to_owned()) } - _ => Err("you must specify a list of bytes, characters, or fields".to_owned()) + _ => Err(msg_expects_one_of!("--fields (-f)", "--chars (-c)", "--bytes (-b)").to_owned()) }; let mode_parse = match mode_parse { @@ -579,9 +561,9 @@ pub fn uumain(args: Vec) -> i32 { Ok(mode) => { match mode { Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("delimiter") => - Err("an input delimiter may be specified only when operating on fields".to_owned()), + Err(msg_opt_only_usable_if!("printing a sequence of fields", "--delimiter", "-d").to_owned()), Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("only-delimited") => - Err("suppressing non-delimited lines makes sense only when operating on fields".to_owned()), + Err(msg_opt_only_usable_if!("printing a sequence of fields", "--only-delimited", "-s").to_owned()), _ => Ok(mode), } } @@ -590,9 +572,7 @@ pub fn uumain(args: Vec) -> i32 { match mode_parse { Ok(mode) => cut_files(matches.free, mode), Err(err_msg) => { - show_error!("{}\n\ - Try '{} --help' for more information", - err_msg, args[0]); + show_error!("{}", err_msg); 1 } } From 1bf2c82520b6e675432da664aaa22feb6e7bdc64 Mon Sep 17 00:00:00 2001 From: Nathan Ross Date: Mon, 8 Aug 2016 04:04:24 -0400 Subject: [PATCH 4/4] arch: use message templates for consistent, original-work error handling --- src/arch/Cargo.toml | 1 - src/arch/arch.rs | 29 +++++++---------------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/arch/Cargo.toml b/src/arch/Cargo.toml index 251ca48a0..ef886adc0 100644 --- a/src/arch/Cargo.toml +++ b/src/arch/Cargo.toml @@ -8,7 +8,6 @@ name = "uu_arch" path = "arch.rs" [dependencies] -getopts = "*" libc = "*" uucore = { path="../uucore" } diff --git a/src/arch/arch.rs b/src/arch/arch.rs index 4b018d1b3..05d9bd63b 100644 --- a/src/arch/arch.rs +++ b/src/arch/arch.rs @@ -9,14 +9,12 @@ * file that was distributed with this source code. */ -extern crate getopts; extern crate libc; #[macro_use] extern crate uucore; use std::ffi::CStr; -use std::io::Write; use std::mem::uninitialized; use uucore::c_types::utsname; @@ -44,28 +42,15 @@ static NAME: &'static str = "arch"; static VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub fn uumain(args: Vec) -> i32 { - let mut opts = getopts::Options::new(); + let mut opts = uucore::coreopts::CoreOptions::new(); + let usage = opts.usage("Determine architecture name for current machine."); + opts.help(format!(" +{0} {1} - opts.optflag("", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); +{0} - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => crash!(1, "{}\nTry '{} --help' for more information.", f, NAME), - }; - - if matches.opt_present("help") { - println!("{} {}", NAME, VERSION); - println!(""); - println!("Usage:"); - println!(" {} [OPTIONS]...", NAME); - println!(""); - print!("{}", opts.usage("Print machine architecture name.")); - return 0; - } else if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - return 0; - } +{2} +", NAME, VERSION, usage)).parse(args); let machine_arch = unsafe { get_machine_arch() }; let mut output = String::new();