refactor: Use select for vaults listing #19

Merged
awiteb merged 1 commit from awiteb/fix-8 into master 2024-04-29 17:50:33 +02:00 AGit
3 changed files with 136 additions and 22 deletions

View file

@ -17,6 +17,7 @@
use std::num::NonZeroU64; use std::num::NonZeroU64;
use clap::Args; use clap::Args;
use inquire::Select;
use crate::{ use crate::{
vault::{vault_state::*, Vaults}, vault::{vault_state::*, Vaults},
@ -26,25 +27,13 @@ use crate::{
#[derive(Debug, Args)] #[derive(Debug, Args)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
pub struct List { 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 /// Return the password with spesifc index
#[arg(short, long, value_name = "INDEX")] #[arg(short, long, value_name = "INDEX")]
get: Option<NonZeroU64>, get: Option<NonZeroU64>,
/// Search and display only matching passwords. /// Filter the select list
/// #[arg(short, long, value_name = "TEXT")]
/// The name and username will be searched. And service and note if included filter: Option<String>,
#[arg(short = 'e', long, value_name = "TEXT")] /// Enable regex when use `--filter` option
search: Option<String>,
/// Enable regex in the search
#[arg(short, long)] #[arg(short, long)]
regex: bool, regex: bool,
} }
@ -53,10 +42,93 @@ impl LprsCommand for List {
fn run(self, vault_manager: Vaults<Plain>) -> LprsResult<()> { fn run(self, vault_manager: Vaults<Plain>) -> LprsResult<()> {
if vault_manager.vaults.is_empty() { if vault_manager.vaults.is_empty() {
return Err(LprsError::Other( 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::<Vec<_>>();
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::<usize>()
.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(())
} }
} }

View file

@ -31,6 +31,7 @@ mod macros;
mod traits; mod traits;
pub use errors::{Error as LprsError, Result as LprsResult}; pub use errors::{Error as LprsError, Result as LprsResult};
use inquire::InquireError;
pub use traits::*; pub use traits::*;
pub const STANDARDBASE: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD); pub const STANDARDBASE: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD);
@ -64,9 +65,14 @@ fn main() -> ExitCode {
} }
if let Err(err) = cli::Cli::parse().run() { if let Err(err) = cli::Cli::parse().run() {
eprintln!("{err}"); if !matches!(
err.exit_code() err,
} else { LprsError::Inquire(InquireError::OperationCanceled)
ExitCode::SUCCESS | LprsError::Inquire(InquireError::OperationInterrupted)
) {
eprintln!("{err}");
return err.exit_code();
}
} }
ExitCode::SUCCESS
} }

View file

@ -136,6 +136,22 @@ impl Vault<Plain> {
cipher::encrypt_some(master_password, self.note.as_ref())?, 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<T> Vaults<T> impl<T> Vaults<T>
@ -198,3 +214,23 @@ impl std::fmt::Display for Format {
) )
} }
} }
impl<T: std::fmt::Debug + Clone> std::fmt::Display for Vault<T> {
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(())
}
}