aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2020-05-22 00:59:34 -0400
committerJesse Luehrs <doy@tozt.net>2020-05-22 00:59:34 -0400
commit707f0bac2141ef7120c0493de3a5d331a511c40c (patch)
tree3cf21d2a94f211b7adf854d0d87662b1ef76a640
parent318413b119c5f0e988079bb7bede1a4a86583a7a (diff)
downloadrbw-707f0bac2141ef7120c0493de3a5d331a511c40c.tar.gz
rbw-707f0bac2141ef7120c0493de3a5d331a511c40c.zip
use structopt instead of clap
-rw-r--r--Cargo.lock105
-rw-r--r--Cargo.toml2
-rw-r--r--src/bin/rbw/commands.rs13
-rw-r--r--src/bin/rbw/main.rs591
4 files changed, 351 insertions, 360 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7f9fbe2..a606f44 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -538,6 +538,15 @@ dependencies = [
]
[[package]]
+name = "heck"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
name = "hermit-abi"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -899,6 +908,33 @@ dependencies = [
]
[[package]]
+name = "paw"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09c0fc9b564dbc3dc2ed7c92c0c144f4de340aa94514ce2b446065417c4084e9"
+dependencies = [
+ "paw-attributes",
+ "paw-raw",
+]
+
+[[package]]
+name = "paw-attributes"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f35583365be5d148e959284f42526841917b7bfa09e2d1a7ad5dde2cf0eaa39"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "paw-raw"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f0b59668fe80c5afe998f0c0bf93322bf2cd66cafeeb80581f291716f3467f2"
+
+[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -949,6 +985,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
[[package]]
+name = "proc-macro-error"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "syn-mid",
+ "version_check",
+]
+
+[[package]]
name = "proc-macro2"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1031,6 +1093,7 @@ dependencies = [
"log",
"nix",
"openssl",
+ "paw",
"percent-encoding",
"rand",
"region",
@@ -1039,6 +1102,7 @@ dependencies = [
"serde",
"serde_json",
"snafu",
+ "structopt",
"tempfile",
"tokio",
"uuid",
@@ -1326,6 +1390,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
+name = "structopt"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef"
+dependencies = [
+ "clap",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "syn"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1337,6 +1425,17 @@ dependencies = [
]
[[package]]
+name = "syn-mid"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "synstructure"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1515,6 +1614,12 @@ dependencies = [
]
[[package]]
+name = "unicode-segmentation"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
+
+[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 6019152..b281ba2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,7 @@ humantime = "1.3"
log = "0.4"
nix = "0.17"
openssl = "0.10"
+paw = "1.0"
percent-encoding = "2.0"
rand = "0.7"
region = "2.1"
@@ -34,6 +35,7 @@ ring = "0.16"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
snafu = "0.6"
+structopt = { version = "0.3", features = ["paw"] }
tempfile = "3.1"
tokio = { version = "0.2", features = ["full"] }
uuid = { version = "0.8", features = ["v4"] }
diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs
index ff6f51e..f7cd6c2 100644
--- a/src/bin/rbw/commands.rs
+++ b/src/bin/rbw/commands.rs
@@ -279,11 +279,11 @@ enum ListField {
Folder,
}
-impl std::convert::TryFrom<&str> for ListField {
+impl std::convert::TryFrom<&String> for ListField {
type Error = anyhow::Error;
- fn try_from(s: &str) -> anyhow::Result<Self> {
- Ok(match s {
+ fn try_from(s: &String) -> anyhow::Result<Self> {
+ Ok(match s.as_str() {
"name" => Self::Name,
"id" => Self::Id,
"user" => Self::User,
@@ -368,10 +368,9 @@ pub fn sync() -> anyhow::Result<()> {
Ok(())
}
-pub fn list(fields: &[&str]) -> anyhow::Result<()> {
+pub fn list(fields: &[String]) -> anyhow::Result<()> {
let fields: Vec<ListField> = fields
.iter()
- .copied()
.map(std::convert::TryFrom::try_from)
.collect::<anyhow::Result<_>>()?;
@@ -438,7 +437,7 @@ pub fn get(name: &str, user: Option<&str>, full: bool) -> anyhow::Result<()> {
pub fn add(
name: &str,
username: Option<&str>,
- uris: Vec<&str>,
+ uris: Vec<String>,
folder: Option<&str>,
) -> anyhow::Result<()> {
unlock()?;
@@ -529,7 +528,7 @@ pub fn add(
pub fn generate(
name: Option<&str>,
username: Option<&str>,
- uris: Vec<&str>,
+ uris: Vec<String>,
folder: Option<&str>,
len: usize,
ty: rbw::pwgen::Type,
diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs
index 9c65cb4..3b6e9b6 100644
--- a/src/bin/rbw/main.rs
+++ b/src/bin/rbw/main.rs
@@ -6,377 +6,262 @@ mod actions;
mod commands;
mod sock;
-fn main() {
+#[derive(Debug, structopt::StructOpt)]
+#[structopt(about = "Unofficial Bitwarden CLI")]
+enum Opt {
+ #[structopt(about = "Get or set configuration options")]
+ Config {
+ #[structopt(subcommand)]
+ config: Config,
+ },
+
+ #[structopt(about = "Log in to the Bitwarden server")]
+ Login,
+
+ #[structopt(about = "Unlock the local Bitwarden database")]
+ Unlock,
+
+ #[structopt(about = "Update the local copy of the Bitwarden database")]
+ Sync,
+
+ #[structopt(
+ about = "List all entries in the local Bitwarden database",
+ visible_alias = "ls"
+ )]
+ List {
+ #[structopt(
+ long,
+ help = "Fields to display. \
+ Available options are id, name, user, folder. \
+ Multiple fields will be separated by tabs.",
+ default_value = "name"
+ )]
+ fields: Vec<String>,
+ },
+
+ #[structopt(about = "Display the password for a given entry")]
+ Get {
+ #[structopt(help = "Name of the entry to display")]
+ name: String,
+ #[structopt(help = "Username of the entry to display")]
+ user: Option<String>,
+ #[structopt(
+ long,
+ help = "Display the notes in addition to the password"
+ )]
+ full: bool,
+ },
+
+ #[structopt(
+ about = "Add a new password to the database",
+ long_about = "Add a new password to the database\n\n\
+ This command will open a text editor to enter \
+ the password and notes. The editor to use is determined \
+ by the value of the $EDITOR environment variable. The \
+ first line will be saved as the password and the \
+ remainder will be saved as a note."
+ )]
+ Add {
+ #[structopt(help = "Name of the password entry")]
+ name: String,
+ #[structopt(help = "Username for the password entry")]
+ user: Option<String>,
+ #[structopt(long, help = "URI for the password entry")]
+ uri: Vec<String>,
+ #[structopt(long, help = "Folder for the password entry")]
+ folder: Option<String>,
+ },
+
+ #[structopt(
+ about = "Generate a new password",
+ long_about = "Generate a new password\n\n\
+ If given a password entry name, also save the generated \
+ password to the database.",
+ visible_alias = "gen",
+ group = clap::ArgGroup::with_name("password-type").args(&[
+ "no-symbols",
+ "only-numbers",
+ "nonconfusables",
+ "diceware",
+ ])
+ )]
+ Generate {
+ #[structopt(help = "Length of the password to generate")]
+ len: usize,
+ #[structopt(help = "Name of the password entry")]
+ name: Option<String>,
+ #[structopt(help = "Username for the password entry")]
+ user: Option<String>,
+ #[structopt(long, help = "URI for the password entry")]
+ uri: Vec<String>,
+ #[structopt(long, help = "Folder for the password entry")]
+ folder: Option<String>,
+ #[structopt(
+ long = "no-symbols",
+ help = "Generate a password with no special characters"
+ )]
+ no_symbols: bool,
+ #[structopt(
+ long = "only-numbers",
+ help = "Generate a password consisting of only numbers"
+ )]
+ only_numbers: bool,
+ #[structopt(
+ long,
+ help = "Generate a password without visually similar \
+ characters (useful for passwords intended to be \
+ written down)"
+ )]
+ nonconfusables: bool,
+ #[structopt(
+ long,
+ help = "Generate a password of multiple dictionary \
+ words chosen from the EFF word list. The len \
+ parameter for this option will set the number \
+ of words to generate, rather than characters."
+ )]
+ diceware: bool,
+ },
+
+ #[structopt(
+ about = "Modify an existing password",
+ long_about = "Modify an existing password\n\n\
+ This command will open a text editor with the existing \
+ password and notes of the given entry for editing. \
+ The editor to use is determined by the value of the \
+ $EDITOR environment variable. The first line will be \
+ saved as the password and the remainder will be saved \
+ as a note."
+ )]
+ Edit {
+ #[structopt(help = "Name of the password entry")]
+ name: String,
+ #[structopt(help = "Username for the password entry")]
+ user: Option<String>,
+ },
+
+ #[structopt(about = "Remove a given entry", visible_alias = "rm")]
+ Remove {
+ #[structopt(help = "Name of the password entry")]
+ name: String,
+ #[structopt(help = "Username for the password entry")]
+ user: Option<String>,
+ },
+
+ #[structopt(about = "View the password history for a given entry")]
+ History {
+ #[structopt(help = "Name of the password entry")]
+ name: String,
+ #[structopt(help = "Username for the password entry")]
+ user: Option<String>,
+ },
+
+ #[structopt(about = "Lock the password database")]
+ Lock,
+
+ #[structopt(about = "Remove the local copy of the password database")]
+ Purge,
+
+ #[structopt(
+ name = "stop-agent",
+ about = "Terminate the background agent"
+ )]
+ StopAgent,
+}
+
+#[derive(Debug, structopt::StructOpt)]
+enum Config {
+ #[structopt(about = "Show the values of all configuration settings")]
+ Show,
+ #[structopt(about = "Set a configuration option")]
+ Set {
+ #[structopt(help = "Configuration key to set")]
+ key: String,
+ #[structopt(help = "Value to set the configuration option to")]
+ value: String,
+ },
+ #[structopt(about = "Reset a configuration option to its default")]
+ Unset {
+ #[structopt(help = "Configuration key to unset")]
+ key: String,
+ },
+}
+
+#[paw::main]
+fn main(opt: Opt) {
env_logger::from_env(
env_logger::Env::default().default_filter_or("info"),
)
.init();
- let matches = clap::App::new("rbw")
- .about("Unofficial Bitwarden CLI")
- .author(clap::crate_authors!())
- .version(clap::crate_version!())
- .subcommand(
- clap::SubCommand::with_name("config")
- .about("Get or set configuration options")
- .subcommand(
- clap::SubCommand::with_name("show").about(
- "Show the values of all configuration settings",
- ),
- )
- .subcommand(
- clap::SubCommand::with_name("set")
- .about("Set a configuration option")
- .arg(
- clap::Arg::with_name("key")
- .required(true)
- .help("Configuration key to set"),
- )
- .arg(
- clap::Arg::with_name("value")
- .required(true)
- .help(
- "Value to set the configuration option to",
- ),
- ),
- )
- .subcommand(
- clap::SubCommand::with_name("unset")
- .about("Reset a configuration option to its default")
- .arg(
- clap::Arg::with_name("key")
- .required(true)
- .help("Configuration key to unset"),
- ),
- ),
- )
- .subcommand(
- clap::SubCommand::with_name("login")
- .about("Log in to the Bitwarden server"),
- )
- .subcommand(
- clap::SubCommand::with_name("unlock")
- .about("Unlock the local Bitwarden database"),
- )
- .subcommand(
- clap::SubCommand::with_name("sync")
- .about("Update the local copy of the Bitwarden database"),
- )
- .subcommand(
- clap::SubCommand::with_name("list")
- .about("List all entries in the local Bitwarden database")
- .arg(
- clap::Arg::with_name("fields")
- .long("fields")
- .takes_value(true)
- .use_delimiter(true)
- .multiple(true)
- .help(
- "Fields to display. \
- Available options are id, name, user, folder. \
- Multiple fields will be separated by tabs.",
- ),
- )
- .visible_alias("ls"),
- )
- .subcommand(
- clap::SubCommand::with_name("get")
- .about("Display the password for a given entry")
- .arg(
- clap::Arg::with_name("name")
- .required(true)
- .help("Name of the entry to display"),
- )
- .arg(
- clap::Arg::with_name("user")
- .help("Username of the entry to display"),
- )
- .arg(
- clap::Arg::with_name("full").long("full").help(
- "Display the notes in addition to the password",
- ),
- ),
- )
- .subcommand(
- clap::SubCommand::with_name("add")
- .about("Add a new password to the database")
- .long_about(
- "Add a new password to the database\n\n\
- This command will open a text editor to enter \
- the password and notes. The editor to use is determined \
- by the value of the $EDITOR environment variable. The \
- first line will be saved as the password and the \
- remainder will be saved as a note.",
- )
- .arg(
- clap::Arg::with_name("name")
- .required(true)
- .help("Name of the password entry"),
- )
- .arg(
- clap::Arg::with_name("user")
- .help("Username for the password entry"),
- )
- .arg(
- clap::Arg::with_name("uri")
- .long("uri")
- .takes_value(true)
- .multiple(true)
- .number_of_values(1)
- .use_delimiter(false)
- .help("URI for the password entry"),
- )
- .arg(
- clap::Arg::with_name("folder")
- .long("folder")
- .takes_value(true)
- .help("Folder for the password entry"),
- ),
- )
- .subcommand(
- clap::SubCommand::with_name("generate")
- .about("Generate a new password")
- .long_about(
- "Generate a new password\n\n\
- If given a password entry name, also save the generated \
- password to the database.",
- )
- .arg(
- clap::Arg::with_name("len")
- .required(true)
- .help("Length of the password to generate"),
- )
- .arg(
- clap::Arg::with_name("name")
- .help("Name of the password entry"),
- )
- .arg(
- clap::Arg::with_name("user")
- .help("Username for the password entry"),
- )
- .arg(
- clap::Arg::with_name("uri")
- .long("uri")
- .takes_value(true)
- .multiple(true)
- .number_of_values(1)
- .use_delimiter(false)
- .help("URI for the password entry"),
- )
- .arg(
- clap::Arg::with_name("folder")
- .long("folder")
- .takes_value(true)
- .help("Folder for the password entry"),
- )
- .arg(
- clap::Arg::with_name("no-symbols")
- .long("no-symbols")
- .help(
- "Generate a password with no special characters",
- ),
- )
- .arg(
- clap::Arg::with_name("only-numbers")
- .long("only-numbers")
- .help(
- "Generate a password consisting of only numbers",
- ),
- )
- .arg(
- clap::Arg::with_name("nonconfusables")
- .long("nonconfusables")
- .help(
- "Generate a password without visually similar \
- characters (useful for passwords intended to be \
- written down)",
- ),
- )
- .arg(clap::Arg::with_name("diceware").long("diceware").help(
- "Generate a password of multiple dictionary \
- words chosen from the EFF word list. The len \
- parameter for this option will set the number \
- of words to generate, rather than characters.",
- ))
- .group(clap::ArgGroup::with_name("password-type").args(&[
- "no-symbols",
- "only-numbers",
- "nonconfusables",
- "diceware",
- ]))
- .visible_alias("gen"),
- )
- .subcommand(
- clap::SubCommand::with_name("edit")
- .about("Modify an existing password")
- .long_about(
- "Modify an existing password\n\n\
- This command will open a text editor with the existing \
- password and notes of the given entry for editing. \
- The editor to use is determined by the value of the \
- $EDITOR environment variable. The first line will be \
- saved as the password and the remainder will be saved \
- as a note.",
- )
- .arg(
- clap::Arg::with_name("name")
- .required(true)
- .help("Name of the password entry"),
- )
- .arg(
- clap::Arg::with_name("user")
- .help("Username for the password entry"),
- ),
- )
- .subcommand(
- clap::SubCommand::with_name("remove")
- .about("Remove a given entry")
- .arg(
- clap::Arg::with_name("name")
- .required(true)
- .help("Name of the password entry"),
- )
- .arg(
- clap::Arg::with_name("user")
- .help("Username for the password entry"),
- )
- .visible_alias("rm"),
- )
- .subcommand(
- clap::SubCommand::with_name("history")
- .about("View the password history for a given entry")
- .arg(
- clap::Arg::with_name("name")
- .required(true)
- .help("Name of the password entry"),
- )
- .arg(
- clap::Arg::with_name("user")
- .help("Username for the password entry"),
- ),
- )
- .subcommand(
- clap::SubCommand::with_name("lock")
- .about("Lock the password database"),
- )
- .subcommand(
- clap::SubCommand::with_name("purge")
- .about("Remove the local copy of the password database"),
- )
- .subcommand(
- clap::SubCommand::with_name("stop-agent")
- .about("Terminate the background agent")
- .visible_alias("logout"),
- )
- .get_matches();
-
- let res = match matches.subcommand() {
- ("config", Some(smatches)) => match smatches.subcommand() {
- ("show", Some(_)) => {
- commands::config_show().context("config show")
+ let res = match opt {
+ Opt::Config { config } => match config {
+ Config::Show => commands::config_show().context("config show"),
+ Config::Set { key, value } => {
+ commands::config_set(&key, &value).context("config set")
}
- // these unwraps are fine because key and value are both marked
- // .required(true)
- ("set", Some(ssmatches)) => commands::config_set(
- ssmatches.value_of("key").unwrap(),
- ssmatches.value_of("value").unwrap(),
- )
- .context("config set"),
- // this unwrap is fine because key is marked .required(true)
- ("unset", Some(ssmatches)) => {
- commands::config_unset(ssmatches.value_of("key").unwrap())
- .context("config unset")
- }
- _ => {
- eprintln!("{}", smatches.usage());
- std::process::exit(1);
+ Config::Unset { key } => {
+ commands::config_unset(&key).context("config unset")
}
},
- ("login", Some(_)) => commands::login().context("login"),
- ("unlock", Some(_)) => commands::unlock().context("unlock"),
- ("sync", Some(_)) => commands::sync().context("sync"),
- ("list", Some(smatches)) => commands::list(
- &smatches
- .values_of("fields")
- .map(|it| it.collect())
- .unwrap_or_else(|| vec!["name"]),
- )
- .context("list"),
- // this unwrap is safe because name is marked .required(true)
- ("get", Some(smatches)) => commands::get(
- smatches.value_of("name").unwrap(),
- smatches.value_of("user"),
- smatches.is_present("full"),
- )
- .context("get"),
- // this unwrap is safe because name is marked .required(true)
- ("add", Some(smatches)) => commands::add(
- smatches.value_of("name").unwrap(),
- smatches.value_of("user"),
- smatches
- .values_of("uri")
- .map(|it| it.collect())
- .unwrap_or_else(|| vec![]),
- smatches.value_of("folder"),
- )
- .context("add"),
- ("generate", Some(smatches)) => {
- let ty = if smatches.is_present("no-symbols") {
+ Opt::Login => commands::login().context("login"),
+ Opt::Unlock => commands::unlock().context("unlock"),
+ Opt::Sync => commands::sync().context("sync"),
+ Opt::List { fields } => commands::list(&fields).context("list"),
+ Opt::Get { name, user, full } => {
+ commands::get(&name, user.as_deref(), full).context("get")
+ }
+ Opt::Add {
+ name,
+ user,
+ uri,
+ folder,
+ } => commands::add(&name, user.as_deref(), uri, folder.as_deref())
+ .context("add"),
+ Opt::Generate {
+ len,
+ name,
+ user,
+ uri,
+ folder,
+ no_symbols,
+ only_numbers,
+ nonconfusables,
+ diceware,
+ } => {
+ let ty = if no_symbols {
rbw::pwgen::Type::NoSymbols
- } else if smatches.is_present("only-numbers") {
+ } else if only_numbers {
rbw::pwgen::Type::Numbers
- } else if smatches.is_present("nonconfusables") {
+ } else if nonconfusables {
rbw::pwgen::Type::NonConfusables
- } else if smatches.is_present("diceware") {
+ } else if diceware {
rbw::pwgen::Type::Diceware
} else {
rbw::pwgen::Type::AllChars
};
- // this unwrap is fine because len is marked as .required(true)
- let len = smatches.value_of("len").unwrap();
- match len.parse() {
- Ok(len) => commands::generate(
- smatches.value_of("name"),
- smatches.value_of("user"),
- smatches
- .values_of("uri")
- .map(|it| it.collect())
- .unwrap_or_else(|| vec![]),
- smatches.value_of("folder"),
- len,
- ty,
- )
- .context("generate"),
- Err(e) => Err(e.into()),
- }
+ commands::generate(
+ name.as_deref(),
+ user.as_deref(),
+ uri,
+ folder.as_deref(),
+ len,
+ ty,
+ )
+ .context("generate")
+ }
+ Opt::Edit { name, user } => {
+ commands::edit(&name, user.as_deref()).context("edit")
}
- // this unwrap is safe because name is marked .required(true)
- ("edit", Some(smatches)) => commands::edit(
- smatches.value_of("name").unwrap(),
- smatches.value_of("user"),
- )
- .context("edit"),
- // this unwrap is safe because name is marked .required(true)
- ("remove", Some(smatches)) => commands::remove(
- smatches.value_of("name").unwrap(),
- smatches.value_of("user"),
- )
- .context("remove"),
- // this unwrap is safe because name is marked .required(true)
- ("history", Some(smatches)) => commands::history(
- smatches.value_of("name").unwrap(),
- smatches.value_of("user"),
- )
- .context("history"),
- ("lock", Some(_)) => commands::lock().context("lock"),
- ("purge", Some(_)) => commands::purge().context("purge"),
- ("stop-agent", Some(_)) => {
- commands::stop_agent().context("stop-agent")
+ Opt::Remove { name, user } => {
+ commands::remove(&name, user.as_deref()).context("remove")
}
- _ => {
- eprintln!("{}", matches.usage());
- std::process::exit(1);
+ Opt::History { name, user } => {
+ commands::history(&name, user.as_deref()).context("history")
}
+ Opt::Lock => commands::lock().context("lock"),
+ Opt::Purge => commands::purge().context("purge"),
+ Opt::StopAgent => commands::stop_agent().context("stop-agent"),
}
.context("rbw");