switch to clap derive instead of builder

This commit is contained in:
Joseph Montanaro 2024-07-15 14:54:25 -04:00
parent 55801384eb
commit 02ba19d709
10 changed files with 221 additions and 187 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "creddy", "name": "creddy",
"version": "0.5.3", "version": "0.5.4",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",

159
src-tauri/Cargo.lock generated
View File

@ -110,6 +110,55 @@ dependencies = [
"libc", "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]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.86" version = "1.0.86"
@ -327,17 +376,6 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 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]] [[package]]
name = "auto-launch" name = "auto-launch"
version = "0.4.0" version = "0.4.0"
@ -1023,42 +1061,43 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "3.2.25" version = "4.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
dependencies = [ dependencies = [
"atty", "clap_builder",
"bitflags 1.3.2",
"clap_derive", "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", "clap_lex",
"indexmap 1.9.3", "strsim",
"once_cell",
"strsim 0.10.0",
"termcolor",
"textwrap",
] ]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "3.2.25" version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
dependencies = [ dependencies = [
"heck 0.4.1", "heck 0.5.0",
"proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109", "syn 2.0.68",
] ]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.2.4" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
dependencies = [
"os_str_bytes",
]
[[package]] [[package]]
name = "cocoa" name = "cocoa"
@ -1090,6 +1129,12 @@ dependencies = [
"objc", "objc",
] ]
[[package]]
name = "colorchoice"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]] [[package]]
name = "combine" name = "combine"
version = "4.6.7" version = "4.6.7"
@ -1196,7 +1241,7 @@ dependencies = [
[[package]] [[package]]
name = "creddy" name = "creddy"
version = "0.5.3" version = "0.5.4"
dependencies = [ dependencies = [
"argon2", "argon2",
"auto-launch", "auto-launch",
@ -1242,7 +1287,7 @@ dependencies = [
[[package]] [[package]]
name = "creddy_cli" name = "creddy_cli"
version = "0.5.3" version = "0.5.4"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1410,7 +1455,7 @@ dependencies = [
"ident_case", "ident_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim 0.11.1", "strsim",
"syn 2.0.68", "syn 2.0.68",
] ]
@ -2446,15 +2491,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 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]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.9" version = "0.3.9"
@ -2777,6 +2813,12 @@ dependencies = [
"windows-sys 0.52.0", "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]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.8" version = "0.4.8"
@ -3527,12 +3569,6 @@ dependencies = [
"windows-sys 0.52.0", "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]] [[package]]
name = "outref" name = "outref"
version = "0.5.1" version = "0.5.1"
@ -5249,12 +5285,6 @@ dependencies = [
"unicode-properties", "unicode-properties",
] ]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"
@ -5742,21 +5772,6 @@ dependencies = [
"utf-8", "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]] [[package]]
name = "thin-slice" name = "thin-slice"
version = "0.1.1" version = "0.1.1"
@ -6228,6 +6243,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.9.1" version = "1.9.1"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "creddy" name = "creddy"
version = "0.5.3" version = "0.5.4"
description = "A friendly AWS credentials manager" description = "A friendly AWS credentials manager"
authors = ["Joseph Montanaro"] authors = ["Joseph Montanaro"]
license = "" license = ""

View File

@ -1,11 +1,11 @@
[package] [package]
name = "creddy_cli" name = "creddy_cli"
version = "0.5.3" version = "0.5.4"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.86" anyhow = "1.0.86"
clap = { version = "3.2.23", features = ["derive"] } clap = { version = "4", features = ["derive"] }
dirs = { workspace = true } dirs = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }

View File

@ -1,4 +1,3 @@
use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command as ChildCommand; use std::process::Command as ChildCommand;
#[cfg(unix)] #[cfg(unix)]
@ -8,13 +7,11 @@ use std::time::Duration;
use anyhow::{bail, Context}; use anyhow::{bail, Context};
use clap::{ use clap::{
Command, Args,
Arg, Parser,
ArgMatches, Subcommand
ArgAction,
builder::PossibleValuesParser,
value_parser,
}; };
use clap::builder::styling::{Styles, AnsiColor};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::proto::{ use crate::proto::{
@ -26,80 +23,97 @@ use crate::proto::{
}; };
pub fn parser() -> Command<'static> { #[derive(Debug, Parser)]
Command::new("creddy") #[command(
.version(env!("CARGO_PKG_VERSION")) about,
.about("A friendly credential manager") version,
.arg( name = "creddy",
Arg::new("server_addr") bin_name = "creddy",
.short('a') styles = Styles::styled()
.long("server-addr") .header(AnsiColor::Yellow.on_default())
.takes_value(true) .usage(AnsiColor::Yellow.on_default())
.value_parser(value_parser!(PathBuf)) .literal(AnsiColor::Green.on_default())
.help("Connect to the main Creddy process at this address") .placeholder(AnsiColor::Green.on_default())
) )]
.subcommand( /// A friendly credential manager
Command::new("run") pub struct Cli {
.about("Launch Creddy") #[command(flatten)]
) pub global_args: GlobalArgs,
.subcommand(
Command::new("get") #[command(subcommand)]
.about("Request AWS credentials from Creddy and output to stdout") pub action: Option<Action>,
.arg( }
Arg::new("base")
.short('b') impl Cli {
.long("base") // proxy the Parser method so that main crate doesn't have to depend on Clap
.action(ArgAction::SetTrue) pub fn parse() -> Self {
.help("Use base credentials instead of session credentials") <Self as Parser>::parse()
) }
.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"])
)
)
)
} }
pub fn get(args: &ArgMatches, global_args: &ArgMatches) -> anyhow::Result<()> { #[derive(Debug, Clone, Args)]
let name = args.get_one("name").cloned(); pub struct GlobalArgs {
let base = *args.get_one("base").unwrap_or(&false); /// Connect to the main Creddy application at this path
let addr = global_args.get_one("server_addr").cloned(); #[arg(long, short = 'a')]
server_addr: Option<PathBuf>,
}
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<String>,
/// 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<String>,
}
#[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}"), r => bail!("Unexpected response from server: {r}"),
}; };
println!("{output}"); 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<()> { pub fn exec(args: ExecArgs, global: GlobalArgs) -> 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();
// Clap guarantees that cmd_line will be a sequence of at least 1 item // Clap guarantees that cmd_line will be a sequence of at least 1 item
// test this! // test this!
let mut cmd_line = args.get_many("command").unwrap(); let mut cmd_line = args.command.iter();
let cmd_name: &String = cmd_line.next().unwrap(); let cmd_name = cmd_line.next().unwrap();
let mut cmd = ChildCommand::new(cmd_name); let mut cmd = ChildCommand::new(cmd_name);
cmd.args(cmd_line); 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)) => { CliResponse::Credential(CliCredential::AwsBase(creds)) => {
cmd.env("AWS_ACCESS_KEY_ID", creds.access_key_id); cmd.env("AWS_ACCESS_KEY_ID", creds.access_key_id);
cmd.env("AWS_SECRET_ACCESS_KEY", creds.secret_access_key); 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)] #[cfg(unix)]
{ {
// cmd.exec() never returns if successful
let e = cmd.exec(); 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(|| { Err(e).with_context(|| {
// eventually figure out how to display the actual command // eventually figure out how to display the actual command
format!("Failed to execute command") format!("Failed to execute command: {}", args.command.join(" "))
})?; })?;
Ok(()) 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<()> { pub fn invoke_shortcut(args: InvokeArgs, global: GlobalArgs) -> anyhow::Result<()> {
let addr = global_args.get_one("server_addr").cloned(); let req = CliRequest::InvokeShortcut(args.shortcut_action);
let action = match args.get_one::<String>("action").map(|s| s.as_str()) { match make_request(global.server_addr, &req)?? {
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)?? {
CliResponse::Empty => Ok(()), CliResponse::Empty => Ok(()),
r => bail!("Unexpected response from server: {r}"), r => bail!("Unexpected response from server: {r}"),
} }

View File

@ -1,8 +1,9 @@
mod cli; mod cli;
pub use cli::{ pub use cli::{
Cli,
Action,
exec, exec,
get, get,
parser,
invoke_shortcut, invoke_shortcut,
}; };

View File

@ -1,19 +1,20 @@
use std::env; use std::env;
use std::process::{self, Command}; use std::process::{self, Command};
use creddy_cli::{Action, Cli};
fn main() { fn main() {
let global_matches = creddy_cli::parser().get_matches(); let cli = Cli::parse();
let res = match global_matches.subcommand() { let res = match cli.action {
None | Some(("run", _)) => launch_gui(), None | Some(Action::Run)=> launch_gui(),
Some(("get", m)) => creddy_cli::get(m, &global_matches), Some(Action::Get(args)) => creddy_cli::get(args, cli.global_args),
Some(("exec", m)) => creddy_cli::exec(m, &global_matches), Some(Action::Exec(args)) => creddy_cli::exec(args, cli.global_args),
Some(("shortcut", m)) => creddy_cli::invoke_shortcut(m, &global_matches), Some(Action::Shortcut(args)) => creddy_cli::invoke_shortcut(args, cli.global_args),
_ => unreachable!("Unknown subcommand"),
}; };
if let Err(e) = res { if let Err(e) = res {
eprintln!("Error: {e}"); eprintln!("Error: {e:?}");
process::exit(1); process::exit(1);
} }
} }

View File

@ -4,6 +4,7 @@ use std::fmt::{
Error as FmtError Error as FmtError
}; };
use clap::ValueEnum;
use serde::{Serialize, Deserialize}; 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 { pub enum ShortcutAction {
ShowWindow, ShowWindow,
LaunchTerminal, LaunchTerminal,

View File

@ -3,23 +3,24 @@
windows_subsystem = "windows" windows_subsystem = "windows"
)] )]
use creddy::{ use creddy::{
app, app,
errors::ShowError, errors::ShowError,
}; };
use creddy_cli::{Action, Cli};
fn main() { fn main() {
let global_matches = creddy_cli::parser().get_matches(); let cli = Cli::parse();
let res = match global_matches.subcommand() { let res = match cli.action {
None | Some(("run", _)) => { None | Some(Action::Run) => {
app::run().error_popup("Creddy encountered an error"); app::run().error_popup("Creddy encountered an error");
Ok(()) Ok(())
}, },
Some(("get", m)) => creddy_cli::get(m, &global_matches), Some(Action::Get(args)) => creddy_cli::get(args, cli.global_args),
Some(("exec", m)) => creddy_cli::exec(m, &global_matches), Some(Action::Exec(args)) => creddy_cli::exec(args, cli.global_args),
Some(("shortcut", m)) => creddy_cli::invoke_shortcut(m, &global_matches), Some(Action::Shortcut(args)) => creddy_cli::invoke_shortcut(args, cli.global_args),
_ => unreachable!(),
}; };
if let Err(e) = res { if let Err(e) = res {

View File

@ -50,7 +50,7 @@
} }
}, },
"productName": "creddy", "productName": "creddy",
"version": "0.5.3", "version": "0.5.4",
"identifier": "creddy", "identifier": "creddy",
"plugins": {}, "plugins": {},
"app": { "app": {