From 44b5b3e09b6c653b0d201e268878718cfa507209 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Thu, 9 May 2024 18:20:33 +0300 Subject: [PATCH 1/3] remove: Remove `--get` option from `list` command BREAKING-CHANGE: The deletion was in favor `get` command, which is better --- src/cli/list_command.rs | 134 +++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 79 deletions(-) diff --git a/src/cli/list_command.rs b/src/cli/list_command.rs index 2276850..0953bed 100644 --- a/src/cli/list_command.rs +++ b/src/cli/list_command.rs @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::num::NonZeroU64; - use clap::Args; use inquire::Select; @@ -25,9 +23,6 @@ use crate::{vault::Vaults, LprsCommand, LprsError, LprsResult}; #[command(author, version, about, long_about = None)] /// List command, used to list the vaults and search pub struct List { - /// Return the password with spesifc index - #[arg(short, long, value_name = "INDEX")] - get: Option, /// Filter the select list #[arg(short, long, value_name = "TEXT")] filter: Option, @@ -43,80 +38,66 @@ impl LprsCommand for List { "Looks like there is no vaults to list".to_owned(), )); } - if let Some(user_vault_index) = self.get.map(|n| (n.get() - 1) as usize) { - log::info!("Getting the vault at index: {user_vault_index}"); - if user_vault_index >= vault_manager.vaults.len() { - return Err(LprsError::Other( - "The `--get` index is great then the vaults length".to_owned(), - )); - } - println!( - "{}", - vault_manager - .vaults - .get(user_vault_index) - .expect("The index is correct") - ); + + let pattern = if self.regex || self.filter.is_none() { + self.filter.unwrap_or_else(|| ".".to_owned()) } else { - let pattern = if self.regex || self.filter.is_none() { - self.filter.unwrap_or_else(|| ".".to_owned()) - } else { - format!( - ".*{}.*", - regex::escape(self.filter.as_deref().unwrap_or("")) - ) - }; - log::debug!("Listing vaults filtered by: {pattern}"); + format!( + ".*{}.*", + regex::escape(self.filter.as_deref().unwrap_or("")) + ) + }; + log::debug!("Listing vaults filtered by: {pattern}"); - let re = regex::Regex::new(&pattern)?; + let re = regex::Regex::new(&pattern)?; - let vaults_list = vault_manager - .vaults - .iter() - .enumerate() - .filter_map(|(idx, v)| { - if re.is_match(&v.name) - || v.username.as_deref().is_some_and(|u| re.is_match(u)) - || v.service.as_deref().is_some_and(|s| re.is_match(s)) - || v.note.as_deref().is_some_and(|n| re.is_match(n)) - { - return Some(format!("{}) {}", idx + 1, v.list_name())); - } - None - }) - .collect::>(); + let vaults_list = vault_manager + .vaults + .iter() + .enumerate() + .filter_map(|(idx, v)| { + if re.is_match(&v.name) + || v.username.as_deref().is_some_and(|u| re.is_match(u)) + || v.service.as_deref().is_some_and(|s| re.is_match(s)) + || v.note.as_deref().is_some_and(|n| re.is_match(n)) + { + return Some(format!("{}) {}", idx + 1, v.list_name())); + } + None + }) + .collect::>(); - if vaults_list.is_empty() { - return Err(LprsError::Other( - "There is no result match your filter".to_owned(), - )); - } - - let vault_idx = Select::new("Select a vault to view:", vaults_list) - .with_formatter(&|s| { - s.value - .split_once(") ") - .expect("The bracket are hard coded above") - .1 - .to_owned() - }) - .prompt()? - .split_once(')') - .expect("The bracket are hard coded above") - .0 - .parse::() - .unwrap_or_default(); - - log::debug!("The user selected the vault at index: {vault_idx}"); - - println!( - "{}", - vault_manager - .vaults - .get(vault_idx - 1) - .expect("The index is correct") - ); + if vaults_list.is_empty() { + return Err(LprsError::Other( + "There is no result match your filter".to_owned(), + )); } + + let vault_idx = Select::new("Select a vault to view:", vaults_list) + .with_formatter(&|s| { + s.value + .split_once(") ") + .expect("The bracket are hard coded above") + .1 + .to_owned() + }) + .prompt()? + .split_once(')') + .expect("The bracket are hard coded above") + .0 + .parse::() + .unwrap_or_default(); + + log::debug!("The user selected the vault at index: {vault_idx}"); + + println!( + "{}", + vault_manager + .vaults + .get(vault_idx - 1) + .expect("The index is correct") + ); + Ok(()) } @@ -126,11 +107,6 @@ impl LprsCommand for List { "You cannot use the `--regex` flag if you did not use the search option".to_owned(), )); } - if self.filter.is_some() && self.get.is_some() { - return Err(LprsError::Other( - "You cannot search while you want a vault with a specific index".to_owned(), - )); - } Ok(()) } } -- 2.45.2 From 8cf83c2775fb5d1ba65c205c91deb934c1cffdea Mon Sep 17 00:00:00 2001 From: Awiteb Date: Fri, 10 May 2024 05:08:48 +0300 Subject: [PATCH 2/3] chore: Use `print!` to print the password --- src/cli/gen_command.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/gen_command.rs b/src/cli/gen_command.rs index 10f423c..305ad6d 100644 --- a/src/cli/gen_command.rs +++ b/src/cli/gen_command.rs @@ -44,7 +44,7 @@ pub struct Gen { impl LprsCommand for Gen { fn run(self, _vault_manager: Vaults) -> LprsResult<()> { - println!( + print!( "{}", passwords::PasswordGenerator::new() .length(self.length.get() as usize) @@ -52,7 +52,7 @@ impl LprsCommand for Gen { .lowercase_letters(self.lowercase) .numbers(self.numbers) .symbols(self.symbols) - .strict(true) + .strict(false) .generate_one() .expect("The length cannot be zero") ); -- 2.45.2 From f9fbf1a0b7b85638ad64287738e05ec1a1c35d25 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Fri, 10 May 2024 09:12:59 +0300 Subject: [PATCH 3/3] feat: Add `get` command Fixes: https://git.4rs.nl/awiteb/lprs/issues/32 --- src/cli/get_command.rs | 140 +++++++++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 6 +- 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 src/cli/get_command.rs diff --git a/src/cli/get_command.rs b/src/cli/get_command.rs new file mode 100644 index 0000000..beae329 --- /dev/null +++ b/src/cli/get_command.rs @@ -0,0 +1,140 @@ +// Lprs - A local CLI vault manager +// Copyright (C) 2024 Awiteb +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::str::FromStr; + +use clap::Args; + +use crate::{ + vault::{Vault, Vaults}, + LprsCommand, + LprsError, + LprsResult, +}; + +#[derive(Debug, Clone, Eq, PartialEq)] +enum VaultGetField { + Index, + Name, + Username, + Password, + Service, + Note, + Custom(String), +} + +impl FromStr for VaultGetField { + type Err = LprsError; + + fn from_str(input: &str) -> Result { + Ok(match input.to_lowercase().as_str() { + "index" => Self::Index, + "name" => Self::Name, + "username" => Self::Username, + "password" => Self::Password, + "service" => Self::Service, + "note" => Self::Note, + _ => Self::Custom(input.to_owned()), + }) + } +} + +impl VaultGetField { + /// Returns the field from the vault + pub fn get_from_vault<'a>(&self, vault: &'a Vault) -> Option<&'a str> { + match self { + Self::Index => None, + Self::Name => Some(&vault.name), + Self::Username => vault.username.as_deref(), + Self::Password => vault.password.as_deref(), + Self::Service => vault.service.as_deref(), + Self::Note => vault.note.as_deref(), + Self::Custom(custom_field) => vault.custom_fields.get(custom_field).map(|x| x.as_str()), + } + } + + /// Returns the field as `&str` + pub fn as_str(&self) -> &str { + match self { + Self::Index => "index", + Self::Name => "name", + Self::Username => "username", + Self::Password => "password", + Self::Service => "service", + Self::Note => "note", + Self::Custom(field) => field.as_str(), + } + } +} + +#[derive(Debug, Args)] +#[command(author, version, about, long_about = None)] +/// 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, + /// A Specific field to get. + /// + /// Can be [name,username,password,service,note,"string"] where the string + /// means a custom field + #[arg(value_parser = VaultGetField::from_str)] + field: Option, +} + +impl LprsCommand for Get { + fn run(self, vault_manager: Vaults) -> LprsResult<()> { + let parsed_index = self.location.trim().parse::(); + let Some((index, vault)) = (if let Ok(index) = parsed_index { + vault_manager.vaults.get(index - 1).map(|v| (index, v)) + } else { + vault_manager + .vaults + .iter() + .enumerate() + .find(|(_, v)| v.name == self.location) + }) else { + return Err(LprsError::Other(format!( + "There is no vault with the given {} `{}`", + if parsed_index.is_ok() { + "index" + } else { + "name" + }, + self.location.trim(), + ))); + }; + + if let Some(field) = self.field { + if field == VaultGetField::Index { + print!("{index}"); + return Ok(()); + } + + if let Some(value) = field.get_from_vault(vault) { + print!("{value}") + } else { + return Err(LprsError::Other(format!( + "There is no value for `{}`", + field.as_str() + ))); + } + } else { + println!("{vault}"); + } + Ok(()) + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a9f055a..7b0ae8f 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -31,6 +31,8 @@ pub mod edit_command; pub mod export_command; /// Generate command, used to generate a password pub mod gen_command; +/// Command to get a entire vault or single field from it +pub mod get_command; /// Import command, used to import vaults from the exported files, `lprs` or /// `BitWarden` pub mod import_command; @@ -56,13 +58,15 @@ pub enum Commands { Edit(edit_command::Edit), /// Generate a password Gen(gen_command::Gen), + /// Get a entire vault or single field from it + Get(get_command::Get), /// Export the vaults Export(export_command::Export), /// Import vaults Import(import_command::Import), } -impl_commands!(Commands, Add Remove List Clean Edit Gen Export Import); +impl_commands!(Commands, Add Remove List Clean Edit Gen Get Export Import); #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] -- 2.45.2