diff --git a/src/cli/list_command.rs b/src/cli/list_command.rs index d0f0cc1..48ad82a 100644 --- a/src/cli/list_command.rs +++ b/src/cli/list_command.rs @@ -17,6 +17,7 @@ use std::num::NonZeroU64; use clap::Args; +use inquire::Select; use crate::{ vault::{vault_state::*, Vaults}, @@ -26,25 +27,13 @@ use crate::{ #[derive(Debug, Args)] #[command(author, version, about, long_about = None)] pub struct List { - /// Show the clean password - #[arg(short = 'p', long)] - unhide_password: bool, - /// Show the service of the password and search in it if you search - #[arg(short = 's', long)] - with_service: bool, - /// Show the note of the password and search in it if you search - #[arg(short = 'n', long)] - with_note: bool, - /// Return the password with spesifc index #[arg(short, long, value_name = "INDEX")] get: Option, - /// Search and display only matching passwords. - /// - /// The name and username will be searched. And service and note if included - #[arg(short = 'e', long, value_name = "TEXT")] - search: Option, - /// Enable regex in the search + /// Filter the select list + #[arg(short, long, value_name = "TEXT")] + filter: Option, + /// Enable regex when use `--filter` option #[arg(short, long)] regex: bool, } @@ -53,10 +42,93 @@ impl LprsCommand for List { fn run(self, vault_manager: Vaults) -> LprsResult<()> { if vault_manager.vaults.is_empty() { return Err(LprsError::Other( - "Looks like there is no passwords to list".to_owned(), + "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) { + 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") + ); + } 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("")) + ) + }; - todo!("https://git.4rs.nl/awiteb/lprs/issues/8") + 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::>(); + + 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(); + + println!( + "{}", + vault_manager + .vaults + .get(vault_idx - 1) + .expect("The index is correct") + ); + } + Ok(()) + } + + fn validate_args(&self) -> LprsResult<()> { + if self.regex && self.filter.is_none() { + return Err(LprsError::Other( + "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(()) } } diff --git a/src/main.rs b/src/main.rs index bfe7443..e7ee365 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ mod macros; mod traits; pub use errors::{Error as LprsError, Result as LprsResult}; +use inquire::InquireError; pub use traits::*; pub const STANDARDBASE: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD); @@ -64,9 +65,14 @@ fn main() -> ExitCode { } if let Err(err) = cli::Cli::parse().run() { - eprintln!("{err}"); - err.exit_code() - } else { - ExitCode::SUCCESS + if !matches!( + err, + LprsError::Inquire(InquireError::OperationCanceled) + | LprsError::Inquire(InquireError::OperationInterrupted) + ) { + eprintln!("{err}"); + return err.exit_code(); + } } + ExitCode::SUCCESS } diff --git a/src/vault/mod.rs b/src/vault/mod.rs index 81c05d3..bd6b720 100644 --- a/src/vault/mod.rs +++ b/src/vault/mod.rs @@ -136,6 +136,22 @@ impl Vault { cipher::encrypt_some(master_password, self.note.as_ref())?, )) } + + /// Return the name of the vault with the service if there + pub fn list_name(&self) -> String { + use std::fmt::Write; + let mut list_name = self.name.clone(); + if let Some(ref username) = self.username { + write!(&mut list_name, " <{username}>").expect("String never fail"); + } + if let Some(ref service) = self.service { + write!(&mut list_name, " ({service})").expect("String never fail"); + } + if self.username.is_none() && self.password.is_none() && self.note.is_some() { + write!(&mut list_name, " *Note").expect("String never fail"); + } + list_name + } } impl Vaults @@ -198,3 +214,23 @@ impl std::fmt::Display for Format { ) } } + +impl std::fmt::Display for Vault { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Name: {}", self.name)?; + if let Some(ref username) = self.username { + write!(f, "\nUsername: {username}")?; + } + if let Some(ref password) = self.password { + write!(f, "\nPassword: {password}")?; + } + if let Some(ref service) = self.service { + write!(f, "\nService: {service}")?; + } + if let Some(ref note) = self.note { + write!(f, "\nNote:\n{note}")?; + } + + Ok(()) + } +}