mirror of
https://github.com/str4d/rage.git
synced 2025-04-03 19:07:42 +03:00
Merge pull request #444 from str4d/clap-completions-and-manpages
`clap` completions and manpages
This commit is contained in:
commit
405304de06
19 changed files with 607 additions and 674 deletions
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -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'
|
||||
|
|
25
Cargo.lock
generated
25
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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]
|
||||
|
|
243
rage/build.rs
Normal file
243
rage/build.rs
Normal file
|
@ -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<String>,
|
||||
}
|
||||
|
||||
impl Example {
|
||||
const fn new(text: String, cmd: &'static str, output: Option<String>) -> Self {
|
||||
Self { text, cmd, output }
|
||||
}
|
||||
}
|
||||
|
||||
struct Examples<const N: usize>([Example; N]);
|
||||
|
||||
impl<const N: usize> Examples<N> {
|
||||
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::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
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<fs::File>) -> 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(())
|
||||
}
|
|
@ -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<G: Generator, S: Into<String>>(
|
||||
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::<G, _>(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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
34
rage/src/bin/rage-keygen/cli.rs
Normal file
34
rage/src/bin/rage-keygen/cli.rs
Normal file
|
@ -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<bool>,
|
||||
|
||||
#[arg(action = ArgAction::Version, short = 'V', long)]
|
||||
#[arg(help = fl!("help-flag-version"))]
|
||||
pub(crate) version: Option<bool>,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(value_name = fl!("output"))]
|
||||
#[arg(help = fl!("keygen-help-flag-output"))]
|
||||
pub(crate) output: Option<String>,
|
||||
}
|
|
@ -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<bool>,
|
||||
|
||||
#[arg(action = ArgAction::Version, short = 'V', long)]
|
||||
#[arg(help = fl!("help-flag-version"))]
|
||||
version: Option<bool>,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(value_name = fl!("output"))]
|
||||
#[arg(help = fl!("keygen-help-flag-output"))]
|
||||
output: Option<String>,
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
53
rage/src/bin/rage-mount/cli.rs
Normal file
53
rage/src/bin/rage-mount/cli.rs
Normal file
|
@ -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<bool>,
|
||||
|
||||
#[arg(action = ArgAction::Version, short = 'V', long)]
|
||||
#[arg(help = fl!("help-flag-version"))]
|
||||
pub(crate) version: Option<bool>,
|
||||
|
||||
#[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<u8>,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(value_name = fl!("identity"))]
|
||||
#[arg(help = fl!("help-flag-identity"))]
|
||||
pub(crate) identity: Vec<String>,
|
||||
}
|
|
@ -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<bool>,
|
||||
|
||||
#[arg(action = ArgAction::Version, short = 'V', long)]
|
||||
#[arg(help = fl!("help-flag-version"))]
|
||||
version: Option<bool>,
|
||||
|
||||
#[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<u8>,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(value_name = fl!("identity"))]
|
||||
#[arg(help = fl!("help-flag-identity"))]
|
||||
identity: Vec<String>,
|
||||
}
|
||||
|
||||
fn mount_fs<T: FilesystemMT + Send + Sync + 'static, F>(
|
||||
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);
|
||||
|
|
142
rage/src/bin/rage/cli.rs
Normal file
142
rage/src/bin/rage/cli.rs
Normal file
|
@ -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<String>,
|
||||
|
||||
#[arg(action = ArgAction::Help, short, long)]
|
||||
#[arg(help = fl!("help-flag-help"))]
|
||||
pub(crate) help: Option<bool>,
|
||||
|
||||
#[arg(action = ArgAction::Version, short = 'V', long)]
|
||||
#[arg(help = fl!("help-flag-version"))]
|
||||
pub(crate) version: Option<bool>,
|
||||
|
||||
#[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<u8>,
|
||||
|
||||
#[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<String>,
|
||||
|
||||
#[arg(short = 'R', long)]
|
||||
#[arg(value_name = fl!("recipients-file"))]
|
||||
#[arg(help = fl!("help-flag-recipients-file"))]
|
||||
pub(crate) recipients_file: Vec<String>,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(value_name = fl!("identity"))]
|
||||
#[arg(help = fl!("help-flag-identity"))]
|
||||
pub(crate) identity: Vec<String>,
|
||||
|
||||
#[arg(short = 'j')]
|
||||
#[arg(value_name = fl!("plugin-name"))]
|
||||
#[arg(help = fl!("help-flag-plugin-name"))]
|
||||
pub(crate) plugin_name: Option<String>,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(value_name = fl!("output"))]
|
||||
#[arg(help = fl!("help-flag-output"))]
|
||||
pub(crate) output: Option<String>,
|
||||
}
|
24
rage/src/bin/rage/i18n.rs
Normal file
24
rage/src/bin/rage/i18n.rs
Normal file
|
@ -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<LanguageIdentifier> {
|
||||
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
|
||||
}
|
|
@ -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 cli;
|
||||
use cli::AgeOptions;
|
||||
|
||||
mod error;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "i18n"]
|
||||
struct Localizations;
|
||||
|
||||
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<String>,
|
||||
|
||||
#[arg(action = ArgAction::Help, short, long)]
|
||||
#[arg(help = fl!("help-flag-help"))]
|
||||
help: Option<bool>,
|
||||
|
||||
#[arg(action = ArgAction::Version, short = 'V', long)]
|
||||
#[arg(help = fl!("help-flag-version"))]
|
||||
version: Option<bool>,
|
||||
|
||||
#[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<u8>,
|
||||
|
||||
#[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<String>,
|
||||
|
||||
#[arg(short = 'R', long)]
|
||||
#[arg(value_name = fl!("recipients-file"))]
|
||||
#[arg(help = fl!("help-flag-recipients-file"))]
|
||||
recipients_file: Vec<String>,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(value_name = fl!("identity"))]
|
||||
#[arg(help = fl!("help-flag-identity"))]
|
||||
identity: Vec<String>,
|
||||
|
||||
#[arg(short = 'j')]
|
||||
#[arg(value_name = fl!("plugin-name"))]
|
||||
#[arg(help = fl!("help-flag-plugin-name"))]
|
||||
plugin_name: Option<String>,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(value_name = fl!("output"))]
|
||||
#[arg(help = fl!("help-flag-output"))]
|
||||
output: Option<String>,
|
||||
}
|
||||
|
||||
fn set_up_io(
|
||||
input: Option<String>,
|
||||
output: Option<String>,
|
||||
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue