From 02ba19d709e90bf7346f5166cd2cb891af14794d Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Mon, 15 Jul 2024 14:54:25 -0400 Subject: [PATCH] switch to clap derive instead of builder --- package.json | 2 +- src-tauri/Cargo.lock | 159 +++++++++++++---------- src-tauri/Cargo.toml | 2 +- src-tauri/creddy_cli/Cargo.toml | 4 +- src-tauri/creddy_cli/src/cli.rs | 201 ++++++++++++++++-------------- src-tauri/creddy_cli/src/lib.rs | 3 +- src-tauri/creddy_cli/src/main.rs | 17 +-- src-tauri/creddy_cli/src/proto.rs | 3 +- src-tauri/src/main.rs | 15 +-- src-tauri/tauri.conf.json | 2 +- 10 files changed, 221 insertions(+), 187 deletions(-) diff --git a/package.json b/package.json index 8bb7b8a..1c31fd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "creddy", - "version": "0.5.3", + "version": "0.5.4", "scripts": { "dev": "vite", "build": "vite build", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b289040..db776a5 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -110,6 +110,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -327,17 +376,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "auto-launch" version = "0.4.0" @@ -1023,42 +1061,43 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.25" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ - "atty", - "bitflags 1.3.2", + "clap_builder", "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "indexmap 1.9.3", - "once_cell", - "strsim 0.10.0", - "termcolor", - "textwrap", + "strsim", ] [[package]] name = "clap_derive" -version = "3.2.25" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ - "heck 0.4.1", - "proc-macro-error", + "heck 0.5.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.68", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "cocoa" @@ -1090,6 +1129,12 @@ dependencies = [ "objc", ] +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "combine" version = "4.6.7" @@ -1196,7 +1241,7 @@ dependencies = [ [[package]] name = "creddy" -version = "0.5.3" +version = "0.5.4" dependencies = [ "argon2", "auto-launch", @@ -1242,7 +1287,7 @@ dependencies = [ [[package]] name = "creddy_cli" -version = "0.5.3" +version = "0.5.4" dependencies = [ "anyhow", "clap", @@ -1410,7 +1455,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.68", ] @@ -2446,15 +2491,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.9" @@ -2777,6 +2813,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itoa" version = "0.4.8" @@ -3527,12 +3569,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - [[package]] name = "outref" version = "0.5.1" @@ -5249,12 +5285,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -5742,21 +5772,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" - [[package]] name = "thin-slice" version = "0.1.1" @@ -6228,6 +6243,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.9.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index d22c84d..77e4a4a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "creddy" -version = "0.5.3" +version = "0.5.4" description = "A friendly AWS credentials manager" authors = ["Joseph Montanaro"] license = "" diff --git a/src-tauri/creddy_cli/Cargo.toml b/src-tauri/creddy_cli/Cargo.toml index e2f4572..0f4b36d 100644 --- a/src-tauri/creddy_cli/Cargo.toml +++ b/src-tauri/creddy_cli/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "creddy_cli" -version = "0.5.3" +version = "0.5.4" edition = "2021" [dependencies] anyhow = "1.0.86" -clap = { version = "3.2.23", features = ["derive"] } +clap = { version = "4", features = ["derive"] } dirs = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/src-tauri/creddy_cli/src/cli.rs b/src-tauri/creddy_cli/src/cli.rs index 479f356..fefc47e 100644 --- a/src-tauri/creddy_cli/src/cli.rs +++ b/src-tauri/creddy_cli/src/cli.rs @@ -1,4 +1,3 @@ -use std::env; use std::path::PathBuf; use std::process::Command as ChildCommand; #[cfg(unix)] @@ -8,13 +7,11 @@ use std::time::Duration; use anyhow::{bail, Context}; use clap::{ - Command, - Arg, - ArgMatches, - ArgAction, - builder::PossibleValuesParser, - value_parser, + Args, + Parser, + Subcommand }; +use clap::builder::styling::{Styles, AnsiColor}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::proto::{ @@ -26,80 +23,97 @@ use crate::proto::{ }; -pub fn parser() -> Command<'static> { - Command::new("creddy") - .version(env!("CARGO_PKG_VERSION")) - .about("A friendly credential manager") - .arg( - Arg::new("server_addr") - .short('a') - .long("server-addr") - .takes_value(true) - .value_parser(value_parser!(PathBuf)) - .help("Connect to the main Creddy process at this address") - ) - .subcommand( - Command::new("run") - .about("Launch Creddy") - ) - .subcommand( - Command::new("get") - .about("Request AWS credentials from Creddy and output to stdout") - .arg( - Arg::new("base") - .short('b') - .long("base") - .action(ArgAction::SetTrue) - .help("Use base credentials instead of session credentials") - ) - .arg( - Arg::new("name") - .help("If unspecified, use default credentials") - ) - ) - .subcommand( - Command::new("exec") - .about("Inject AWS credentials into the environment of another command") - .trailing_var_arg(true) - .arg( - Arg::new("base") - .short('b') - .long("base") - .action(ArgAction::SetTrue) - .help("Use base credentials instead of session credentials") - ) - .arg( - Arg::new("name") - .short('n') - .long("name") - .takes_value(true) - .help("If unspecified, use default credentials") - ) - .arg( - Arg::new("command") - .multiple_values(true) - ) - ) - .subcommand( - Command::new("shortcut") - .about("Invoke an action normally trigged by hotkey (e.g. launch terminal)") - .arg( - Arg::new("action") - .value_parser( - PossibleValuesParser::new(["show_window", "launch_terminal"]) - ) - ) - ) +#[derive(Debug, Parser)] +#[command( + about, + version, + name = "creddy", + bin_name = "creddy", + styles = Styles::styled() + .header(AnsiColor::Yellow.on_default()) + .usage(AnsiColor::Yellow.on_default()) + .literal(AnsiColor::Green.on_default()) + .placeholder(AnsiColor::Green.on_default()) +)] +/// A friendly credential manager +pub struct Cli { + #[command(flatten)] + pub global_args: GlobalArgs, + + #[command(subcommand)] + pub action: Option, +} + +impl Cli { + // proxy the Parser method so that main crate doesn't have to depend on Clap + pub fn parse() -> Self { + ::parse() + } } -pub fn get(args: &ArgMatches, global_args: &ArgMatches) -> anyhow::Result<()> { - let name = args.get_one("name").cloned(); - let base = *args.get_one("base").unwrap_or(&false); - let addr = global_args.get_one("server_addr").cloned(); +#[derive(Debug, Clone, Args)] +pub struct GlobalArgs { + /// Connect to the main Creddy application at this path + #[arg(long, short = 'a')] + server_addr: Option, +} - let output = match make_request(addr, &CliRequest::GetCredential { name, base })?? { - CliResponse::Credential(c) => serde_json::to_string_pretty(&c).unwrap(), + +#[derive(Debug, Subcommand)] +pub enum Action { + /// Launch Creddy + Run, + /// Request credentials from Creddy and output to stdout + Get(GetArgs), + /// Inject credentials into the environment of another command + Exec(ExecArgs), + /// Invoke an action normally triggered by hotkey (e.g. launch terminal) + Shortcut(InvokeArgs), +} + + +#[derive(Debug, Args)] +pub struct GetArgs { + /// If unspecified, use default credentials + #[arg(short, long)] + name: Option, + /// Use base credentials instead of session credentials (only applicable to AWS) + #[arg(long, short, default_value_t = false)] + base: bool, +} + + +#[derive(Debug, Args)] +pub struct ExecArgs { + #[command(flatten)] + get_args: GetArgs, + #[arg(trailing_var_arg = true)] + /// Command to be wrapped + command: Vec, +} + + +#[derive(Debug, Args)] +pub struct InvokeArgs { + #[arg(value_name = "ACTION", value_enum)] + shortcut_action: ShortcutAction, +} + + +pub fn get(args: GetArgs, global: GlobalArgs) -> anyhow::Result<()> { + let req = CliRequest::GetCredential { + name: args.name, + base: args.base, + }; + + let output = match make_request(global.server_addr, &req)?? { + CliResponse::Credential(CliCredential::AwsBase(c)) => { + serde_json::to_string_pretty(&c).unwrap() + }, + CliResponse::Credential(CliCredential::AwsSession(c)) => { + serde_json::to_string_pretty(&c).unwrap() + }, r => bail!("Unexpected response from server: {r}"), }; println!("{output}"); @@ -107,18 +121,20 @@ pub fn get(args: &ArgMatches, global_args: &ArgMatches) -> anyhow::Result<()> { } -pub fn exec(args: &ArgMatches, global_args: &ArgMatches) -> anyhow::Result<()> { - let name = args.get_one("name").cloned(); - let base = *args.get_one("base").unwrap_or(&false); - let addr = global_args.get_one("server_addr").cloned(); +pub fn exec(args: ExecArgs, global: GlobalArgs) -> anyhow::Result<()> { // Clap guarantees that cmd_line will be a sequence of at least 1 item // test this! - let mut cmd_line = args.get_many("command").unwrap(); - let cmd_name: &String = cmd_line.next().unwrap(); + let mut cmd_line = args.command.iter(); + let cmd_name = cmd_line.next().unwrap(); let mut cmd = ChildCommand::new(cmd_name); cmd.args(cmd_line); - match make_request(addr, &CliRequest::GetCredential { name, base })?? { + let req = CliRequest::GetCredential { + name: args.get_args.name, + base: args.get_args.base, + }; + + match make_request(global.server_addr, &req)?? { CliResponse::Credential(CliCredential::AwsBase(creds)) => { cmd.env("AWS_ACCESS_KEY_ID", creds.access_key_id); cmd.env("AWS_SECRET_ACCESS_KEY", creds.secret_access_key); @@ -133,11 +149,11 @@ pub fn exec(args: &ArgMatches, global_args: &ArgMatches) -> anyhow::Result<()> { #[cfg(unix)] { - // cmd.exec() never returns if successful let e = cmd.exec(); + // cmd.exec() never returns if successful, so we never hit this line unless there's an error Err(e).with_context(|| { // eventually figure out how to display the actual command - format!("Failed to execute command") + format!("Failed to execute command: {}", args.command.join(" ")) })?; Ok(()) } @@ -160,16 +176,9 @@ pub fn exec(args: &ArgMatches, global_args: &ArgMatches) -> anyhow::Result<()> { } -pub fn invoke_shortcut(args: &ArgMatches, global_args: &ArgMatches) -> anyhow::Result<()> { - let addr = global_args.get_one("server_addr").cloned(); - let action = match args.get_one::("action").map(|s| s.as_str()) { - Some("show_window") => ShortcutAction::ShowWindow, - Some("launch_terminal") => ShortcutAction::LaunchTerminal, - Some(&_) | None => unreachable!("Unknown shortcut action"), // guaranteed by clap - }; - - let req = CliRequest::InvokeShortcut(action); - match make_request(addr, &req)?? { +pub fn invoke_shortcut(args: InvokeArgs, global: GlobalArgs) -> anyhow::Result<()> { + let req = CliRequest::InvokeShortcut(args.shortcut_action); + match make_request(global.server_addr, &req)?? { CliResponse::Empty => Ok(()), r => bail!("Unexpected response from server: {r}"), } diff --git a/src-tauri/creddy_cli/src/lib.rs b/src-tauri/creddy_cli/src/lib.rs index 3cf050e..8738e04 100644 --- a/src-tauri/creddy_cli/src/lib.rs +++ b/src-tauri/creddy_cli/src/lib.rs @@ -1,8 +1,9 @@ mod cli; pub use cli::{ + Cli, + Action, exec, get, - parser, invoke_shortcut, }; diff --git a/src-tauri/creddy_cli/src/main.rs b/src-tauri/creddy_cli/src/main.rs index d6e380a..f41513f 100644 --- a/src-tauri/creddy_cli/src/main.rs +++ b/src-tauri/creddy_cli/src/main.rs @@ -1,19 +1,20 @@ use std::env; use std::process::{self, Command}; +use creddy_cli::{Action, Cli}; + fn main() { - let global_matches = creddy_cli::parser().get_matches(); - let res = match global_matches.subcommand() { - None | Some(("run", _)) => launch_gui(), - Some(("get", m)) => creddy_cli::get(m, &global_matches), - Some(("exec", m)) => creddy_cli::exec(m, &global_matches), - Some(("shortcut", m)) => creddy_cli::invoke_shortcut(m, &global_matches), - _ => unreachable!("Unknown subcommand"), + let cli = Cli::parse(); + let res = match cli.action { + None | Some(Action::Run)=> launch_gui(), + Some(Action::Get(args)) => creddy_cli::get(args, cli.global_args), + Some(Action::Exec(args)) => creddy_cli::exec(args, cli.global_args), + Some(Action::Shortcut(args)) => creddy_cli::invoke_shortcut(args, cli.global_args), }; if let Err(e) = res { - eprintln!("Error: {e}"); + eprintln!("Error: {e:?}"); process::exit(1); } } diff --git a/src-tauri/creddy_cli/src/proto.rs b/src-tauri/creddy_cli/src/proto.rs index 4326da3..52459ce 100644 --- a/src-tauri/creddy_cli/src/proto.rs +++ b/src-tauri/creddy_cli/src/proto.rs @@ -4,6 +4,7 @@ use std::fmt::{ Error as FmtError }; +use clap::ValueEnum; use serde::{Serialize, Deserialize}; @@ -17,7 +18,7 @@ pub enum CliRequest { } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize, ValueEnum)] pub enum ShortcutAction { ShowWindow, LaunchTerminal, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 587f126..91f703b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,23 +3,24 @@ windows_subsystem = "windows" )] + use creddy::{ app, errors::ShowError, }; +use creddy_cli::{Action, Cli}; fn main() { - let global_matches = creddy_cli::parser().get_matches(); - let res = match global_matches.subcommand() { - None | Some(("run", _)) => { + let cli = Cli::parse(); + let res = match cli.action { + None | Some(Action::Run) => { app::run().error_popup("Creddy encountered an error"); Ok(()) }, - Some(("get", m)) => creddy_cli::get(m, &global_matches), - Some(("exec", m)) => creddy_cli::exec(m, &global_matches), - Some(("shortcut", m)) => creddy_cli::invoke_shortcut(m, &global_matches), - _ => unreachable!(), + Some(Action::Get(args)) => creddy_cli::get(args, cli.global_args), + Some(Action::Exec(args)) => creddy_cli::exec(args, cli.global_args), + Some(Action::Shortcut(args)) => creddy_cli::invoke_shortcut(args, cli.global_args), }; if let Err(e) = res { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c17a103..c3005ed 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -50,7 +50,7 @@ } }, "productName": "creddy", - "version": "0.5.3", + "version": "0.5.4", "identifier": "creddy", "plugins": {}, "app": {