diff --git a/Cargo.lock b/Cargo.lock index 2bfb7f3..005236b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,7 +154,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -207,6 +207,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" + [[package]] name = "bitmaps" version = "2.1.0" @@ -304,33 +310,31 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "4.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098" dependencies = [ - "atty", - "bitflags", + "bitflags 2.0.2", "clap_lex", - "indexmap", + "is-terminal", "strsim", "termcolor", - "textwrap", ] [[package]] name = "clap_complete" -version = "3.2.5" +version = "4.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8" +checksum = "37686beaba5ac9f3ab01ee3172f792fc6ffdd685bfb9e63cfef02c0571a4e8e1" dependencies = [ "clap", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" dependencies = [ "os_str_bytes", ] @@ -827,6 +831,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -984,6 +994,18 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +[[package]] +name = "is-terminal" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys", +] + [[package]] name = "iter-enum" version = "1.0.1" @@ -1172,7 +1194,7 @@ version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cc", "cfg-if", "libc", @@ -1185,7 +1207,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", @@ -1260,7 +1282,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -1314,7 +1336,7 @@ version = "0.10.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -1656,7 +1678,7 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1772,7 +1794,7 @@ version = "0.36.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -1792,7 +1814,7 @@ version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfc8644681285d1fb67a467fb3021bfea306b99b4146b166a1fe3ada965eece" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "clipboard-win", "dirs-next", @@ -1837,7 +1859,7 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -2103,12 +2125,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "thiserror" version = "1.0.30" diff --git a/Cargo.toml b/Cargo.toml index d01c60f..a7f5c9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ exclude = ["/assets"] [dependencies] tokio = { version = "1.19.2", features = ["macros", "rt-multi-thread", "time", "sync"] } -clap = "3.2.22" +clap = "4.1.10" chrono = "0.4.22" chrono-tz= "0.6.3" gluesql = { version ="0.13.1", default-features = false, features = ["memory-storage"] } @@ -32,7 +32,7 @@ serde = "1.0.144" reqwest = { version = "0.11", features = ["json"] } colored = "2" bincode = { version = "2.0.0-rc.1", features = ["alloc"]} -clap_complete = "3.2.2" +clap_complete = "4.1.5" rustyline = "11.0.0" [[bin]] diff --git a/src/command/action.rs b/src/command/action.rs index dbe0494..b130ac1 100644 --- a/src/command/action.rs +++ b/src/command/action.rs @@ -1,3 +1,5 @@ +use clap::builder::Str; + use crate::{ command::application::{CLEAR, CREATE, DELETE, EXIT, HISTORY, LIST, LS, Q, QUEUE, TEST}, error::ParseError, @@ -47,3 +49,18 @@ impl From for String { } } } + +impl From for Str { + fn from(action: ActionType) -> Self { + match action { + ActionType::Create => CREATE.into(), + ActionType::Queue => QUEUE.into(), + ActionType::Delete => DELETE.into(), + ActionType::List => LIST.into(), + ActionType::Test => TEST.into(), + ActionType::Exit => EXIT.into(), + ActionType::Clear => CLEAR.into(), + ActionType::History => HISTORY.into(), + } + } +} diff --git a/src/command/application.rs b/src/command/application.rs index 78d0a76..acfeffe 100644 --- a/src/command/application.rs +++ b/src/command/application.rs @@ -1,4 +1,4 @@ -use clap::{Arg, ArgMatches, Command}; +use clap::{Arg, ArgAction, ArgMatches, Command}; use std::sync::Arc; use crate::command::action::ActionType; @@ -27,7 +27,7 @@ pub enum CommandType { AutoComplete(ArgMatches), } -pub fn get_start_and_uds_client_command() -> Command<'static> { +pub fn get_start_and_uds_client_command() -> Command { Command::new(BINARY_NAME) .version(env!("CARGO_PKG_VERSION")) .author(AUTHOR) @@ -36,15 +36,28 @@ pub fn get_start_and_uds_client_command() -> Command<'static> { .arg( Arg::new("config") .help("read credential json file from this path") - .takes_value(true) - .value_name("FILE") + .num_args(1) .short('c') .long("config"), ) - .subcommands(get_common_subcommands()) + .subcommands({ + let mut cmd = get_common_subcommands(); + cmd.push( + Command::new("completion") + .about("generate completions for shells") + .arg(Arg::new("shell").value_parser([ + "fish", + "zsh", + "bash", + "elvish", + "powershell", + ])), + ); + cmd + }) } -pub fn get_main_command() -> Command<'static> { +pub fn get_main_command() -> Command { Command::new(BINARY_NAME) .no_binary_name(true) .version(env!("CARGO_PKG_VERSION")) @@ -59,7 +72,7 @@ pub fn get_main_command() -> Command<'static> { }) } -fn get_common_subcommands() -> Vec> { +fn get_common_subcommands() -> Vec { vec![ { let cmd = Command::new(ActionType::Create) @@ -79,7 +92,7 @@ fn get_common_subcommands() -> Vec> { .arg( Arg::new("id") .help("The ID of notification to delete") - .takes_value(true) + .num_args(1) .conflicts_with("all") .short('i') .long("id"), @@ -95,25 +108,22 @@ fn get_common_subcommands() -> Vec> { .about("list notifications"), Command::new(ActionType::History).about("show archived notifications"), Command::new(ActionType::Test).about("test notification"), - Command::new("completion") - .about("generate completions for shells") - .arg(Arg::new("shell").possible_values(["fish", "zsh", "bash"])), ] } -pub(crate) fn add_args_for_create_subcommand(command: Command<'_>) -> Command { - let command = command +pub(crate) fn add_args_for_create_subcommand(command: Command) -> Command { + command .arg( Arg::new("work") .help("The focus time. Unit is minutes") - .takes_value(true) + .num_args(1) .short('w') .default_value("0"), ) .arg( Arg::new("break") .help("The break time, Unit is minutes") - .takes_value(true) + .num_args(1) .short('b') .default_value("0"), ) @@ -123,43 +133,66 @@ pub(crate) fn add_args_for_create_subcommand(command: Command<'_>) -> Command { .conflicts_with("work") .conflicts_with("break") .short('d') - .long("default"), - ); - - command + .long("default") + .action(ArgAction::SetTrue), + ) } #[cfg(test)] mod tests { - use std::iter::zip; - + use super::{get_start_and_uds_client_command, AUTHOR, BINARY_NAME}; use clap::{Arg, Command}; use crate::command::application::get_common_subcommands; - use super::{ - add_args_for_create_subcommand, get_main_command, get_start_and_uds_client_command, AUTHOR, - BINARY_NAME, - }; + use super::{add_args_for_create_subcommand, get_main_command}; #[test] fn test_get_start_and_uds_client_command() { - let command = get_start_and_uds_client_command(); + let uds_cmd = get_start_and_uds_client_command(); + let completion_cmd = Command::new("completion") + .about("generate completions for shells") + .arg(Arg::new("shell").value_parser(["fish", "zsh", "bash", "elvish", "powershell"])); + + let uds_sub_cmds = uds_cmd.get_subcommands().collect::>(); + let mut main_sub_cmds = get_common_subcommands(); + main_sub_cmds.push(completion_cmd); + + assert_eq!(uds_cmd.get_name(), BINARY_NAME); + assert_eq!(uds_cmd.get_author().unwrap(), AUTHOR); + + // Test that the number of subcommands is the same + assert_eq!(main_sub_cmds.len(), uds_sub_cmds.len()); + + for (i, main_subcommand) in main_sub_cmds.iter().enumerate() { + let uds_subcommand = &uds_sub_cmds[i]; + + // Test that the subcommand names are the same + assert_eq!(main_subcommand.get_name(), uds_subcommand.get_name()); + + let main_args = main_subcommand.get_arguments().collect::>(); + let uds_args = uds_subcommand.get_arguments().collect::>(); + + // Test that the number of arguments is the same + assert_eq!(main_args.len(), uds_args.len()); + + for (j, main_arg) in main_args.iter().enumerate() { + let uds_arg = &uds_args[j]; - assert_eq!(command.get_name(), BINARY_NAME); - assert_eq!(command.get_author().unwrap(), AUTHOR); + // Test that the argument names are the same + assert_eq!(main_arg.get_id(), uds_arg.get_id()); - let args: Vec<&Arg> = command - .get_arguments() - .filter(|arg| arg.get_id() == "config") - .collect(); - assert_eq!(args.len(), 1); + // Test that the argument help messages are the same + assert_eq!(main_arg.get_help(), uds_arg.get_help()); - let subcommands = command.get_subcommands().collect::>(); - let common_subcommand = get_common_subcommands(); + // Test that the argument short and long names are the same + assert_eq!(main_arg.get_short(), uds_arg.get_short()); + assert_eq!(main_arg.get_long(), uds_arg.get_long()); - zip(subcommands, common_subcommand) - .for_each(|(actual, expected)| assert_eq!(actual, &expected)); + // Test that the argument value names are the same + assert_eq!(main_arg.get_value_names(), uds_arg.get_value_names()); + } + } } #[test] @@ -171,7 +204,7 @@ mod tests { #[test] fn test_get_common_subcommands() { let subcommands = get_common_subcommands(); - assert_eq!(subcommands.len(), 7); + assert_eq!(subcommands.len(), 6); } #[test] @@ -181,9 +214,9 @@ mod tests { let matches = add_args_for_create_subcommand(cmd) .get_matches_from("myapp -w 25 -b 5".split_whitespace()); - let work = matches.value_of("work").unwrap(); + let work = matches.get_one::("work").unwrap(); assert!(work.eq("25")); - let r#break = matches.value_of("break").unwrap(); + let r#break = matches.get_one::("break").unwrap(); assert!(r#break.eq("5")); // test default @@ -191,6 +224,6 @@ mod tests { let matches = add_args_for_create_subcommand(cmd).get_matches_from("myapp -d".split_whitespace()); - assert!(matches.is_present("default")); + assert!(matches.contains_id("default")); } } diff --git a/src/command/handler/uds_client.rs b/src/command/handler/uds_client.rs index 10d962a..aeb202d 100644 --- a/src/command/handler/uds_client.rs +++ b/src/command/handler/uds_client.rs @@ -84,7 +84,7 @@ async fn handle_queue(socket: UnixDatagram, sub_matches: &ArgMatches) -> HandleU } async fn handle_delete(socket: UnixDatagram, sub_matches: &ArgMatches) -> HandleUdsResult { - let (id, all) = if sub_matches.is_present("id") { + let (id, all) = if sub_matches.contains_id("id") { ( util::parse_arg::(sub_matches, "id").map_err(UdsHandlerError::ParseError)?, false, diff --git a/src/command/handler/user_input.rs b/src/command/handler/user_input.rs index ce905ce..b1161af 100644 --- a/src/command/handler/user_input.rs +++ b/src/command/handler/user_input.rs @@ -1,5 +1,6 @@ use chrono::Utc; -use clap::{ArgMatches, Command, ErrorKind}; +use clap::error::ErrorKind; +use clap::{ArgMatches, Command}; use std::process; use std::result; use std::str::SplitWhitespace; @@ -192,7 +193,7 @@ async fn handle_delete( glue: &ArcGlue, output_accumulator: &mut OutputAccumulater, ) -> HandleUserInputResult { - if sub_matches.is_present("id") { + if sub_matches.contains_id("id") { // delete one let id = util::parse_arg::(sub_matches, "id").map_err(UserInputHandlerError::ParseError)?; @@ -312,20 +313,17 @@ fn get_matches( ErrorKind::DisplayHelp => { // print!("\n{}\n", err); // TODO(young): test format! works well - output_accumulator.push(OutputType::Print, format!("\n{}\n", err)); - return Ok(None); + output_accumulator + .push(OutputType::Print, format!("\n{}\n", err.render().ansi())); + Ok(None) } // clap automatically print version string with out newline. ErrorKind::DisplayVersion => { output_accumulator.push(OutputType::Println, String::from("")); - return Ok(None); - } - _ => { - print!("\n error while handling the input, {}\n", err); + Ok(None) } + _ => Err(UserInputHandlerError::CommandMatchError(err)), } - - Err(UserInputHandlerError::CommandMatchError(err)) } } } diff --git a/src/command/util.rs b/src/command/util.rs index a772736..4c749d7 100644 --- a/src/command/util.rs +++ b/src/command/util.rs @@ -7,7 +7,7 @@ use clap::ArgMatches; use clap_complete::Shell; pub fn parse_work_and_break_time(matches: &ArgMatches) -> Result<(u16, u16), ParseError> { - let (work_time, break_time) = if matches.is_present("default") { + let (work_time, break_time) = if matches.contains_id("default") { (DEFAULT_WORK_TIME, DEFAULT_BREAK_TIME) } else { let work_time = parse_arg::(matches, "work")?; @@ -20,12 +20,14 @@ pub fn parse_work_and_break_time(matches: &ArgMatches) -> Result<(u16, u16), Par } pub fn parse_shell(matches: &ArgMatches) -> Option { - let shell = matches.value_of("shell"); + let shell = matches.get_one::("shell"); if let Some(shell) = shell { - match shell { + match shell.as_str() { "fish" => Some(Shell::Fish), "zsh" => Some(Shell::Zsh), "bash" => Some(Shell::Bash), + "elvish" => Some(Shell::Elvish), + "powershell" => Some(Shell::PowerShell), _ => None, } } else { @@ -38,7 +40,7 @@ where C: FromStr, { let str = arg_matches - .value_of(arg_name) + .get_one::(arg_name) .ok_or(format!("failed to get ({}) from cli", arg_name)) .map_err(ParseError::new)?; @@ -73,7 +75,7 @@ mod tests { #[test] fn test_parse_arg() { let m = Command::new("myapp") - .arg(Arg::new("id").takes_value(true)) + .arg(Arg::new("id").num_args(1)) .get_matches_from("myapp abc".split_whitespace()); // parse as expected @@ -81,7 +83,7 @@ mod tests { assert!(id.eq("abc")); let m = Command::new("myapp") - .arg(Arg::new("id").takes_value(true)) + .arg(Arg::new("id").num_args(1)) .get_matches_from("myapp abc".split_whitespace()); // error when parsing diff --git a/src/configuration.rs b/src/configuration.rs index ed37287..b49f373 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -55,7 +55,7 @@ impl Configuration { } pub fn get_configuration(matches: &ArgMatches) -> Result, ConfigurationError> { - let credential_file_path = matches.value_of("config"); + let credential_file_path = matches.get_one::("config").map(|s| s.as_str()); let (configuration, config_error) = load_configuration(credential_file_path)?; let report = generate_configuration_report(&configuration, config_error); diff --git a/src/error.rs b/src/error.rs index 8d649da..61a9478 100644 --- a/src/error.rs +++ b/src/error.rs @@ -173,7 +173,7 @@ impl fmt::Display for UserInputHandlerError { } UserInputHandlerError::ParseError(e) => write!(f, "failed to parse: {}", e), UserInputHandlerError::CommandMatchError(e) => { - write!(f, "failed to get matches: {}", e) + write!(f, "failed to get matches: {}", e.render().ansi()) } UserInputHandlerError::NotificationError(e) => write!(f, "{}", e), } diff --git a/src/main.rs b/src/main.rs index 2ecb8dd..5380c0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -155,7 +155,7 @@ async fn main() -> Result<(), Box> { handler::uds_client::handle(matches, socket).await?; } CommandType::AutoComplete(sub_matches) => { - if sub_matches.is_present("shell") { + if sub_matches.contains_id("shell") { if let Some(shell) = util::parse_shell(&sub_matches) { let mut main_command = command::get_main_command(); let bin_name = main_command.get_name().to_string();