refactor: Use select for vaults listing #19
3 changed files with 136 additions and 22 deletions
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue