refactor: Use select for vaults listing (#19)
Reviewed-on: #19 Fixes: #10 Fixes: #8 Co-authored-by: Awiteb <a@4rs.nl> Co-committed-by: Awiteb <a@4rs.nl>
This commit is contained in:
parent
848c82b433
commit
83c7296bf7
3 changed files with 136 additions and 22 deletions
|
@ -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<NonZeroU64>,
|
||||
/// 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<String>,
|
||||
/// Enable regex in the search
|
||||
/// Filter the select list
|
||||
#[arg(short, long, value_name = "TEXT")]
|
||||
filter: Option<String>,
|
||||
/// 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<Plain>) -> 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::<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(())
|
||||
}
|
||||
}
|
||||
|
|
14
src/main.rs
14
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
|
||||
}
|
||||
|
|
|
@ -136,6 +136,22 @@ impl Vault<Plain> {
|
|||
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>
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue