From 1c90a825440b1c8ad4eee627407797e0b017a279 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 20 Aug 2024 13:44:50 +0200 Subject: [PATCH] refactor: Use `Either` type instade of `String` for index or name (#65) Reviewed-on: https://git.4rs.nl///awiteb/lprs/pulls/65 Co-authored-by: Awiteb Co-committed-by: Awiteb --- Cargo.lock | 7 ++++++ Cargo.toml | 1 + src/clap_parsers.rs | 29 ++++++++++++++++++++++ src/cli/edit_command.rs | 28 +++++++++++---------- src/cli/get_command.rs | 13 ++++++---- src/cli/remove_command.rs | 28 +++++++++++---------- src/utils.rs | 52 +++++++++++++++++++-------------------- 7 files changed, 100 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eda428d..a3ee073 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,6 +349,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "env_logger" version = "0.10.2" @@ -708,6 +714,7 @@ dependencies = [ "clap", "clap_complete", "directories", + "either", "inquire", "log", "passwords", diff --git a/Cargo.toml b/Cargo.toml index 6d5eb4d..daafff9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ base64 = "0.22.1" clap_complete = "4.5.2" totp-lite = "2.0.1" base32 = "0.5.0" +either = { version = "1.13.0", default-features = false } [features] default = ["update-notify"] diff --git a/src/clap_parsers.rs b/src/clap_parsers.rs index 7666505..db60083 100644 --- a/src/clap_parsers.rs +++ b/src/clap_parsers.rs @@ -14,9 +14,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use std::{fmt::Display, str::FromStr}; + +use either::Either::{self, Left, Right}; + use crate::{LprsError, LprsResult}; /// Parse the key & value arguments. +/// /// ## Errors /// - If the argument value syntax not `key=value` pub fn kv_parser(value: &str) -> LprsResult<(String, Option)> { @@ -30,3 +35,27 @@ pub fn kv_parser(value: &str) -> LprsResult<(String, Option)> { Ok((value.trim().to_owned(), None)) } } + +/// Parse `Either` type arguments. +/// +/// ## Errors +/// - If the argument value can't be parsed to `L` or `R` +pub fn either_parser(value: &str) -> LprsResult> +where + L: FromStr, + R: FromStr, + ::Err: Display, + ::Err: Display, +{ + value + .trim() + .parse::() + .map_err(|err| LprsError::ArgParse(err.to_string())) + .map(Left) + .or_else(|_| { + value + .parse::() + .map_err(|err| LprsError::ArgParse(err.to_string())) + .map(Right) + }) +} diff --git a/src/cli/edit_command.rs b/src/cli/edit_command.rs index 23ad011..caa43a1 100644 --- a/src/cli/edit_command.rs +++ b/src/cli/edit_command.rs @@ -14,10 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use std::num::NonZeroUsize; + use clap::Args; +use either::Either; use crate::{ - clap_parsers, + clap_parsers::{either_parser, kv_parser}, utils, vault::{cipher, Vaults}, LprsCommand, @@ -29,8 +32,8 @@ use crate::{ /// Edit command, used to edit the vault content pub struct Edit { /// The vault to edit, index or name - #[arg(name = "INDEX-or-NAME")] - location: String, + #[arg(name = "INDEX-or-NAME", value_parser = either_parser::)] + location: Either, #[arg(short, long)] /// The new vault name @@ -61,7 +64,7 @@ pub struct Edit { /// If the custom field not exist will created it, if it's will update it, /// if there is no value, you will enter it through a prompt (e.g `-c key`) #[arg(name = "KEY=VALUE", short = 'c', long = "custom")] - #[arg(value_parser = clap_parsers::kv_parser)] + #[arg(value_parser = kv_parser)] custom_fields: Vec<(String, Option)>, /// Force edit, will not return error if there is a problem with the args. /// @@ -72,16 +75,15 @@ pub struct Edit { impl LprsCommand for Edit { fn run(self, mut vault_manager: Vaults) -> LprsResult<()> { - let vault = - match utils::vault_by_index_or_name(self.location.trim(), &mut vault_manager.vaults) { - Ok((_, v)) => v, - Err(err) => { - if self.force { - return Ok(()); - } - return Err(err); + let vault = match utils::vault_by_index_or_name(self.location, &mut vault_manager.vaults) { + Ok((_, v)) => v, + Err(err) => { + if self.force { + return Ok(()); } - }; + return Err(err); + } + }; log::info!("Applying the new values to the vault"); if let Some(new_name) = self.name { diff --git a/src/cli/get_command.rs b/src/cli/get_command.rs index 237b1c1..93d5498 100644 --- a/src/cli/get_command.rs +++ b/src/cli/get_command.rs @@ -14,11 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::str::FromStr; +use std::{num::NonZeroUsize, str::FromStr}; use clap::Args; +use either::Either; use crate::{ + clap_parsers::either_parser, utils, vault::{cipher, Vault, Vaults}, LprsCommand, @@ -94,8 +96,9 @@ impl VaultGetField { /// Command to get a entire vault or single field from it pub struct Get { /// Whether the index of the vault or its name - #[arg(value_name = "INDEX-or-NAME")] - location: String, + #[arg(name = "INDEX-or-NAME", value_parser = either_parser::)] + location: Either, + /// A Specific field to get. /// /// Can be [name, username, password, service, note, totp_secret, totp_code, @@ -103,13 +106,13 @@ pub struct Get { /// /// where the string means a custom field #[arg(value_parser = VaultGetField::from_str)] - field: Option, + field: Option, } impl LprsCommand for Get { fn run(self, mut vault_manager: Vaults) -> LprsResult<()> { let (index, vault) = - utils::vault_by_index_or_name(self.location.trim(), &mut vault_manager.vaults)?; + utils::vault_by_index_or_name(self.location, &mut vault_manager.vaults)?; if let Some(field) = self.field { if field == VaultGetField::Index { diff --git a/src/cli/remove_command.rs b/src/cli/remove_command.rs index 28d6323..d0a904d 100644 --- a/src/cli/remove_command.rs +++ b/src/cli/remove_command.rs @@ -14,16 +14,19 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use clap::Args; +use std::num::NonZeroUsize; -use crate::{utils, vault::Vaults, LprsCommand, LprsResult}; +use clap::Args; +use either::Either; + +use crate::{clap_parsers::either_parser, utils, vault::Vaults, LprsCommand, LprsResult}; #[derive(Debug, Args)] /// Remove command, used to remove a vault from the vaults file pub struct Remove { /// The vault to remove, index or name - #[arg(name = "INDEX-or-NAME")] - location: String, + #[arg(name = "INDEX-or-NAME", value_parser = either_parser::)] + location: Either, /// Force remove, will not return error if there is no vault with the given /// index or name @@ -33,16 +36,15 @@ pub struct Remove { impl LprsCommand for Remove { fn run(self, mut vault_manager: Vaults) -> LprsResult<()> { - let index = - match utils::vault_by_index_or_name(self.location.trim(), &mut vault_manager.vaults) { - Ok((idx, _)) => idx, - Err(err) => { - if self.force { - return Ok(()); - } - return Err(err); + let index = match utils::vault_by_index_or_name(self.location, &mut vault_manager.vaults) { + Ok((idx, _)) => idx, + Err(err) => { + if self.force { + return Ok(()); } - }; + return Err(err); + } + }; vault_manager.vaults.remove(index); vault_manager.try_export() } diff --git a/src/utils.rs b/src/utils.rs index 74eccba..86492a7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -15,8 +15,10 @@ // along with this program. If not, see . use std::collections::BTreeMap; +use std::num::NonZeroUsize; use std::{fs, path::PathBuf}; +use either::Either; use inquire::{validator::Validation, Password, PasswordDisplayMode}; use passwords::{analyzer, scorer}; #[cfg(feature = "update-notify")] @@ -231,35 +233,31 @@ pub fn prompt_custom( Ok(new_fields) } -/// Returns the vault with its index by its index or name +/// Returns the vault with its index by either its index or name /// /// ## Errors /// - If there is no vault with the given index or name -pub fn vault_by_index_or_name<'a>( - index_or_name: &str, - vaults: &'a mut [Vault], -) -> LprsResult<(usize, &'a mut Vault)> { - let parsed_index = index_or_name.parse::(); +pub fn vault_by_index_or_name( + location: Either, + vaults: &mut [Vault], +) -> LprsResult<(usize, &mut Vault)> { + let idx = location + .map_right(|name| { + vaults + .iter() + .enumerate() + .find_map(|(idx, v)| (v.name == name).then_some(idx)) + .ok_or_else(|| { + LprsError::Other(format!("There is no vault with the given name `{name}`")) + }) + }) + .map_left(|idx| LprsResult::Ok(idx.get() - 1)) + .into_inner()?; - let Some((index, vault)) = (if let Ok(index) = parsed_index { - index - .checked_sub(1) - .and_then(|zeroindex| vaults.get_mut(zeroindex).map(|v| (index, v))) - } else { - vaults - .iter_mut() - .enumerate() - .find(|(_, v)| v.name == index_or_name) - }) else { - return Err(LprsError::Other(format!( - "There is no vault with the given {} `{}`", - if parsed_index.is_ok() { - "index" - } else { - "name" - }, - index_or_name, - ))); - }; - Ok((index, vault)) + Ok(( + idx, + vaults.get_mut(idx).ok_or_else(|| { + LprsError::Other(format!("There is no vault with the given index `{idx}`")) + })?, + )) }