From f9087bea50501cc7c7d84b1147b2088a7439465f Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 8 Jan 2024 03:00:36 +0000 Subject: [PATCH] Migrate from `gumdrop` to `clap` Closes str4d/rage#437. --- Cargo.lock | 46 ++-- Cargo.toml | 2 +- age-plugin/Cargo.toml | 2 +- age-plugin/README.md | 14 +- age-plugin/examples/age-plugin-unencrypted.rs | 11 +- age-plugin/src/lib.rs | 14 +- rage/CHANGELOG.md | 1 + rage/Cargo.toml | 3 +- rage/i18n/en-US/rage.ftl | 8 +- rage/i18n/es-AR/rage.ftl | 8 +- rage/i18n/it/rage.ftl | 8 +- rage/i18n/zh-CN/rage.ftl | 8 +- rage/i18n/zh-TW/rage.ftl | 8 +- rage/src/bin/rage-keygen/main.rs | 85 +++---- rage/src/bin/rage-mount/main.rs | 53 ++--- rage/src/bin/rage/main.rs | 212 +++++++++--------- rage/tests/cmd/rage-keygen/help.toml | 14 +- rage/tests/cmd/rage-keygen/help_it.toml | 14 +- rage/tests/cmd/rage-mount/help.toml | 24 +- rage/tests/cmd/rage-mount/help_it.toml | 24 +- rage/tests/cmd/rage/help.toml | 43 ++-- rage/tests/cmd/rage/help_it.toml | 43 ++-- supply-chain/config.toml | 30 +-- supply-chain/imports.lock | 23 +- 24 files changed, 333 insertions(+), 365 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2d793d4..5721935d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,7 +126,7 @@ dependencies = [ "base64", "bech32", "chrono", - "gumdrop", + "clap", ] [[package]] @@ -497,6 +497,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb690e81c7840c0d7aade59f242ea3b41b9bc27bcd5997890e7702ae4b32e487" dependencies = [ "clap_builder", + "clap_derive", + "once_cell", ] [[package]] @@ -505,8 +507,10 @@ version = "4.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ed2e96bc16d8d740f6f48d663eddf4b8a0983e79210fd55479b7bcd0a69860e" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", ] [[package]] @@ -518,6 +522,18 @@ dependencies = [ "clap", ] +[[package]] +name = "clap_derive" +version = "4.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.46", +] + [[package]] name = "clap_lex" version = "0.5.0" @@ -1118,26 +1134,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "gumdrop" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" -dependencies = [ - "gumdrop_derive", -] - -[[package]] -name = "gumdrop_derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "half" version = "1.8.2" @@ -1150,6 +1146,12 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.3" @@ -2002,7 +2004,6 @@ dependencies = [ "flate2", "fuse_mt", "fuser", - "gumdrop", "i18n-embed", "i18n-embed-fl", "lazy_static", @@ -2521,7 +2522,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote", "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index 0084cf25..ae8fcf28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ rust-embed = "8" # CLI chrono = "0.4" +clap = { version = "4.3", features = ["derive"] } console = { version = "0.15", default-features = false } env_logger = "0.10" -gumdrop = "0.8" log = "0.4" diff --git a/age-plugin/Cargo.toml b/age-plugin/Cargo.toml index 4a972788..12712258 100644 --- a/age-plugin/Cargo.toml +++ b/age-plugin/Cargo.toml @@ -21,7 +21,7 @@ bech32.workspace = true chrono.workspace = true [dev-dependencies] -gumdrop.workspace = true +clap.workspace = true [lib] bench = false diff --git a/age-plugin/README.md b/age-plugin/README.md index 22caf6df..d1300f32 100644 --- a/age-plugin/README.md +++ b/age-plugin/README.md @@ -67,7 +67,7 @@ users will use identity files containing identities that specify that plugin nam ## Example plugin binary -The following example uses `gumdrop` to parse CLI arguments, but any argument parsing +The following example uses `clap` to parse CLI arguments, but any argument parsing logic will work as long as it can detect the `--age-plugin=STATE_MACHINE` flag. ```rust @@ -78,7 +78,8 @@ use age_plugin::{ recipient::{self, RecipientPluginV1}, Callbacks, run_state_machine, }; -use gumdrop::Options; +use clap::Parser; + use std::collections::HashMap; use std::io; @@ -133,17 +134,14 @@ impl IdentityPluginV1 for IdentityPlugin { } } -#[derive(Debug, Options)] +#[derive(Debug, Parser)] struct PluginOptions { - #[options(help = "print help message")] - help: bool, - - #[options(help = "run the given age plugin state machine", no_short)] + #[arg(help = "run the given age plugin state machine", long)] age_plugin: Option, } fn main() -> io::Result<()> { - let opts = PluginOptions::parse_args_default_or_exit(); + let opts = PluginOptions::parse(); if let Some(state_machine) = opts.age_plugin { // The plugin was started by an age client; run the state machine. diff --git a/age-plugin/examples/age-plugin-unencrypted.rs b/age-plugin/examples/age-plugin-unencrypted.rs index 86cc64f1..e287f289 100644 --- a/age-plugin/examples/age-plugin-unencrypted.rs +++ b/age-plugin/examples/age-plugin-unencrypted.rs @@ -8,7 +8,7 @@ use age_plugin::{ recipient::{self, RecipientPluginV1}, run_state_machine, Callbacks, }; -use gumdrop::Options; +use clap::Parser; use std::collections::HashMap; use std::env; @@ -139,17 +139,14 @@ impl IdentityPluginV1 for IdentityPlugin { } } -#[derive(Debug, Options)] +#[derive(Debug, Parser)] struct PluginOptions { - #[options(help = "print help message")] - help: bool, - - #[options(help = "run the given age plugin state machine", no_short)] + #[arg(help = "run the given age plugin state machine", long)] age_plugin: Option, } fn main() -> io::Result<()> { - let opts = PluginOptions::parse_args_default_or_exit(); + let opts = PluginOptions::parse(); if let Some(state_machine) = opts.age_plugin { run_state_machine( diff --git a/age-plugin/src/lib.rs b/age-plugin/src/lib.rs index 5dc2563d..fedd7251 100644 --- a/age-plugin/src/lib.rs +++ b/age-plugin/src/lib.rs @@ -65,7 +65,7 @@ //! //! # Example plugin binary //! -//! The following example uses `gumdrop` to parse CLI arguments, but any argument parsing +//! The following example uses `clap` to parse CLI arguments, but any argument parsing //! logic will work as long as it can detect the `--age-plugin=STATE_MACHINE` flag. //! //! ``` @@ -76,7 +76,8 @@ //! recipient::{self, RecipientPluginV1}, //! Callbacks, run_state_machine, //! }; -//! use gumdrop::Options; +//! use clap::Parser; +//! //! use std::collections::HashMap; //! use std::io; //! @@ -131,17 +132,14 @@ //! } //! } //! -//! #[derive(Debug, Options)] +//! #[derive(Debug, Parser)] //! struct PluginOptions { -//! #[options(help = "print help message")] -//! help: bool, -//! -//! #[options(help = "run the given age plugin state machine", no_short)] +//! #[arg(help = "run the given age plugin state machine", long)] //! age_plugin: Option, //! } //! //! fn main() -> io::Result<()> { -//! let opts = PluginOptions::parse_args_default_or_exit(); +//! let opts = PluginOptions::parse(); //! //! if let Some(state_machine) = opts.age_plugin { //! // The plugin was started by an age client; run the state machine. diff --git a/rage/CHANGELOG.md b/rage/CHANGELOG.md index 1fc70fc9..4c352264 100644 --- a/rage/CHANGELOG.md +++ b/rage/CHANGELOG.md @@ -11,6 +11,7 @@ to 1.0.0 are beta releases. ## [Unreleased] ### Changed - MSRV is now 1.65.0. +- Migrated from `gumdrop` to `clap` for argument parsing. ### Fixed - OpenSSH private keys passed to `-i/--identity` that contain invalid public diff --git a/rage/Cargo.toml b/rage/Cargo.toml index 8ff9cd08..6ea0c454 100644 --- a/rage/Cargo.toml +++ b/rage/Cargo.toml @@ -56,9 +56,9 @@ maintenance = { status = "experimental" } # rage and rage-keygen dependencies age = { workspace = true, features = ["armor", "cli-common", "plugin"] } chrono.workspace = true +clap.workspace = true console.workspace = true env_logger.workspace = true -gumdrop.workspace = true i18n-embed = { workspace = true, features = ["desktop-requester"] } i18n-embed-fl.workspace = true lazy_static.workspace = true @@ -76,7 +76,6 @@ time = { version = ">=0.3.7, <0.3.24", optional = true } # time 0.3.24 has MSRV zip = { version = "0.6.2", optional = true } [dev-dependencies] -clap = { version = "4", default-features = false } clap_complete = "4" flate2 = "1" man = "0.3" diff --git a/rage/i18n/en-US/rage.ftl b/rage/i18n/en-US/rage.ftl index cb3446c6..db4ef496 100644 --- a/rage/i18n/en-US/rage.ftl +++ b/rage/i18n/en-US/rage.ftl @@ -35,13 +35,7 @@ usage-header = Usage: -rage-usage = - {usage-header} - {" "}{$usage_a} - {" "}{$usage_b} - - {$flags} - +rage-after-help = {-input} defaults to standard input, and {-output} defaults to standard output. {-recipient} can be: diff --git a/rage/i18n/es-AR/rage.ftl b/rage/i18n/es-AR/rage.ftl index e1e00fb9..45df2d1b 100644 --- a/rage/i18n/es-AR/rage.ftl +++ b/rage/i18n/es-AR/rage.ftl @@ -35,13 +35,7 @@ usage-header = Usage: -rage-usage = - {usage-header} - {" "}{$usage_a} - {" "}{$usage_b} - - {$flags} - +rage-after-help = {-input} por defecto a standard input, y {-output} por defecto standard output. {-recipient} puede ser: diff --git a/rage/i18n/it/rage.ftl b/rage/i18n/it/rage.ftl index 32b0e179..c146cbfd 100644 --- a/rage/i18n/it/rage.ftl +++ b/rage/i18n/it/rage.ftl @@ -35,13 +35,7 @@ usage-header = Usage: -rage-usage = - {usage-header} - {" "}{$usage_a} - {" "}{$usage_b} - - {$flags} - +rage-after-help = {-input} ha come valore predefinito lo standard input, e {-output} ha come valore predefinito lo standard output. diff --git a/rage/i18n/zh-CN/rage.ftl b/rage/i18n/zh-CN/rage.ftl index 4823f5e5..4fe28b05 100644 --- a/rage/i18n/zh-CN/rage.ftl +++ b/rage/i18n/zh-CN/rage.ftl @@ -35,13 +35,7 @@ usage-header = Usage: -rage-usage = - {usage-header} - {" "}{$usage_a} - {" "}{$usage_b} - - {$flags} - +rage-after-help = {-input} 默认为标准输入 (stdin), 而 {-output} 默认为标准输出 (stdout) 。 {-recipient} 可为: diff --git a/rage/i18n/zh-TW/rage.ftl b/rage/i18n/zh-TW/rage.ftl index 58d4fea7..733ca15a 100644 --- a/rage/i18n/zh-TW/rage.ftl +++ b/rage/i18n/zh-TW/rage.ftl @@ -35,13 +35,7 @@ usage-header = Usage: -rage-usage = - {usage-header} - {" "}{$usage_a} - {" "}{$usage_b} - - {$flags} - +rage-after-help = {-input} 默認為標準輸入 (stdin), 而 {-output} 默認為標準輸出 (stdout) 。 {-recipient} 可為: diff --git a/rage/src/bin/rage-keygen/main.rs b/rage/src/bin/rage-keygen/main.rs index a00d7559..3c577a69 100644 --- a/rage/src/bin/rage-keygen/main.rs +++ b/rage/src/bin/rage-keygen/main.rs @@ -1,7 +1,7 @@ #![forbid(unsafe_code)] use age::{cli_common::file_io, secrecy::ExposeSecret}; -use gumdrop::Options; +use clap::{ArgAction, Parser}; use i18n_embed::{ fluent::{fluent_language_loader, FluentLanguageLoader}, DesktopLanguageRequester, @@ -31,15 +31,23 @@ macro_rules! fl { }}; } -#[derive(Debug, Options)] +#[derive(Debug, Parser)] +#[command(display_name = "rage-keygen")] +#[command(name = "rage-keygen")] +#[command(version)] +#[command(disable_help_flag(true))] +#[command(disable_version_flag(true))] struct AgeOptions { - #[options(help = "Print this help message and exit.")] - help: bool, + #[arg(action = ArgAction::Help, short, long)] + #[arg(help = "Print this help message and exit.")] + help: Option, - #[options(help = "Print version info and exit.", short = "V")] - version: bool, + #[arg(action = ArgAction::Version, short = 'V', long)] + #[arg(help = "Print version info and exit.")] + version: Option, - #[options(help = "Write the result to the file at path OUTPUT. Defaults to standard output.")] + #[arg(short, long)] + #[arg(help = "Write the result to the file at path OUTPUT. Defaults to standard output.")] output: Option, } @@ -57,40 +65,35 @@ fn main() -> Result<(), error::Error> { // Isolation Marks, so we disable them for now. LANGUAGE_LOADER.set_use_isolating(false); - let opts = AgeOptions::parse_args_default_or_exit(); + let opts = AgeOptions::parse(); + + let mut output = file_io::OutputWriter::new( + opts.output, + false, + file_io::OutputFormat::Text, + 0o600, + false, + ) + .map_err(error::Error::FailedToOpenOutput)?; + + let sk = age::x25519::Identity::generate(); + let pk = sk.to_public(); + + (|| { + writeln!( + output, + "# {}: {}", + fl!("identity-file-created"), + chrono::Local::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true) + )?; + writeln!(output, "# {}: {}", fl!("identity-file-pubkey"), pk)?; + writeln!(output, "{}", sk.to_string().expose_secret())?; + + if !output.is_terminal() { + eprintln!("{}: {}", fl!("tty-pubkey"), pk); + } - if opts.version { - println!("rage-keygen {}", env!("CARGO_PKG_VERSION")); Ok(()) - } else { - let mut output = file_io::OutputWriter::new( - opts.output, - false, - file_io::OutputFormat::Text, - 0o600, - false, - ) - .map_err(error::Error::FailedToOpenOutput)?; - - let sk = age::x25519::Identity::generate(); - let pk = sk.to_public(); - - (|| { - writeln!( - output, - "# {}: {}", - fl!("identity-file-created"), - chrono::Local::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true) - )?; - writeln!(output, "# {}: {}", fl!("identity-file-pubkey"), pk)?; - writeln!(output, "{}", sk.to_string().expose_secret())?; - - if !output.is_terminal() { - eprintln!("{}: {}", fl!("tty-pubkey"), pk); - } - - Ok(()) - })() - .map_err(error::Error::FailedToWriteOutput) - } + })() + .map_err(error::Error::FailedToWriteOutput) } diff --git a/rage/src/bin/rage-mount/main.rs b/rage/src/bin/rage-mount/main.rs index 3d19bc78..746f6ae2 100644 --- a/rage/src/bin/rage-mount/main.rs +++ b/rage/src/bin/rage-mount/main.rs @@ -5,9 +5,9 @@ use age::{ cli_common::{read_identities, read_secret}, stream::StreamReader, }; +use clap::{ArgAction, CommandFactory, Parser}; use fuse_mt::FilesystemMT; use fuser::MountOption; -use gumdrop::Options; use i18n_embed::{ fluent::{fluent_language_loader, FluentLanguageLoader}, DesktopLanguageRequester, @@ -125,31 +125,37 @@ impl fmt::Debug for Error { } } -#[derive(Debug, Options)] +#[derive(Debug, Parser)] +#[command(display_name = "rage-mount")] +#[command(name = "rage-mount")] +#[command(version)] +#[command(disable_help_flag(true))] +#[command(disable_version_flag(true))] struct AgeMountOptions { - #[options(free, help = "The encrypted filesystem to mount.")] + #[arg(help = "The encrypted filesystem to mount.")] filename: String, - #[options(free, help = "The directory to mount the filesystem at.")] + #[arg(help = "The directory to mount the filesystem at.")] mountpoint: String, - #[options(help = "Print this help message and exit.")] - help: bool, + #[arg(action = ArgAction::Help, short, long)] + #[arg(help = "Print this help message and exit.")] + help: Option, - #[options(help = "Print version info and exit.", short = "V")] - version: bool, + #[arg(action = ArgAction::Version, short = 'V', long)] + #[arg(help = "Print version info and exit.")] + version: Option, - #[options(help = "Indicates the filesystem type (one of \"tar\", \"zip\").")] + #[arg(short, long)] + #[arg(help = "Indicates the filesystem type (one of \"tar\", \"zip\").")] types: String, - #[options( - help = "Maximum work factor to allow for passphrase decryption.", - meta = "WF", - no_short - )] + #[arg(long, value_name = "WF")] + #[arg(help = "Maximum work factor to allow for passphrase decryption.")] max_work_factor: Option, - #[options(help = "Use the private key file at IDENTITY. May be repeated.")] + #[arg(short, long)] + #[arg(help = "Use the private key file at IDENTITY. May be repeated.")] identity: Vec, } @@ -219,24 +225,13 @@ fn main() -> Result<(), Error> { // Isolation Marks, so we disable them for now. LANGUAGE_LOADER.set_use_isolating(false); - let args = args().collect::>(); - - if console::user_attended() && args.len() == 1 { - // If gumdrop ever merges that PR, that can be used here - // instead. - println!("{} {} [OPTIONS]", fl!("usage-header"), args[0]); - println!(); - println!("{}", AgeMountOptions::usage()); - + if console::user_attended() && args().len() == 1 { + AgeMountOptions::command().print_help()?; return Ok(()); } - let opts = AgeMountOptions::parse_args_default_or_exit(); + let opts = AgeMountOptions::parse(); - if opts.version { - println!("rage-mount {}", env!("CARGO_PKG_VERSION")); - return Ok(()); - } if opts.filename.is_empty() { return Err(Error::MissingFilename); } diff --git a/rage/src/bin/rage/main.rs b/rage/src/bin/rage/main.rs index 1c4481a8..1624a79a 100644 --- a/rage/src/bin/rage/main.rs +++ b/rage/src/bin/rage/main.rs @@ -9,7 +9,7 @@ use age::{ secrecy::ExposeSecret, Identity, IdentityFile, IdentityFileEntry, Recipient, }; -use gumdrop::{Options, ParsingStyle}; +use clap::{ArgAction, CommandFactory, Parser}; use i18n_embed::{ fluent::{fluent_language_loader, FluentLanguageLoader}, DesktopLanguageRequester, @@ -233,56 +233,106 @@ fn read_recipients( Ok(recipients) } -#[derive(Debug, Options)] +fn binary_name() -> String { + if let Some(arg) = std::env::args_os().next() { + Path::new(&arg) + .file_name() + .expect("is not directory") + .to_string_lossy() + .to_string() + } else { + "rage".into() + } +} + +fn usage() -> String { + let binary_name = binary_name(); + format!( + "{binary_name} [--encrypt] -r RECIPIENT [-i IDENTITY] [-a] [-o OUTPUT] [INPUT]\n \ + {binary_name} --decrypt [-i IDENTITY] [-o OUTPUT] [INPUT]", + ) +} + +fn after_help() -> String { + let binary_name = binary_name(); + let keygen_name = format!("{}-keygen", binary_name); + let example_a = format!("$ {} -o key.txt", keygen_name); + let example_a_output = "age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"; + let example_b = format!( + "$ tar cvz ~/data | {} -r {} > data.tar.gz.age", + binary_name, example_a_output, + ); + let example_c = format!( + "$ {} -d -i key.txt -o data.tar.gz data.tar.gz.age", + binary_name, + ); + + fl!( + "rage-after-help", + keygen_name = keygen_name, + example_a = example_a, + example_a_output = example_a_output, + example_b = example_b, + example_c = example_c, + ) +} + +#[derive(Debug, Parser)] +#[command(version)] +#[command(override_usage(usage()))] +#[command(disable_help_flag(true))] +#[command(disable_version_flag(true))] +#[command(after_help(after_help()))] struct AgeOptions { - #[options(free, help = "Path to a file to read from.")] + #[arg(help = "Path to a file to read from.")] input: Option, - #[options(help = "Print this help message and exit.")] - help: bool, + #[arg(action = ArgAction::Help, short, long)] + #[arg(help = "Print this help message and exit.")] + help: Option, - #[options(help = "Print version info and exit.", short = "V")] - version: bool, + #[arg(action = ArgAction::Version, short = 'V', long)] + #[arg(help = "Print version info and exit.")] + version: Option, - #[options(help = "Encrypt the input (the default).")] + #[arg(short, long)] + #[arg(help = "Encrypt the input (the default).")] encrypt: bool, - #[options(help = "Decrypt the input.")] + #[arg(short, long)] + #[arg(help = "Decrypt the input.")] decrypt: bool, - #[options(help = "Encrypt with a passphrase instead of recipients.")] + #[arg(short, long)] + #[arg(help = "Encrypt with a passphrase instead of recipients.")] passphrase: bool, - #[options( - help = "Maximum work factor to allow for passphrase decryption.", - meta = "WF", - no_short - )] + #[arg(long, value_name = "WF")] + #[arg(help = "Maximum work factor to allow for passphrase decryption.")] max_work_factor: Option, - #[options(help = "Encrypt to a PEM encoded format.")] + #[arg(short, long)] + #[arg(help = "Encrypt to a PEM encoded format.")] armor: bool, - #[options(help = "Encrypt to the specified RECIPIENT. May be repeated.")] + #[arg(short, long)] + #[arg(help = "Encrypt to the specified RECIPIENT. May be repeated.")] recipient: Vec, - #[options( - help = "Encrypt to the recipients listed at PATH. May be repeated.", - meta = "PATH" - )] + #[arg(short = 'R', long, value_name = "PATH")] + #[arg(help = "Encrypt to the recipients listed at PATH. May be repeated.")] recipients_file: Vec, - #[options(help = "Use the identity file at IDENTITY. May be repeated.")] + #[arg(short, long)] + #[arg(help = "Use the identity file at IDENTITY. May be repeated.")] identity: Vec, - #[options( - help = "Use age-plugin-PLUGIN-NAME in its default mode as an identity.", - no_long, - short = "j" - )] - plugin_name: String, + #[arg(short = 'j', value_name = "PLUGIN-NAME")] + #[arg(help = "Use age-plugin-PLUGIN-NAME in its default mode as an identity.")] + plugin_name: Option, - #[options(help = "Write the result to the file at path OUTPUT.")] + #[arg(short, long)] + #[arg(help = "Write the result to the file at path OUTPUT.")] output: Option, } @@ -344,7 +394,7 @@ impl io::Read for ReadChecker { } fn encrypt(opts: AgeOptions) -> Result<(), error::EncryptError> { - if !opts.plugin_name.is_empty() { + if opts.plugin_name.is_some() { return Err(error::EncryptError::PluginNameFlag); } @@ -479,7 +529,7 @@ fn decrypt(opts: AgeOptions) -> Result<(), error::DecryptError> { return Err(error::DecryptError::RecipientsFileFlag); } - if !(opts.identity.is_empty() || opts.plugin_name.is_empty()) { + if !(opts.identity.is_empty() || opts.plugin_name.is_none()) { return Err(error::DecryptError::MixedIdentityAndPluginName); } @@ -549,13 +599,14 @@ fn decrypt(opts: AgeOptions) -> Result<(), error::DecryptError> { } } age::Decryptor::Recipients(decryptor) => { - let identities = if opts.plugin_name.is_empty() { + let plugin_name = opts.plugin_name.as_deref().unwrap_or_default(); + let identities = if plugin_name.is_empty() { read_identities(opts.identity, opts.max_work_factor)? } else { // Construct the default plugin. vec![Box::new(plugin::IdentityPluginV1::new( - &opts.plugin_name, - &[plugin::Identity::default_for_plugin(&opts.plugin_name)], + plugin_name, + &[plugin::Identity::default_for_plugin(plugin_name)], UiCallbacks, )?) as Box] }; @@ -588,82 +639,39 @@ fn main() -> Result<(), error::Error> { // Isolation Marks, so we disable them for now. LANGUAGE_LOADER.set_use_isolating(false); - let args = args().collect::>(); - - let opts = AgeOptions::parse_args(&args[1..], ParsingStyle::default()).unwrap_or_else(|e| { - eprintln!("{}: {}", args[0], e); - std::process::exit(2); - }); - // If you are piping input with no other args, this will not allow // it. - if (console::user_attended() && args.len() == 1) || opts.help_requested() { - let binary_name = args[0].as_str(); - let keygen_name = format!("{}-keygen", binary_name); - let usage_a = format!( - "{} [--encrypt] -r RECIPIENT [-i IDENTITY] [-a] [-o OUTPUT] [INPUT]", - binary_name - ); - let usage_b = format!( - "{} --decrypt [-i IDENTITY] [-o OUTPUT] [INPUT]", - binary_name - ); - let example_a = format!("$ {} -o key.txt", keygen_name); - let example_a_output = "age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"; - let example_b = format!( - "$ tar cvz ~/data | {} -r {} > data.tar.gz.age", - binary_name, example_a_output - ); - let example_c = format!( - "$ {} -d -i key.txt -o data.tar.gz data.tar.gz.age", - binary_name - ); - - println!( - "{}", - fl!( - "rage-usage", - usage_a = usage_a, - usage_b = usage_b, - flags = AgeOptions::usage(), - keygen_name = keygen_name, - example_a = example_a, - example_a_output = example_a_output, - example_b = example_b, - example_c = example_c, - ) - ); - + if console::user_attended() && args().len() == 1 { + AgeOptions::command() + .print_help() + .map_err(error::EncryptError::Io)?; return Ok(()); } - if opts.version { - println!("rage {}", env!("CARGO_PKG_VERSION")); - Ok(()) - } else { - if opts.encrypt && opts.decrypt { - return Err(error::Error::MixedEncryptAndDecrypt); - } - if !(opts.identity.is_empty() || opts.encrypt || opts.decrypt) { - return Err(error::Error::IdentityFlagAmbiguous); - } + let opts = AgeOptions::parse(); - if let (Some(in_file), Some(out_file)) = (&opts.input, &opts.output) { - // Check that the given filenames do not correspond to the same file. - let in_path = Path::new(&in_file); - let out_path = Path::new(&out_file); - match (in_path.canonicalize(), out_path.canonicalize()) { - (Ok(in_abs), Ok(out_abs)) if in_abs == out_abs => { - return Err(error::Error::SameInputAndOutput(out_file.clone())); - } - _ => (), + if opts.encrypt && opts.decrypt { + return Err(error::Error::MixedEncryptAndDecrypt); + } + if !(opts.identity.is_empty() || opts.encrypt || opts.decrypt) { + return Err(error::Error::IdentityFlagAmbiguous); + } + + if let (Some(in_file), Some(out_file)) = (&opts.input, &opts.output) { + // Check that the given filenames do not correspond to the same file. + let in_path = Path::new(&in_file); + let out_path = Path::new(&out_file); + match (in_path.canonicalize(), out_path.canonicalize()) { + (Ok(in_abs), Ok(out_abs)) if in_abs == out_abs => { + return Err(error::Error::SameInputAndOutput(out_file.clone())); } + _ => (), } + } - if opts.decrypt { - decrypt(opts).map_err(error::Error::from) - } else { - encrypt(opts).map_err(error::Error::from) - } + if opts.decrypt { + decrypt(opts).map_err(error::Error::from) + } else { + encrypt(opts).map_err(error::Error::from) } } diff --git a/rage/tests/cmd/rage-keygen/help.toml b/rage/tests/cmd/rage-keygen/help.toml index 96883960..58ef3fdb 100644 --- a/rage/tests/cmd/rage-keygen/help.toml +++ b/rage/tests/cmd/rage-keygen/help.toml @@ -1,11 +1,11 @@ bin.name = "rage-keygen" args = "--help" -stdout = "" -stderr = """ -Usage: [..]rage-keygen[EXE] [OPTIONS] +stdout = """ +Usage: rage-keygen[EXE] [OPTIONS] -Optional arguments: - -h, --help Print this help message and exit. - -V, --version Print version info and exit. - -o, --output OUTPUT Write the result to the file at path OUTPUT. Defaults to standard output. +Options: + -h, --help Print this help message and exit. + -V, --version Print version info and exit. + -o, --output Write the result to the file at path OUTPUT. Defaults to standard output. """ +stderr = "" diff --git a/rage/tests/cmd/rage-keygen/help_it.toml b/rage/tests/cmd/rage-keygen/help_it.toml index 2e2814de..3922b9e4 100644 --- a/rage/tests/cmd/rage-keygen/help_it.toml +++ b/rage/tests/cmd/rage-keygen/help_it.toml @@ -1,12 +1,12 @@ bin.name = "rage-keygen" args = "--help" env.add.LANG = "it" -stdout = "" -stderr = """ -Usage: [..]rage-keygen[EXE] [OPTIONS] +stdout = """ +Usage: rage-keygen[EXE] [OPTIONS] -Optional arguments: - -h, --help Print this help message and exit. - -V, --version Print version info and exit. - -o, --output OUTPUT Write the result to the file at path OUTPUT. Defaults to standard output. +Options: + -h, --help Print this help message and exit. + -V, --version Print version info and exit. + -o, --output Write the result to the file at path OUTPUT. Defaults to standard output. """ +stderr = "" diff --git a/rage/tests/cmd/rage-mount/help.toml b/rage/tests/cmd/rage-mount/help.toml index 52e91e0b..c78c5679 100644 --- a/rage/tests/cmd/rage-mount/help.toml +++ b/rage/tests/cmd/rage-mount/help.toml @@ -1,17 +1,17 @@ bin.name = "rage-mount" args = "--help" -stdout = "" -stderr = """ -Usage: [..]rage-mount[EXE] [OPTIONS] +stdout = """ +Usage: rage-mount[EXE] [OPTIONS] --types -Positional arguments: - filename The encrypted filesystem to mount. - mountpoint The directory to mount the filesystem at. +Arguments: + The encrypted filesystem to mount. + The directory to mount the filesystem at. -Optional arguments: - -h, --help Print this help message and exit. - -V, --version Print version info and exit. - -t, --types TYPES Indicates the filesystem type (one of "tar", "zip"). - --max-work-factor WF Maximum work factor to allow for passphrase decryption. - -i, --identity IDENTITY Use the private key file at IDENTITY. May be repeated. +Options: + -h, --help Print this help message and exit. + -V, --version Print version info and exit. + -t, --types Indicates the filesystem type (one of "tar", "zip"). + --max-work-factor Maximum work factor to allow for passphrase decryption. + -i, --identity Use the private key file at IDENTITY. May be repeated. """ +stderr = "" diff --git a/rage/tests/cmd/rage-mount/help_it.toml b/rage/tests/cmd/rage-mount/help_it.toml index c4730f21..28f4e662 100644 --- a/rage/tests/cmd/rage-mount/help_it.toml +++ b/rage/tests/cmd/rage-mount/help_it.toml @@ -1,18 +1,18 @@ bin.name = "rage-mount" args = "--help" env.add.LANG = "it" -stdout = "" -stderr = """ -Usage: [..]rage-mount[EXE] [OPTIONS] +stdout = """ +Usage: rage-mount[EXE] [OPTIONS] --types -Positional arguments: - filename The encrypted filesystem to mount. - mountpoint The directory to mount the filesystem at. +Arguments: + The encrypted filesystem to mount. + The directory to mount the filesystem at. -Optional arguments: - -h, --help Print this help message and exit. - -V, --version Print version info and exit. - -t, --types TYPES Indicates the filesystem type (one of "tar", "zip"). - --max-work-factor WF Maximum work factor to allow for passphrase decryption. - -i, --identity IDENTITY Use the private key file at IDENTITY. May be repeated. +Options: + -h, --help Print this help message and exit. + -V, --version Print version info and exit. + -t, --types Indicates the filesystem type (one of "tar", "zip"). + --max-work-factor Maximum work factor to allow for passphrase decryption. + -i, --identity Use the private key file at IDENTITY. May be repeated. """ +stderr = "" diff --git a/rage/tests/cmd/rage/help.toml b/rage/tests/cmd/rage/help.toml index 95d47361..7e875b07 100644 --- a/rage/tests/cmd/rage/help.toml +++ b/rage/tests/cmd/rage/help.toml @@ -1,31 +1,30 @@ bin.name = "rage" args = "--help" stdout = """ -Usage: - [..]rage[EXE] [--encrypt] -r RECIPIENT [-i IDENTITY] [-a] [-o OUTPUT] [INPUT] - [..]rage[EXE] --decrypt [-i IDENTITY] [-o OUTPUT] [INPUT] +Usage: rage[EXE] [--encrypt] -r RECIPIENT [-i IDENTITY] [-a] [-o OUTPUT] [INPUT] + rage[EXE] --decrypt [-i IDENTITY] [-o OUTPUT] [INPUT] -Positional arguments: - input Path to a file to read from. +Arguments: + [INPUT] Path to a file to read from. -Optional arguments: - -h, --help Print this help message and exit. - -V, --version Print version info and exit. - -e, --encrypt Encrypt the input (the default). - -d, --decrypt Decrypt the input. - -p, --passphrase Encrypt with a passphrase instead of recipients. - --max-work-factor WF Maximum work factor to allow for passphrase decryption. - -a, --armor Encrypt to a PEM encoded format. - -r, --recipient RECIPIENT Encrypt to the specified RECIPIENT. May be repeated. - -R, --recipients-file PATH Encrypt to the recipients listed at PATH. May be repeated. - -i, --identity IDENTITY Use the identity file at IDENTITY. May be repeated. - -j PLUGIN-NAME Use age-plugin-PLUGIN-NAME in its default mode as an identity. - -o, --output OUTPUT Write the result to the file at path OUTPUT. +Options: + -h, --help Print this help message and exit. + -V, --version Print version info and exit. + -e, --encrypt Encrypt the input (the default). + -d, --decrypt Decrypt the input. + -p, --passphrase Encrypt with a passphrase instead of recipients. + --max-work-factor Maximum work factor to allow for passphrase decryption. + -a, --armor Encrypt to a PEM encoded format. + -r, --recipient Encrypt to the specified RECIPIENT. May be repeated. + -R, --recipients-file Encrypt to the recipients listed at PATH. May be repeated. + -i, --identity Use the identity file at IDENTITY. May be repeated. + -j Use age-plugin-PLUGIN-NAME in its default mode as an identity. + -o, --output Write the result to the file at path OUTPUT. INPUT defaults to standard input, and OUTPUT defaults to standard output. RECIPIENT can be: -- An age public key, as generated by [..]rage-keygen ("age1..."). +- An age public key, as generated by rage-keygen ("age1..."). - An SSH public key ("ssh-ed25519 AAAA...", "ssh-rsa AAAA..."). PATH is a path to a file containing age recipients, one per line @@ -37,9 +36,9 @@ Passphrase-encrypted age identity files can be used as identity files. Multiple identities may be provided, and any unused ones will be ignored. Example: - $ [..]rage-keygen -o key.txt + $ rage-keygen -o key.txt Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p - $ tar cvz ~/data | [..]rage -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p > data.tar.gz.age - $ [..]rage -d -i key.txt -o data.tar.gz data.tar.gz.age + $ tar cvz ~/data | rage -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p > data.tar.gz.age + $ rage -d -i key.txt -o data.tar.gz data.tar.gz.age """ stderr = "" diff --git a/rage/tests/cmd/rage/help_it.toml b/rage/tests/cmd/rage/help_it.toml index 8d742bcc..6d4f81d0 100644 --- a/rage/tests/cmd/rage/help_it.toml +++ b/rage/tests/cmd/rage/help_it.toml @@ -2,32 +2,31 @@ bin.name = "rage" args = "--help" env.add.LANG = "it" stdout = """ -Usage: - [..]rage[EXE] [--encrypt] -r RECIPIENT [-i IDENTITY] [-a] [-o OUTPUT] [INPUT] - [..]rage[EXE] --decrypt [-i IDENTITY] [-o OUTPUT] [INPUT] +Usage: rage[EXE] [--encrypt] -r RECIPIENT [-i IDENTITY] [-a] [-o OUTPUT] [INPUT] + rage[EXE] --decrypt [-i IDENTITY] [-o OUTPUT] [INPUT] -Positional arguments: - input Path to a file to read from. +Arguments: + [INPUT] Path to a file to read from. -Optional arguments: - -h, --help Print this help message and exit. - -V, --version Print version info and exit. - -e, --encrypt Encrypt the input (the default). - -d, --decrypt Decrypt the input. - -p, --passphrase Encrypt with a passphrase instead of recipients. - --max-work-factor WF Maximum work factor to allow for passphrase decryption. - -a, --armor Encrypt to a PEM encoded format. - -r, --recipient RECIPIENT Encrypt to the specified RECIPIENT. May be repeated. - -R, --recipients-file PATH Encrypt to the recipients listed at PATH. May be repeated. - -i, --identity IDENTITY Use the identity file at IDENTITY. May be repeated. - -j PLUGIN-NAME Use age-plugin-PLUGIN-NAME in its default mode as an identity. - -o, --output OUTPUT Write the result to the file at path OUTPUT. +Options: + -h, --help Print this help message and exit. + -V, --version Print version info and exit. + -e, --encrypt Encrypt the input (the default). + -d, --decrypt Decrypt the input. + -p, --passphrase Encrypt with a passphrase instead of recipients. + --max-work-factor Maximum work factor to allow for passphrase decryption. + -a, --armor Encrypt to a PEM encoded format. + -r, --recipient Encrypt to the specified RECIPIENT. May be repeated. + -R, --recipients-file Encrypt to the recipients listed at PATH. May be repeated. + -i, --identity Use the identity file at IDENTITY. May be repeated. + -j Use age-plugin-PLUGIN-NAME in its default mode as an identity. + -o, --output Write the result to the file at path OUTPUT. INPUT ha come valore predefinito lo standard input, e OUTPUT ha come valore predefinito lo standard output. RECIPIENT può essere: -- Una chiave pubblica age, come generata da [..]rage-keygen ("age1..."). +- Una chiave pubblica age, come generata da rage-keygen ("age1..."). - Una chiave pubblica SSH ("ssh-ed25519 AAAA...", "ssh-rsa AAAA..."). PATH è il percorso ad un file contenente dei destinatari age, @@ -40,9 +39,9 @@ I file di identità possono essere cifrati con age e una passphrase. Possono essere fornite più identità, quelle inutilizzate verranno ignorate. Esempio: - $ [..]rage-keygen -o key.txt + $ rage-keygen -o key.txt Chiave pubblica: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p - $ tar cvz ~/data | [..]rage -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p > data.tar.gz.age - $ [..]rage -d -i key.txt -o data.tar.gz data.tar.gz.age + $ tar cvz ~/data | rage -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p > data.tar.gz.age + $ rage -d -i key.txt -o data.tar.gz data.tar.gz.age """ stderr = "" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index ce21716c..dc0d8d45 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -67,23 +67,23 @@ criteria = "safe-to-deploy" [[exemptions.anstream]] version = "0.3.2" -criteria = "safe-to-run" +criteria = "safe-to-deploy" [[exemptions.anstyle]] version = "1.0.2" -criteria = "safe-to-run" +criteria = "safe-to-deploy" [[exemptions.anstyle-parse]] version = "0.2.1" -criteria = "safe-to-run" +criteria = "safe-to-deploy" [[exemptions.anstyle-query]] version = "1.0.0" -criteria = "safe-to-run" +criteria = "safe-to-deploy" [[exemptions.anstyle-wincon]] version = "1.0.2" -criteria = "safe-to-run" +criteria = "safe-to-deploy" [[exemptions.arc-swap]] version = "1.6.0" @@ -171,16 +171,24 @@ criteria = "safe-to-deploy" [[exemptions.clap]] version = "4.3.24" -criteria = "safe-to-run" +criteria = "safe-to-deploy" [[exemptions.clap_builder]] version = "4.3.24" -criteria = "safe-to-run" +criteria = "safe-to-deploy" [[exemptions.clap_complete]] version = "4.3.2" criteria = "safe-to-run" +[[exemptions.clap_derive]] +version = "4.3.12" +criteria = "safe-to-deploy" + +[[exemptions.clap_lex]] +version = "0.5.0" +criteria = "safe-to-deploy" + [[exemptions.console]] version = "0.15.7" criteria = "safe-to-deploy" @@ -341,14 +349,6 @@ criteria = "safe-to-deploy" version = "0.28.1" criteria = "safe-to-run" -[[exemptions.gumdrop]] -version = "0.8.1" -criteria = "safe-to-deploy" - -[[exemptions.gumdrop_derive]] -version = "0.8.1" -criteria = "safe-to-deploy" - [[exemptions.hashbrown]] version = "0.14.3" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 213451db..acd33316 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -344,6 +344,12 @@ criteria = "safe-to-deploy" version = "0.3.27" notes = "Unsafe used to implement a concurrency primitive AtomicWaker. Well-commented and not obviously incorrect. Like my other audits of these concurrency primitives inside the futures family, I couldn't certify that it is correct without formal methods, but that is out of scope for this vetting." +[[audits.bytecode-alliance.audits.heck]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "0.4.0" +notes = "Contains `forbid_unsafe` and only uses `std::fmt` from the standard library. Otherwise only contains string manipulation." + [[audits.bytecode-alliance.audits.iana-time-zone]] who = "Dan Gohman " criteria = "safe-to-deploy" @@ -500,12 +506,6 @@ criteria = "safe-to-run" version = "0.3.67" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" -[[audits.google.audits.clap_lex]] -who = "George Burgess IV " -criteria = "safe-to-run" -version = "0.4.1" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" - [[audits.google.audits.crossbeam-deque]] who = "George Burgess IV " criteria = "safe-to-run" @@ -682,11 +682,6 @@ who = "David Cook " criteria = "safe-to-deploy" version = "0.9.0" -[[audits.isrg.audits.clap_lex]] -who = "Brandon Pitman " -criteria = "safe-to-run" -delta = "0.4.1 -> 0.5.0" - [[audits.isrg.audits.criterion]] who = "Brandon Pitman " criteria = "safe-to-run" @@ -1198,6 +1193,12 @@ capabilities. """ aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.heck]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "0.4.0 -> 0.4.1" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.hex]] who = "Simon Friedberger " criteria = "safe-to-deploy"