diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a9a74d0..bb841285 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -190,12 +190,6 @@ jobs: run: cargo build --release --locked --target ${{ matrix.target }} ${{ matrix.build_flags }} working-directory: ./rage - - name: Generate completions - run: cargo run --example generate-completions - - - name: Generate manpages - run: cargo run --example generate-docs - - name: Update Debian package config for cross-compile run: sed -i '/\/rage-mount/d' rage/Cargo.toml if: matrix.name != 'linux' diff --git a/Cargo.lock b/Cargo.lock index 5721935d..d4182fd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -540,6 +540,16 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +[[package]] +name = "clap_mangen" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2e32b579dae093c2424a8b7e2bea09c89da01e1ce5065eb2f0a6f1cc15cc1f" +dependencies = [ + "clap", + "roff", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -1472,15 +1482,6 @@ dependencies = [ "libc", ] -[[package]] -name = "man" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf5fa795187a80147b1ac10aaedcf5ffd3bbeb1838bda61801a1c9ad700a1c9" -dependencies = [ - "roff", -] - [[package]] name = "memchr" version = "2.7.1" @@ -1998,6 +1999,7 @@ dependencies = [ "chrono", "clap", "clap_complete", + "clap_mangen", "console", "ctrlc", "env_logger", @@ -2009,7 +2011,6 @@ dependencies = [ "lazy_static", "libc", "log", - "man", "pinentry", "rust-embed", "tar", @@ -2126,9 +2127,9 @@ dependencies = [ [[package]] name = "roff" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33e4fb37ba46888052c763e4ec2acfedd8f00f62897b630cadb6298b833675e" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rpassword" diff --git a/rage/Cargo.toml b/rage/Cargo.toml index 76389d5e..f3c1ece3 100644 --- a/rage/Cargo.toml +++ b/rage/Cargo.toml @@ -24,18 +24,18 @@ assets = [ ["target/release/rage", "usr/bin/", "755"], ["target/release/rage-keygen", "usr/bin/", "755"], ["target/release/rage-mount", "usr/bin/", "755"], - ["../target/completions/rage.bash", "usr/share/bash-completion/completions/rage", "644"], - ["../target/completions/rage-keygen.bash", "usr/share/bash-completion/completions/rage-keygen", "644"], - ["../target/completions/rage-mount.bash", "usr/share/bash-completion/completions/rage-mount", "644"], - ["../target/completions/rage.fish", "usr/share/fish/completions/", "644"], - ["../target/completions/rage-keygen.fish", "usr/share/fish/completions/", "644"], - ["../target/completions/rage-mount.fish", "usr/share/fish/completions/", "644"], - ["../target/completions/rage.zsh", "usr/share/zsh/functions/Completion/Debian/", "644"], - ["../target/completions/rage-keygen.zsh", "usr/share/zsh/functions/Completion/Debian/", "644"], - ["../target/completions/rage-mount.zsh", "usr/share/zsh/functions/Completion/Debian/", "644"], - ["../target/manpages/rage.1.gz", "usr/share/man/man1/", "644"], - ["../target/manpages/rage-keygen.1.gz", "usr/share/man/man1/", "644"], - ["../target/manpages/rage-mount.1.gz", "usr/share/man/man1/", "644"], + ["target/release/completions/rage.bash", "usr/share/bash-completion/completions/rage", "644"], + ["target/release/completions/rage-keygen.bash", "usr/share/bash-completion/completions/rage-keygen", "644"], + ["target/release/completions/rage-mount.bash", "usr/share/bash-completion/completions/rage-mount", "644"], + ["target/release/completions/rage.fish", "usr/share/fish/completions/", "644"], + ["target/release/completions/rage-keygen.fish", "usr/share/fish/completions/", "644"], + ["target/release/completions/rage-mount.fish", "usr/share/fish/completions/", "644"], + ["target/release/completions/_rage", "usr/share/zsh/functions/Completion/Debian/", "644"], + ["target/release/completions/_rage-keygen", "usr/share/zsh/functions/Completion/Debian/", "644"], + ["target/release/completions/_rage-mount", "usr/share/zsh/functions/Completion/Debian/", "644"], + ["target/release/manpages/rage.1.gz", "usr/share/man/man1/", "644"], + ["target/release/manpages/rage-keygen.1.gz", "usr/share/man/man1/", "644"], + ["target/release/manpages/rage-mount.1.gz", "usr/share/man/man1/", "644"], ["../README.md", "usr/share/doc/rage/README.md", "644"], ] features = ["mount"] @@ -75,10 +75,17 @@ tar = { version = "0.4", optional = true } time = { version = ">=0.3.7, <0.3.24", optional = true } # time 0.3.24 has MSRV 1.67 zip = { version = "0.6.2", optional = true } -[dev-dependencies] +[build-dependencies] +clap = { workspace = true, features = ["string", "unstable-styles"] } clap_complete = "4" +clap_mangen = "0.2" flate2 = "1" -man = "0.3" +i18n-embed = { workspace = true, features = ["desktop-requester"] } +i18n-embed-fl.workspace = true +lazy_static.workspace = true +rust-embed.workspace = true + +[dev-dependencies] trycmd = "0.14" [features] diff --git a/rage/build.rs b/rage/build.rs new file mode 100644 index 00000000..53ebe178 --- /dev/null +++ b/rage/build.rs @@ -0,0 +1,243 @@ +use std::env; +use std::fs; +use std::io; +use std::path::Path; +use std::path::PathBuf; + +use clap::{Command, CommandFactory, ValueEnum}; +use clap_complete::{generate_to, Shell}; +use clap_mangen::{ + roff::{Inline, Roff}, + Man, +}; +use flate2::{write::GzEncoder, Compression}; + +mod i18n { + include!("src/bin/rage/i18n.rs"); +} +mod rage { + include!("src/bin/rage/cli.rs"); +} +mod rage_keygen { + include!("src/bin/rage-keygen/cli.rs"); +} +mod rage_mount { + include!("src/bin/rage-mount/cli.rs"); +} + +#[macro_export] +macro_rules! fl { + ($message_id:literal) => {{ + i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id) + }}; + + ($message_id:literal, $($args:expr),* $(,)?) => {{ + i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id, $($args), *) + }}; +} + +struct Example { + text: String, + cmd: &'static str, + output: Option, +} + +impl Example { + const fn new(text: String, cmd: &'static str, output: Option) -> Self { + Self { text, cmd, output } + } +} + +struct Examples([Example; N]); + +impl Examples { + fn render(self, w: &mut impl io::Write) -> io::Result<()> { + let mut roff = Roff::default(); + roff.control("SH", ["EXAMPLES"]); + for example in self.0 { + roff.control("TP", []); + roff.text( + [ + Inline::Roman(format!("{}:", example.text)), + Inline::LineBreak, + Inline::Bold(format!("$ {}", example.cmd)), + Inline::LineBreak, + ] + .into_iter() + .chain( + example + .output + .into_iter() + .flat_map(|output| [Inline::Roman(output), Inline::LineBreak]), + ) + .collect::>(), + ); + } + roff.to_writer(w) + } +} + +#[derive(Clone)] +struct Cli { + rage: Command, + rage_keygen: Command, + rage_mount: Command, +} + +impl Cli { + fn build() -> Self { + Self { + rage: rage::AgeOptions::command(), + rage_keygen: rage_keygen::AgeOptions::command(), + rage_mount: rage_mount::AgeMountOptions::command(), + } + } + + fn generate_completions(&mut self, out_dir: &Path) -> io::Result<()> { + fs::create_dir_all(out_dir)?; + + for &shell in Shell::value_variants() { + generate_to(shell, &mut self.rage, "rage", out_dir)?; + generate_to(shell, &mut self.rage_keygen, "rage-keygen", out_dir)?; + generate_to(shell, &mut self.rage_mount, "rage-mount", out_dir)?; + } + + Ok(()) + } + + fn generate_manpages(self, out_dir: &Path) -> io::Result<()> { + fs::create_dir_all(out_dir)?; + + fn generate_manpage( + out_dir: &Path, + name: &str, + cmd: Command, + custom: impl FnOnce(&Man, &mut GzEncoder) -> io::Result<()>, + ) -> io::Result<()> { + let file = fs::File::create(out_dir.join(format!("{}.1.gz", name)))?; + let mut w = GzEncoder::new(file, Compression::best()); + + let man = Man::new(cmd); + man.render_title(&mut w)?; + man.render_name_section(&mut w)?; + man.render_synopsis_section(&mut w)?; + man.render_options_section(&mut w)?; + custom(&man, &mut w)?; + man.render_version_section(&mut w)?; + man.render_authors_section(&mut w) + } + + generate_manpage( + out_dir, + "rage", + self.rage + .about(fl!("man-rage-about")) + .after_help(rage::after_help_content("rage-keygen")), + |man, w| { + man.render_extra_section(w)?; + Examples([ + Example::new( + fl!("man-rage-example-enc-single"), + "echo \"_o/\" | rage -o hello.age -r age1uvscypafkkxt6u2gkguxet62cenfmnpc0smzzlyun0lzszfatawq4kvf2u", + None, + ), + Example::new( + fl!("man-rage-example-enc-multiple"), + "echo \"_o/\" | rage -r age1uvscypafkkxt6u2gkguxet62cenfmnpc0smzzlyun0lzszfatawq4kvf2u \ + -r age1ex4ty8ppg02555at009uwu5vlk5686k3f23e7mac9z093uvzfp8sxr5jum > hello.age", + None, + ), + Example::new( + fl!("man-rage-example-enc-password"), + "rage -p -o hello.txt.age hello.txt", + Some(format!("{}:", fl!("type-passphrase"))), + ), + Example::new( + fl!("man-rage-example-enc-list"), + "tar cv ~/xxx | rage -R recipients.txt > xxx.tar.age", + None, + ), + Example::new( + fl!("man-rage-example-enc-identities"), + "tar cv ~/xxx | rage -e -i keyA.txt -i keyB.txt > xxx.tar.age", + None, + ), + Example::new( + fl!("man-rage-example-enc-url"), + "echo \"_o/\" | rage -o hello.age -R <(curl https://github.com/str4d.keys)", + None, + ), + Example::new( + fl!("man-rage-example-dec-identities"), + "rage -d -o hello -i keyA.txt -i keyB.txt hello.age", + None, + ), + ]) + .render(w) + }, + )?; + generate_manpage( + out_dir, + "rage-keygen", + self.rage_keygen.about(fl!("man-keygen-about")), + |_, w| { + Examples([ + Example::new(fl!("man-keygen-example-stdout"), "rage-keygen", None), + Example::new( + fl!("man-keygen-example-file"), + "rage-keygen -o key.txt", + Some(format!( + "{}: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p", + fl!("tty-pubkey") + )), + ), + ]) + .render(w) + }, + )?; + generate_manpage( + out_dir, + "rage-mount", + self.rage_mount.about(fl!("man-mount-about")), + |_, w| { + Examples([ + Example::new( + fl!("man-mount-example-identity"), + "rage-mount -t tar -i key.txt encrypted.tar.age ./tmp", + None, + ), + Example::new( + fl!("man-mount-example-passphrase"), + "rage-mount -t zip encrypted.zip.age ./tmp", + Some(format!("{}:", fl!("type-passphrase"))), + ), + ]) + .render(w) + }, + )?; + + Ok(()) + } +} + +fn main() -> io::Result<()> { + i18n::load_languages(); + + // `OUT_DIR` is "intentionally opaque as it is only intended for `rustc` interaction" + // (https://github.com/rust-lang/cargo/issues/9858). Peek into the black box and use + // it to figure out where the target directory is. + let out_dir = match env::var_os("OUT_DIR") { + None => return Ok(()), + Some(out_dir) => PathBuf::from(out_dir) + .ancestors() + .nth(3) + .expect("should be absolute path") + .to_path_buf(), + }; + + let mut cli = Cli::build(); + cli.generate_completions(&out_dir.join("completions"))?; + cli.generate_manpages(&out_dir.join("manpages"))?; + + Ok(()) +} diff --git a/rage/examples/generate-completions.rs b/rage/examples/generate-completions.rs deleted file mode 100644 index 527eb4a9..00000000 --- a/rage/examples/generate-completions.rs +++ /dev/null @@ -1,127 +0,0 @@ -use clap::{Arg, ArgAction, Command}; -use clap_complete::{generate, shells, Generator}; -use std::fs::{create_dir_all, File}; - -const COMPLETIONS_DIR: &str = "./target/completions"; - -fn generate_completion>( - gen: G, - app: &mut Command, - bin_name: S, - file_name: String, -) { - let mut file = File::create(format!("{}/{}", COMPLETIONS_DIR, file_name)) - .expect("Should be able to open file in target directory"); - generate::(gen, app, bin_name, &mut file); -} - -fn generate_completions(mut app: Command, bin_name: &str) { - generate_completion( - shells::Bash, - &mut app, - bin_name, - format!("{}.bash", bin_name), - ); - generate_completion( - shells::Elvish, - &mut app, - bin_name, - format!("{}.elv", bin_name), - ); - generate_completion( - shells::Fish, - &mut app, - bin_name, - format!("{}.fish", bin_name), - ); - generate_completion( - shells::PowerShell, - &mut app, - format!("{}.exe", bin_name), - format!("{}.ps1", bin_name), - ); - generate_completion(shells::Zsh, &mut app, bin_name, format!("{}.zsh", bin_name)); -} - -fn rage_completions() { - let app = Command::new("rage") - .arg(Arg::new("input")) - .arg( - Arg::new("encrypt") - .short('e') - .long("encrypt") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("decrypt") - .short('d') - .long("decrypt") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("passphrase") - .short('p') - .long("passphrase") - .action(ArgAction::SetTrue), - ) - .arg(Arg::new("max-work-factor").long("max-work-factor")) - .arg( - Arg::new("armor") - .short('a') - .long("armor") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("recipient") - .short('r') - .long("recipient") - .action(ArgAction::Append), - ) - .arg( - Arg::new("recipients-file") - .short('R') - .long("recipients-file") - .action(ArgAction::Append), - ) - .arg( - Arg::new("identity") - .short('i') - .long("identity") - .action(ArgAction::Append), - ) - .arg(Arg::new("plugin-name").short('j')) - .arg(Arg::new("output").short('o').long("output")); - - generate_completions(app, "rage"); -} - -fn rage_keygen_completions() { - let app = Command::new("rage-keygen").arg(Arg::new("output").short('o').long("output")); - - generate_completions(app, "rage-keygen"); -} - -fn rage_mount_completions() { - let app = Command::new("rage-mount") - .arg(Arg::new("filename")) - .arg(Arg::new("mountpoint")) - .arg(Arg::new("types").short('t').long("types")) - .arg(Arg::new("max-work-factor").long("max-work-factor")) - .arg( - Arg::new("identity") - .short('i') - .long("identity") - .action(ArgAction::Append), - ); - - generate_completions(app, "rage-mount"); -} - -fn main() { - // Create the target directory if it does not exist. - let _ = create_dir_all(COMPLETIONS_DIR); - - rage_completions(); - rage_keygen_completions(); - rage_mount_completions(); -} diff --git a/rage/examples/generate-docs.rs b/rage/examples/generate-docs.rs deleted file mode 100644 index d29ab3f6..00000000 --- a/rage/examples/generate-docs.rs +++ /dev/null @@ -1,223 +0,0 @@ -use flate2::{write::GzEncoder, Compression}; -use man::prelude::*; -use std::fs::{create_dir_all, File}; -use std::io::prelude::*; - -const MANPAGES_DIR: &str = "./target/manpages"; - -fn generate_manpage(page: String, name: &str) { - let file = File::create(format!("{}/{}.1.gz", MANPAGES_DIR, name)) - .expect("Should be able to open file in target directory"); - let mut encoder = GzEncoder::new(file, Compression::best()); - encoder - .write_all(page.as_bytes()) - .expect("Should be able to write to file in target directory"); -} - -fn rage_page() { - let builder = Manual::new("rage") - .about("A simple, secure, and modern encryption tool") - .author(Author::new("Jack Grigg").email("thestr4d@gmail.com")) - .flag( - Flag::new() - .short("-h") - .long("--help") - .help("Display help text and exit."), - ) - .flag( - Flag::new() - .short("-V") - .long("--version") - .help("Display version info and exit."), - ) - .flag( - Flag::new() - .short("-e") - .long("--encrypt") - .help("Encrypt the input. By default, the input is encrypted."), - ) - .flag( - Flag::new() - .short("-d") - .long("--decrypt") - .help("Decrypt the input. By default, the input is encrypted."), - ) - .flag( - Flag::new() - .short("-p") - .long("--passphrase") - .help("Encrypt with a passphrase instead of recipients."), - ) - .flag( - Flag::new() - .short("-a") - .long("--armor") - .help("Encrypt to a PEM encoded format."), - ) - .option( - Opt::new("RECIPIENT") - .short("-r") - .long("--recipient") - .help("Encrypt to the specified RECIPIENT. May be repeated."), - ) - .option( - Opt::new("PATH") - .short("-R") - .long("--recipients-file") - .help("Encrypt to the recipients listed at PATH. May be repeated."), - ) - .option( - Opt::new("IDENTITY") - .short("-i") - .long("--identity") - .help("Use the identity file at IDENTITY. May be repeated."), - ) - .option( - Opt::new("OUTPUT") - .short("-o") - .long("--output") - .help("Write the result to the file at path OUTPUT. Defaults to standard output."), - ) - .option( - Opt::new("WF") - .long("--max-work-factor") - .help("The maximum work factor to allow for passphrase decryption."), - ) - .arg(Arg::new("[INPUT_FILE (defaults to stdin)]")) - .example(Example::new().text("Encryption to a recipient").command( - "echo \"_o/\" | rage -o hello.age -r age1uvscypafkkxt6u2gkguxet62cenfmnpc0smzzlyun0lzszfatawq4kvf2u", - )) - .example( - Example::new() - .text("Encryption to multiple recipients (with default output to stdout)") - .command( - "echo \"_o/\" | rage -r age1uvscypafkkxt6u2gkguxet62cenfmnpc0smzzlyun0lzszfatawq4kvf2u \ - -r age1ex4ty8ppg02555at009uwu5vlk5686k3f23e7mac9z093uvzfp8sxr5jum > hello.age", - ), - ) - .example( - Example::new() - .text("Encryption with a password (interactive only, use recipients for batch!)") - .command("rage -p -o hello.txt.age hello.txt") - .output("Type passphrase:"), - ) - .example( - Example::new() - .text("Encryption to a list of recipients in a file") - .command("tar cv ~/xxx | rage -R recipients.txt > xxx.tar.age"), - ) - .example( - Example::new() - .text("Encryption to several identities") - .command("tar cv ~/xxx | rage -e -i keyA.txt -i keyB.txt > xxx.tar.age"), - ) - .example( - Example::new() - .text("Encryption to a list of recipients at an HTTPS URL") - .command( - "echo \"_o/\" | rage -o hello.age -R <(curl https://github.com/str4d.keys)", - ), - ) - .example( - Example::new() - .text("Decryption with identities") - .command("rage -d -o hello -i keyA.txt -i keyB.txt hello.age"), - ); - let page = builder.render(); - - generate_manpage(page, "rage"); -} - -fn rage_keygen_page() { - let page = Manual::new("rage-keygen") - .about("Generate age-compatible encryption key pairs") - .author(Author::new("Jack Grigg").email("thestr4d@gmail.com")) - .flag( - Flag::new() - .short("-h") - .long("--help") - .help("Display help text and exit."), - ) - .flag( - Flag::new() - .short("-V") - .long("--version") - .help("Display version info and exit."), - ) - .option( - Opt::new("OUTPUT").short("-o").long("--output").help( - "Write the key pair to the file at path OUTPUT. Defaults to standard output.", - ), - ) - .example( - Example::new() - .text("Generate a new key pair") - .command("rage-keygen"), - ) - .example( - Example::new() - .text("Generate a new key pair and save it to a file") - .command("rage-keygen -o key.txt") - .output( - "Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p", - ), - ) - .render(); - - generate_manpage(page, "rage-keygen"); -} - -fn rage_mount_page() { - let page = Manual::new("rage-mount") - .about("Mount an age-encrypted filesystem") - .author(Author::new("Jack Grigg").email("thestr4d@gmail.com")) - .flag( - Flag::new() - .short("-h") - .long("--help") - .help("Display help text and exit."), - ) - .flag( - Flag::new() - .short("-V") - .long("--version") - .help("Display version info and exit."), - ) - .flag( - Flag::new() - .short("-t") - .long("--types") - .help("The type of the filesystem (one of \"tar\", \"zip\")."), - ) - .option( - Opt::new("IDENTITY") - .short("-i") - .long("--identity") - .help("Use the private key file at IDENTITY. May be repeated."), - ) - .arg(Arg::new("filename")) - .arg(Arg::new("mountpoint")) - .example( - Example::new() - .text("Mounting an archive encrypted to a recipient") - .command("rage-mount -t tar -i key.txt encrypted.tar.age ./tmp"), - ) - .example( - Example::new() - .text("Mounting an archive encrypted with a passphrase") - .command("rage-mount -t zip encrypted.zip.age ./tmp") - .output("Type passphrase:"), - ) - .render(); - - generate_manpage(page, "rage-mount"); -} - -fn main() { - // Create the target directory if it does not exist. - let _ = create_dir_all(MANPAGES_DIR); - - rage_page(); - rage_keygen_page(); - rage_mount_page(); -} diff --git a/rage/i18n/en-US/rage.ftl b/rage/i18n/en-US/rage.ftl index 04952016..4070790b 100644 --- a/rage/i18n/en-US/rage.ftl +++ b/rage/i18n/en-US/rage.ftl @@ -56,12 +56,12 @@ help-flag-identity = Use the identity file at {identity}. May be repeated. help-flag-plugin-name = Use {-age-plugin-}{plugin-name} in its default mode as an identity. help-flag-output = Write the result to the file at path {output}. -rage-after-help = +rage-after-help-content = {input} defaults to standard input, and {output} defaults to standard output. {recipient} can be: - - An {-age} public key, as generated by {$keygen_name} ("age1..."). - - An SSH public key ("ssh-ed25519 AAAA...", "ssh-rsa AAAA..."). + - An {-age} public key, as generated by {$keygen_name} ({$example_age_pubkey}). + - An SSH public key ({$example_ssh_pubkey}). {recipients-file} is a path to a file containing {-age} recipients, one per line (ignoring "#" prefixed comments and empty lines). @@ -71,6 +71,7 @@ rage-after-help = Passphrase-encrypted {-age} identity files can be used as identity files. Multiple identities may be provided, and any unused ones will be ignored. +rage-after-help-example = Example: {" "}{$example_a} {" "}{tty-pubkey}: {$example_a_output} @@ -197,3 +198,25 @@ err-mnt-unknown-type = Unknown filesystem type "{$fs_type}" ## Unstable features test-unstable = To test this, build {-rage} with {-flag-unstable}. + +## Manpages + +man-rage-about = A simple, secure, and modern encryption tool + +man-rage-example-enc-single = Encryption to a recipient +man-rage-example-enc-multiple = Encryption to multiple recipients (with default output to stdout) +man-rage-example-enc-password = Encryption with a password (interactive only, use recipients for batch!) +man-rage-example-enc-list = Encryption to a list of recipients in a file +man-rage-example-enc-identities = Encryption to several identities +man-rage-example-enc-url = Encryption to a list of recipients at an HTTPS URL +man-rage-example-dec-identities = Decryption with identities + +man-keygen-about = Generate age-compatible encryption key pairs + +man-keygen-example-stdout = Generate a new key pair +man-keygen-example-file = Generate a new key pair and save it to a file + +man-mount-about = Mount an age-encrypted filesystem + +man-mount-example-identity = Mounting an archive encrypted to a recipient +man-mount-example-passphrase = Mounting an archive encrypted with a passphrase diff --git a/rage/i18n/es-AR/rage.ftl b/rage/i18n/es-AR/rage.ftl index fec5a5df..24c6b399 100644 --- a/rage/i18n/es-AR/rage.ftl +++ b/rage/i18n/es-AR/rage.ftl @@ -37,12 +37,12 @@ plugin-name = PLUGIN-NAME input = INPUT output = OUTPUT -rage-after-help = +rage-after-help-content = {input} por defecto a standard input, y {output} por defecto standard output. {recipient} puede ser: - - Una clave pública {-age}, como es generada por {$keygen_name} ("age1..."). - - Una clave pública SSH ("ssh-ed25519 AAAA...", "ssh-rsa AAAA..."). + - Una clave pública {-age}, como es generada por {$keygen_name} ({$example_age_pubkey}). + - Una clave pública SSH ({$example_ssh_pubkey}). {recipients-file} es una ruta a un archivo que contenga un destinatario {-age} por línea (ignorando comentarios con el prefijo "#" y líneas vacías). @@ -54,6 +54,7 @@ rage-after-help = Pueden proveerse múltiples idendidades, cualquiera que no sea utilizada será ignorada. +rage-after-help-example = Ejemplo: {" "}{$example_a} {" "}{tty-pubkey}: {$example_a_output} diff --git a/rage/i18n/it/rage.ftl b/rage/i18n/it/rage.ftl index da7305ce..e2bfce1d 100644 --- a/rage/i18n/it/rage.ftl +++ b/rage/i18n/it/rage.ftl @@ -37,13 +37,13 @@ plugin-name = PLUGIN-NAME input = INPUT output = OUTPUT -rage-after-help = +rage-after-help-content = {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 {$keygen_name} ("age1..."). - - Una chiave pubblica SSH ("ssh-ed25519 AAAA...", "ssh-rsa AAAA..."). + - Una chiave pubblica {-age}, come generata da {$keygen_name} ({$example_age_pubkey}). + - Una chiave pubblica SSH ({$example_ssh_pubkey}). {recipients-file} è il percorso ad un file contenente dei destinatari {-age}, uno per riga (ignorando i commenti che iniziano con "#" e le righe vuote). @@ -54,6 +54,7 @@ rage-after-help = I file di identità possono essere cifrati con {-age} e una passphrase. Possono essere fornite più identità, quelle inutilizzate verranno ignorate. +rage-after-help-example = Esempio: {" "}{$example_a} {" "}{tty-pubkey}: {$example_a_output} diff --git a/rage/i18n/zh-CN/rage.ftl b/rage/i18n/zh-CN/rage.ftl index 558b4c23..a62562ee 100644 --- a/rage/i18n/zh-CN/rage.ftl +++ b/rage/i18n/zh-CN/rage.ftl @@ -37,12 +37,12 @@ plugin-name = PLUGIN-NAME input = INPUT output = OUTPUT -rage-after-help = +rage-after-help-content = {input} 默认为标准输入 (stdin), 而 {output} 默认为标准输出 (stdout) 。 {recipient} 可为: - - 一把以 {$keygen_name} 生成的 {-age} 公钥 ("age1...")。 - - 一把 SSH 公钥 ("ssh-ed25519 AAAA...", "ssh-rsa AAAA...")。 + - 一把以 {$keygen_name} 生成的 {-age} 公钥 ({$example_age_pubkey})。 + - 一把 SSH 公钥 ({$example_ssh_pubkey})。 {recipients-file} 是一个文件路径。该文件应含有 {-age} 接收方, 每行一个 (前缀为 "#" 的注释以及空行将被忽略)。 @@ -52,6 +52,7 @@ rage-after-help = Passphrase-encrypted {-age} identity files can be used as identity files. 您可提供多份身份, 未使用的身份将被忽略。 +rage-after-help-example = Example: {" "}{$example_a} {" "}{tty-pubkey}: {$example_a_output} diff --git a/rage/i18n/zh-TW/rage.ftl b/rage/i18n/zh-TW/rage.ftl index 72206a6e..1ac2fc90 100644 --- a/rage/i18n/zh-TW/rage.ftl +++ b/rage/i18n/zh-TW/rage.ftl @@ -37,12 +37,12 @@ plugin-name = PLUGIN-NAME input = INPUT output = OUTPUT -rage-after-help = +rage-after-help-content = {input} 默認為標準輸入 (stdin), 而 {output} 默認為標準輸出 (stdout) 。 {recipient} 可為: - - 一把以 {$keygen_name} 生成的 {-age} 公鑰 ("age1...")。 - - 一把 SSH 公鑰 ("ssh-ed25519 AAAA...", "ssh-rsa AAAA...")。 + - 一把以 {$keygen_name} 生成的 {-age} 公鑰 ({$example_age_pubkey})。 + - 一把 SSH 公鑰 ({$example_ssh_pubkey})。 {recipients-file} 是一個文件路徑。該文件應含有 {-age} 接收方, 每行一個 (前綴為 "#" 的注釋以及空行將被忽略)。 @@ -52,6 +52,7 @@ rage-after-help = Passphrase-encrypted {-age} identity files can be used as identity files. 您可提供多份身份, 未使用的身份將被忽略。 +rage-after-help-example = Example: {" "}{$example_a} {" "}{tty-pubkey}: {$example_a_output} diff --git a/rage/src/bin/rage-keygen/cli.rs b/rage/src/bin/rage-keygen/cli.rs new file mode 100644 index 00000000..aa6c5aa7 --- /dev/null +++ b/rage/src/bin/rage-keygen/cli.rs @@ -0,0 +1,34 @@ +use clap::{builder::Styles, ArgAction, Parser}; + +use crate::fl; + +#[derive(Debug, Parser)] +#[command(display_name = "rage-keygen")] +#[command(name = "rage-keygen")] +#[command(author, version)] +#[command(help_template = format!("\ +{{before-help}}{{about-with-newline}} +{}{}:{} {{usage}} + +{{all-args}}{{after-help}}\ + ", + Styles::default().get_usage().render(), + fl!("usage-header"), + Styles::default().get_usage().render_reset()))] +#[command(next_help_heading = fl!("flags-header"))] +#[command(disable_help_flag(true))] +#[command(disable_version_flag(true))] +pub(crate) struct AgeOptions { + #[arg(action = ArgAction::Help, short, long)] + #[arg(help = fl!("help-flag-help"))] + pub(crate) help: Option, + + #[arg(action = ArgAction::Version, short = 'V', long)] + #[arg(help = fl!("help-flag-version"))] + pub(crate) version: Option, + + #[arg(short, long)] + #[arg(value_name = fl!("output"))] + #[arg(help = fl!("keygen-help-flag-output"))] + pub(crate) output: Option, +} diff --git a/rage/src/bin/rage-keygen/main.rs b/rage/src/bin/rage-keygen/main.rs index 9f7e318a..a5e9cf2a 100644 --- a/rage/src/bin/rage-keygen/main.rs +++ b/rage/src/bin/rage-keygen/main.rs @@ -1,67 +1,28 @@ #![forbid(unsafe_code)] use age::{cli_common::file_io, secrecy::ExposeSecret}; -use clap::{builder::Styles, ArgAction, Parser}; -use i18n_embed::{ - fluent::{fluent_language_loader, FluentLanguageLoader}, - DesktopLanguageRequester, -}; -use lazy_static::lazy_static; -use rust_embed::RustEmbed; +use clap::Parser; + use std::io::Write; +mod cli; mod error; -#[derive(RustEmbed)] -#[folder = "i18n"] -struct Localizations; - -lazy_static! { - static ref LANGUAGE_LOADER: FluentLanguageLoader = fluent_language_loader!(); +mod i18n { + include!("../rage/i18n.rs"); } #[macro_export] macro_rules! fl { ($message_id:literal) => {{ - i18n_embed_fl::fl!($crate::LANGUAGE_LOADER, $message_id) + i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id) }}; ($message_id:literal, $($args:expr),* $(,)?) => {{ - i18n_embed_fl::fl!($crate::LANGUAGE_LOADER, $message_id, $($args), *) + i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id, $($args), *) }}; } -#[derive(Debug, Parser)] -#[command(display_name = "rage-keygen")] -#[command(name = "rage-keygen")] -#[command(version)] -#[command(help_template = format!("\ -{{before-help}}{{about-with-newline}} -{}{}:{} {{usage}} - -{{all-args}}{{after-help}}\ - ", - Styles::default().get_usage().render(), - fl!("usage-header"), - Styles::default().get_usage().render_reset()))] -#[command(next_help_heading = fl!("flags-header"))] -#[command(disable_help_flag(true))] -#[command(disable_version_flag(true))] -struct AgeOptions { - #[arg(action = ArgAction::Help, short, long)] - #[arg(help = fl!("help-flag-help"))] - help: Option, - - #[arg(action = ArgAction::Version, short = 'V', long)] - #[arg(help = fl!("help-flag-version"))] - version: Option, - - #[arg(short, long)] - #[arg(value_name = fl!("output"))] - #[arg(help = fl!("keygen-help-flag-output"))] - output: Option, -} - fn main() -> Result<(), error::Error> { env_logger::builder() .format_timestamp(None) @@ -69,14 +30,10 @@ fn main() -> Result<(), error::Error> { .parse_default_env() .init(); - let requested_languages = DesktopLanguageRequester::requested_languages(); - i18n_embed::select(&*LANGUAGE_LOADER, &Localizations, &requested_languages).unwrap(); + let requested_languages = i18n::load_languages(); age::localizer().select(&requested_languages).unwrap(); - // Unfortunately the common Windows terminals don't support Unicode Directionality - // Isolation Marks, so we disable them for now. - LANGUAGE_LOADER.set_use_isolating(false); - let opts = AgeOptions::parse(); + let opts = cli::AgeOptions::parse(); let mut output = file_io::OutputWriter::new( opts.output, diff --git a/rage/src/bin/rage-mount/cli.rs b/rage/src/bin/rage-mount/cli.rs new file mode 100644 index 00000000..5da4e86d --- /dev/null +++ b/rage/src/bin/rage-mount/cli.rs @@ -0,0 +1,53 @@ +use clap::{builder::Styles, ArgAction, Parser}; + +use crate::fl; + +#[derive(Debug, Parser)] +#[command(display_name = "rage-mount")] +#[command(name = "rage-mount")] +#[command(author, version)] +#[command(help_template = format!("\ +{{before-help}}{{about-with-newline}} +{}{}:{} {{usage}} + +{{all-args}}{{after-help}}\ + ", + Styles::default().get_usage().render(), + fl!("usage-header"), + Styles::default().get_usage().render_reset()))] +#[command(next_help_heading = fl!("flags-header"))] +#[command(disable_help_flag(true))] +#[command(disable_version_flag(true))] +pub(crate) struct AgeMountOptions { + #[arg(help_heading = fl!("args-header"))] + #[arg(value_name = fl!("mnt-filename"))] + #[arg(help = fl!("help-arg-mnt-filename"))] + pub(crate) filename: String, + + #[arg(help_heading = fl!("args-header"))] + #[arg(value_name = fl!("mnt-mountpoint"))] + #[arg(help = fl!("help-arg-mnt-mountpoint"))] + pub(crate) mountpoint: String, + + #[arg(action = ArgAction::Help, short, long)] + #[arg(help = fl!("help-flag-help"))] + pub(crate) help: Option, + + #[arg(action = ArgAction::Version, short = 'V', long)] + #[arg(help = fl!("help-flag-version"))] + pub(crate) version: Option, + + #[arg(short, long)] + #[arg(value_name = fl!("mnt-types"))] + #[arg(help = fl!("help-arg-mnt-types", types = "\"tar\", \"zip\""))] + pub(crate) types: String, + + #[arg(long, value_name = "WF")] + #[arg(help = fl!("help-flag-max-work-factor"))] + pub(crate) max_work_factor: Option, + + #[arg(short, long)] + #[arg(value_name = fl!("identity"))] + #[arg(help = fl!("help-flag-identity"))] + pub(crate) identity: Vec, +} diff --git a/rage/src/bin/rage-mount/main.rs b/rage/src/bin/rage-mount/main.rs index 6030b8c4..8b6393f7 100644 --- a/rage/src/bin/rage-mount/main.rs +++ b/rage/src/bin/rage-mount/main.rs @@ -5,40 +5,32 @@ use age::{ cli_common::{read_identities, read_secret}, stream::StreamReader, }; -use clap::{builder::Styles, ArgAction, CommandFactory, Parser}; +use clap::{CommandFactory, Parser}; use fuse_mt::FilesystemMT; use fuser::MountOption; -use i18n_embed::{ - fluent::{fluent_language_loader, FluentLanguageLoader}, - DesktopLanguageRequester, -}; -use lazy_static::lazy_static; use log::info; -use rust_embed::RustEmbed; use std::fmt; use std::fs::File; use std::io; use std::sync::mpsc; +mod cli; mod tar; mod zip; -#[derive(RustEmbed)] -#[folder = "i18n"] -struct Localizations; - -lazy_static! { - static ref LANGUAGE_LOADER: FluentLanguageLoader = fluent_language_loader!(); +mod i18n { + include!("../rage/i18n.rs"); } +#[macro_export] macro_rules! fl { ($message_id:literal) => {{ - i18n_embed_fl::fl!($crate::LANGUAGE_LOADER, $message_id) + i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id) }}; ($message_id:literal, $($args:expr),* $(,)?) => {{ - i18n_embed_fl::fl!($crate::LANGUAGE_LOADER, $message_id, $($args), *) + i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id, $($args), *) }}; } @@ -125,56 +117,6 @@ impl fmt::Debug for Error { } } -#[derive(Debug, Parser)] -#[command(display_name = "rage-mount")] -#[command(name = "rage-mount")] -#[command(version)] -#[command(help_template = format!("\ -{{before-help}}{{about-with-newline}} -{}{}:{} {{usage}} - -{{all-args}}{{after-help}}\ - ", - Styles::default().get_usage().render(), - fl!("usage-header"), - Styles::default().get_usage().render_reset()))] -#[command(next_help_heading = fl!("flags-header"))] -#[command(disable_help_flag(true))] -#[command(disable_version_flag(true))] -struct AgeMountOptions { - #[arg(help_heading = fl!("args-header"))] - #[arg(value_name = fl!("mnt-filename"))] - #[arg(help = fl!("help-arg-mnt-filename"))] - filename: String, - - #[arg(help_heading = fl!("args-header"))] - #[arg(value_name = fl!("mnt-mountpoint"))] - #[arg(help = fl!("help-arg-mnt-mountpoint"))] - mountpoint: String, - - #[arg(action = ArgAction::Help, short, long)] - #[arg(help = fl!("help-flag-help"))] - help: Option, - - #[arg(action = ArgAction::Version, short = 'V', long)] - #[arg(help = fl!("help-flag-version"))] - version: Option, - - #[arg(short, long)] - #[arg(value_name = fl!("mnt-types"))] - #[arg(help = fl!("help-arg-mnt-types", types = "\"tar\", \"zip\""))] - types: String, - - #[arg(long, value_name = "WF")] - #[arg(help = fl!("help-flag-max-work-factor"))] - max_work_factor: Option, - - #[arg(short, long)] - #[arg(value_name = fl!("identity"))] - #[arg(help = fl!("help-flag-identity"))] - identity: Vec, -} - fn mount_fs( open: F, mountpoint: String, @@ -234,19 +176,15 @@ fn main() -> Result<(), Error> { .parse_default_env() .init(); - let requested_languages = DesktopLanguageRequester::requested_languages(); - i18n_embed::select(&*LANGUAGE_LOADER, &Localizations, &requested_languages).unwrap(); + let requested_languages = i18n::load_languages(); age::localizer().select(&requested_languages).unwrap(); - // Unfortunately the common Windows terminals don't support Unicode Directionality - // Isolation Marks, so we disable them for now. - LANGUAGE_LOADER.set_use_isolating(false); if console::user_attended() && args().len() == 1 { - AgeMountOptions::command().print_help()?; + cli::AgeMountOptions::command().print_help()?; return Ok(()); } - let opts = AgeMountOptions::parse(); + let opts = cli::AgeMountOptions::parse(); if opts.filename.is_empty() { return Err(Error::MissingFilename); diff --git a/rage/src/bin/rage/cli.rs b/rage/src/bin/rage/cli.rs new file mode 100644 index 00000000..60c061b2 --- /dev/null +++ b/rage/src/bin/rage/cli.rs @@ -0,0 +1,142 @@ +use std::path::Path; + +use clap::{builder::Styles, ArgAction, Parser}; + +use crate::fl; + +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(); + let recipient = fl!("recipient"); + let identity = fl!("identity"); + let input = fl!("input"); + let output = fl!("output"); + + format!( + "{binary_name} [--encrypt] -r {recipient} [-i {identity}] [-a] [-o {output}] [{input}]\n \ + {binary_name} --decrypt [-i {identity}] [-o {output}] [{input}]", + ) +} + +pub(crate) fn after_help_content(keygen_name: &str) -> String { + fl!( + "rage-after-help-content", + keygen_name = keygen_name, + example_age_pubkey = "\"age1...\"", + example_ssh_pubkey = "\"ssh-ed25519 AAAA...\", \"ssh-rsa AAAA...\"", + ) +} + +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, + ); + + format!( + "{}\n\n{}", + after_help_content(&keygen_name), + fl!( + "rage-after-help-example", + example_a = example_a, + example_a_output = example_a_output, + example_b = example_b, + example_c = example_c, + ), + ) +} + +#[derive(Debug, Parser)] +#[command(author, version)] +#[command(help_template = format!("\ +{{before-help}}{{about-with-newline}} +{}{}:{} {{usage}} + +{{all-args}}{{after-help}}\ + ", + Styles::default().get_usage().render(), + fl!("usage-header"), + Styles::default().get_usage().render_reset()))] +#[command(override_usage(usage()))] +#[command(next_help_heading = fl!("flags-header"))] +#[command(disable_help_flag(true))] +#[command(disable_version_flag(true))] +#[command(after_help(after_help()))] +pub(crate) struct AgeOptions { + #[arg(help_heading = fl!("args-header"))] + #[arg(value_name = fl!("input"))] + #[arg(help = fl!("help-arg-input"))] + pub(crate) input: Option, + + #[arg(action = ArgAction::Help, short, long)] + #[arg(help = fl!("help-flag-help"))] + pub(crate) help: Option, + + #[arg(action = ArgAction::Version, short = 'V', long)] + #[arg(help = fl!("help-flag-version"))] + pub(crate) version: Option, + + #[arg(short, long)] + #[arg(help = fl!("help-flag-encrypt"))] + pub(crate) encrypt: bool, + + #[arg(short, long)] + #[arg(help = fl!("help-flag-decrypt"))] + pub(crate) decrypt: bool, + + #[arg(short, long)] + #[arg(help = fl!("help-flag-passphrase"))] + pub(crate) passphrase: bool, + + #[arg(long, value_name = "WF")] + #[arg(help = fl!("help-flag-max-work-factor"))] + pub(crate) max_work_factor: Option, + + #[arg(short, long)] + #[arg(help = fl!("help-flag-armor"))] + pub(crate) armor: bool, + + #[arg(short, long)] + #[arg(value_name = fl!("recipient"))] + #[arg(help = fl!("help-flag-recipient"))] + pub(crate) recipient: Vec, + + #[arg(short = 'R', long)] + #[arg(value_name = fl!("recipients-file"))] + #[arg(help = fl!("help-flag-recipients-file"))] + pub(crate) recipients_file: Vec, + + #[arg(short, long)] + #[arg(value_name = fl!("identity"))] + #[arg(help = fl!("help-flag-identity"))] + pub(crate) identity: Vec, + + #[arg(short = 'j')] + #[arg(value_name = fl!("plugin-name"))] + #[arg(help = fl!("help-flag-plugin-name"))] + pub(crate) plugin_name: Option, + + #[arg(short, long)] + #[arg(value_name = fl!("output"))] + #[arg(help = fl!("help-flag-output"))] + pub(crate) output: Option, +} diff --git a/rage/src/bin/rage/i18n.rs b/rage/src/bin/rage/i18n.rs new file mode 100644 index 00000000..27abf2ab --- /dev/null +++ b/rage/src/bin/rage/i18n.rs @@ -0,0 +1,24 @@ +use i18n_embed::{ + fluent::{fluent_language_loader, FluentLanguageLoader}, + unic_langid::LanguageIdentifier, + DesktopLanguageRequester, +}; +use lazy_static::lazy_static; +use rust_embed::RustEmbed; + +#[derive(RustEmbed)] +#[folder = "i18n"] +struct Localizations; + +lazy_static! { + pub(crate) static ref LANGUAGE_LOADER: FluentLanguageLoader = fluent_language_loader!(); +} + +pub(crate) fn load_languages() -> Vec { + let requested_languages = DesktopLanguageRequester::requested_languages(); + i18n_embed::select(&*LANGUAGE_LOADER, &Localizations, &requested_languages).unwrap(); + // Unfortunately the common Windows terminals don't support Unicode Directionality + // Isolation Marks, so we disable them for now. + LANGUAGE_LOADER.set_use_isolating(false); + requested_languages +} diff --git a/rage/src/bin/rage/main.rs b/rage/src/bin/rage/main.rs index d2adbeb6..fecf4d76 100644 --- a/rage/src/bin/rage/main.rs +++ b/rage/src/bin/rage/main.rs @@ -9,35 +9,27 @@ use age::{ secrecy::ExposeSecret, Identity, IdentityFile, IdentityFileEntry, Recipient, }; -use clap::{builder::Styles, ArgAction, CommandFactory, Parser}; -use i18n_embed::{ - fluent::{fluent_language_loader, FluentLanguageLoader}, - DesktopLanguageRequester, -}; -use lazy_static::lazy_static; -use rust_embed::RustEmbed; +use clap::{CommandFactory, Parser}; + use std::fs::File; use std::io::{self, BufRead, BufReader}; use std::path::Path; -mod error; +mod cli; +use cli::AgeOptions; -#[derive(RustEmbed)] -#[folder = "i18n"] -struct Localizations; +mod error; -lazy_static! { - static ref LANGUAGE_LOADER: FluentLanguageLoader = fluent_language_loader!(); -} +mod i18n; #[macro_export] macro_rules! fl { ($message_id:literal) => {{ - i18n_embed_fl::fl!($crate::LANGUAGE_LOADER, $message_id) + i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id) }}; ($message_id:literal, $($args:expr),* $(,)?) => {{ - i18n_embed_fl::fl!($crate::LANGUAGE_LOADER, $message_id, $($args), *) + i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id, $($args), *) }}; } @@ -233,131 +225,6 @@ fn read_recipients( Ok(recipients) } -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(); - let recipient = fl!("recipient"); - let identity = fl!("identity"); - let input = fl!("input"); - let output = fl!("output"); - - 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(help_template = format!("\ -{{before-help}}{{about-with-newline}} -{}{}:{} {{usage}} - -{{all-args}}{{after-help}}\ - ", - Styles::default().get_usage().render(), - fl!("usage-header"), - Styles::default().get_usage().render_reset()))] -#[command(override_usage(usage()))] -#[command(next_help_heading = fl!("flags-header"))] -#[command(disable_help_flag(true))] -#[command(disable_version_flag(true))] -#[command(after_help(after_help()))] -struct AgeOptions { - #[arg(help_heading = fl!("args-header"))] - #[arg(value_name = fl!("input"))] - #[arg(help = fl!("help-arg-input"))] - input: Option, - - #[arg(action = ArgAction::Help, short, long)] - #[arg(help = fl!("help-flag-help"))] - help: Option, - - #[arg(action = ArgAction::Version, short = 'V', long)] - #[arg(help = fl!("help-flag-version"))] - version: Option, - - #[arg(short, long)] - #[arg(help = fl!("help-flag-encrypt"))] - encrypt: bool, - - #[arg(short, long)] - #[arg(help = fl!("help-flag-decrypt"))] - decrypt: bool, - - #[arg(short, long)] - #[arg(help = fl!("help-flag-passphrase"))] - passphrase: bool, - - #[arg(long, value_name = "WF")] - #[arg(help = fl!("help-flag-max-work-factor"))] - max_work_factor: Option, - - #[arg(short, long)] - #[arg(help = fl!("help-flag-armor"))] - armor: bool, - - #[arg(short, long)] - #[arg(value_name = fl!("recipient"))] - #[arg(help = fl!("help-flag-recipient"))] - recipient: Vec, - - #[arg(short = 'R', long)] - #[arg(value_name = fl!("recipients-file"))] - #[arg(help = fl!("help-flag-recipients-file"))] - recipients_file: Vec, - - #[arg(short, long)] - #[arg(value_name = fl!("identity"))] - #[arg(help = fl!("help-flag-identity"))] - identity: Vec, - - #[arg(short = 'j')] - #[arg(value_name = fl!("plugin-name"))] - #[arg(help = fl!("help-flag-plugin-name"))] - plugin_name: Option, - - #[arg(short, long)] - #[arg(value_name = fl!("output"))] - #[arg(help = fl!("help-flag-output"))] - output: Option, -} - fn set_up_io( input: Option, output: Option, @@ -654,12 +521,8 @@ fn main() -> Result<(), error::Error> { .parse_default_env() .init(); - let requested_languages = DesktopLanguageRequester::requested_languages(); - i18n_embed::select(&*LANGUAGE_LOADER, &Localizations, &requested_languages).unwrap(); + let requested_languages = i18n::load_languages(); age::localizer().select(&requested_languages).unwrap(); - // Unfortunately the common Windows terminals don't support Unicode Directionality - // Isolation Marks, so we disable them for now. - LANGUAGE_LOADER.set_use_isolating(false); // If you are piping input with no other args, this will not allow // it. diff --git a/supply-chain/config.toml b/supply-chain/config.toml index dc0d8d45..def62e78 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -179,7 +179,7 @@ criteria = "safe-to-deploy" [[exemptions.clap_complete]] version = "4.3.2" -criteria = "safe-to-run" +criteria = "safe-to-deploy" [[exemptions.clap_derive]] version = "4.3.12" @@ -189,6 +189,10 @@ criteria = "safe-to-deploy" version = "0.5.0" criteria = "safe-to-deploy" +[[exemptions.clap_mangen]] +version = "0.2.12" +criteria = "safe-to-deploy" + [[exemptions.console]] version = "0.15.7" criteria = "safe-to-deploy" @@ -433,10 +437,6 @@ criteria = "safe-to-deploy" version = "0.4.11" criteria = "safe-to-deploy" -[[exemptions.man]] -version = "0.3.0" -criteria = "safe-to-run" - [[exemptions.memchr]] version = "2.6.3" criteria = "safe-to-deploy" @@ -610,8 +610,8 @@ version = "0.8.37" criteria = "safe-to-run" [[exemptions.roff]] -version = "0.1.0" -criteria = "safe-to-run" +version = "0.2.1" +criteria = "safe-to-deploy" [[exemptions.rpassword]] version = "7.3.1"