From 798a03331170766b0f83e8de8ad451b67bd6ca94 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 30 Apr 2021 15:23:54 +0200 Subject: [PATCH] pinky: move from getopts to clap (#2123) --- src/uu/pinky/Cargo.toml | 1 + src/uu/pinky/src/pinky.rs | 164 ++++++++++++++++++++++-------------- tests/by-util/test_pinky.rs | 30 +++++++ 3 files changed, 132 insertions(+), 63 deletions(-) diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index 3f4a75241..a3c36259a 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -17,6 +17,7 @@ path = "src/pinky.rs" [dependencies] uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx", "entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +clap = "2.33.3" [[bin]] name = "pinky" diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index a02096bc8..e116a2382 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -19,67 +19,110 @@ use std::io::BufReader; use std::fs::File; use std::os::unix::fs::MetadataExt; +use clap::{App, Arg}; use std::path::PathBuf; use uucore::InvalidEncodingHandling; -static SYNTAX: &str = "[OPTION]... [USER]..."; -static SUMMARY: &str = "A lightweight 'finger' program; print user information."; - const BUFSIZE: usize = 1024; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "pinky - lightweight finger"; + +mod options { + pub const LONG_FORMAT: &str = "long_format"; + pub const OMIT_HOME_DIR: &str = "omit_home_dir"; + pub const OMIT_PROJECT_FILE: &str = "omit_project_file"; + pub const OMIT_PLAN_FILE: &str = "omit_plan_file"; + pub const SHORT_FORMAT: &str = "short_format"; + pub const OMIT_HEADINGS: &str = "omit_headings"; + pub const OMIT_NAME: &str = "omit_name"; + pub const OMIT_NAME_HOST: &str = "omit_name_host"; + pub const OMIT_NAME_HOST_TIME: &str = "omit_name_host_time"; + pub const USER: &str = "user"; +} + +fn get_usage() -> String { + format!("{0} [OPTION]... [USER]...", executable!()) +} + +fn get_long_usage() -> String { + format!( + "A lightweight 'finger' program; print user information.\n\ + The utmp file will be {}.", + utmpx::DEFAULT_FILE + ) +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let long_help = &format!( - " - -l produce long format output for the specified USERs - -b omit the user's home directory and shell in long format - -h omit the user's project file in long format - -p omit the user's plan file in long format - -s do short format output, this is the default - -f omit the line of column headings in short format - -w omit the user's full name in short format - -i omit the user's full name and remote host in short format - -q omit the user's full name, remote host and idle time - in short format - --help display this help and exit - --version output version information and exit + let usage = get_usage(); + let after_help = get_long_usage(); -The utmp file will be {}", - utmpx::DEFAULT_FILE - ); - let mut opts = app!(SYNTAX, SUMMARY, &long_help); - opts.optflag( - "l", - "", - "produce long format output for the specified USERs", - ); - opts.optflag( - "b", - "", - "omit the user's home directory and shell in long format", - ); - opts.optflag("h", "", "omit the user's project file in long format"); - opts.optflag("p", "", "omit the user's plan file in long format"); - opts.optflag("s", "", "do short format output, this is the default"); - opts.optflag("f", "", "omit the line of column headings in short format"); - opts.optflag("w", "", "omit the user's full name in short format"); - opts.optflag( - "i", - "", - "omit the user's full name and remote host in short format", - ); - opts.optflag( - "q", - "", - "omit the user's full name, remote host and idle time in short format", - ); - opts.optflag("", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .after_help(&after_help[..]) + .arg( + Arg::with_name(options::LONG_FORMAT) + .short("l") + .requires(options::USER) + .help("produce long format output for the specified USERs"), + ) + .arg( + Arg::with_name(options::OMIT_HOME_DIR) + .short("b") + .help("omit the user's home directory and shell in long format"), + ) + .arg( + Arg::with_name(options::OMIT_PROJECT_FILE) + .short("h") + .help("omit the user's project file in long format"), + ) + .arg( + Arg::with_name(options::OMIT_PLAN_FILE) + .short("p") + .help("omit the user's plan file in long format"), + ) + .arg( + Arg::with_name(options::SHORT_FORMAT) + .short("s") + .help("do short format output, this is the default"), + ) + .arg( + Arg::with_name(options::OMIT_HEADINGS) + .short("f") + .help("omit the line of column headings in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME) + .short("w") + .help("omit the user's full name in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME_HOST) + .short("i") + .help("omit the user's full name and remote host in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME_HOST_TIME) + .short("q") + .help("omit the user's full name, remote host and idle time in short format"), + ) + .arg( + Arg::with_name(options::USER) + .takes_value(true) + .multiple(true), + ) + .get_matches_from(args); - let matches = opts.parse(args); + let users: Vec = matches + .values_of(options::USER) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); // If true, display the hours:minutes since each user has touched // the keyboard, or blank if within the last minute, or days followed @@ -87,45 +130,40 @@ The utmp file will be {}", let mut include_idle = true; // If true, display a line at the top describing each field. - let include_heading = !matches.opt_present("f"); + let include_heading = !matches.is_present(options::OMIT_HEADINGS); // if true, display the user's full name from pw_gecos. let mut include_fullname = true; // if true, display the user's ~/.project file when doing long format. - let include_project = !matches.opt_present("h"); + let include_project = !matches.is_present(options::OMIT_PROJECT_FILE); // if true, display the user's ~/.plan file when doing long format. - let include_plan = !matches.opt_present("p"); + let include_plan = !matches.is_present(options::OMIT_PLAN_FILE); // if true, display the user's home directory and shell // when doing long format. - let include_home_and_shell = !matches.opt_present("b"); + let include_home_and_shell = !matches.is_present(options::OMIT_HOME_DIR); // if true, use the "short" output format. - let do_short_format = !matches.opt_present("l"); + let do_short_format = !matches.is_present(options::LONG_FORMAT); /* if true, display the ut_host field. */ let mut include_where = true; - if matches.opt_present("w") { + if matches.is_present(options::OMIT_NAME) { include_fullname = false; } - if matches.opt_present("i") { + if matches.is_present(options::OMIT_NAME_HOST) { include_fullname = false; include_where = false; } - if matches.opt_present("q") { + if matches.is_present(options::OMIT_NAME_HOST_TIME) { include_fullname = false; include_idle = false; include_where = false; } - if !do_short_format && matches.free.is_empty() { - show_usage_error!("no username specified; at least one must be specified when using -l"); - return 1; - } - let pk = Pinky { include_idle, include_heading, @@ -134,7 +172,7 @@ The utmp file will be {}", include_plan, include_home_and_shell, include_where, - names: matches.free, + names: users, }; if do_short_format { diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index 7a4a3f3df..1a7ef8b61 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -34,6 +34,36 @@ fn test_long_format() { )); } +#[cfg(target_os = "linux")] +#[test] +fn test_long_format_multiple_users() { + let scene = TestScenario::new(util_name!()); + + let expected = scene + .cmd_keepenv(util_name!()) + .env("LANGUAGE", "C") + .arg("-l") + .arg("root") + .arg("root") + .arg("root") + .succeeds(); + + scene + .ucmd() + .arg("-l") + .arg("root") + .arg("root") + .arg("root") + .succeeds() + .stdout_is(expected.stdout_str()); +} + +#[test] +fn test_long_format_wo_user() { + // "no username specified; at least one must be specified when using -l" + new_ucmd!().arg("-l").fails().code_is(1); +} + #[cfg(target_os = "linux")] #[test] fn test_short_format_i() {