feat: Add get command #37

Merged
awiteb merged 3 commits from awiteb/get-command into master 2024-05-10 09:00:15 +02:00 AGit
4 changed files with 202 additions and 82 deletions

View file

@ -44,7 +44,7 @@ pub struct Gen {
impl LprsCommand for Gen { impl LprsCommand for Gen {
fn run(self, _vault_manager: Vaults) -> LprsResult<()> { fn run(self, _vault_manager: Vaults) -> LprsResult<()> {
println!( print!(
"{}", "{}",
passwords::PasswordGenerator::new() passwords::PasswordGenerator::new()
.length(self.length.get() as usize) .length(self.length.get() as usize)
@ -52,7 +52,7 @@ impl LprsCommand for Gen {
.lowercase_letters(self.lowercase) .lowercase_letters(self.lowercase)
.numbers(self.numbers) .numbers(self.numbers)
.symbols(self.symbols) .symbols(self.symbols)
.strict(true) .strict(false)
.generate_one() .generate_one()
.expect("The length cannot be zero") .expect("The length cannot be zero")
); );

140
src/cli/get_command.rs Normal file
View file

@ -0,0 +1,140 @@
// Lprs - A local CLI vault manager
// Copyright (C) 2024 Awiteb <a@4rs.nl>
//
// 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 <https://www.gnu.org/licenses/gpl-3.0.html>.
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<Self, Self::Err> {
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<VaultGetField>,
}
impl LprsCommand for Get {
fn run(self, vault_manager: Vaults) -> LprsResult<()> {
let parsed_index = self.location.trim().parse::<usize>();
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(())
}
}

View file

@ -14,8 +14,6 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>. // along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
use std::num::NonZeroU64;
use clap::Args; use clap::Args;
use inquire::Select; use inquire::Select;
@ -25,9 +23,6 @@ use crate::{vault::Vaults, LprsCommand, LprsError, LprsResult};
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
/// List command, used to list the vaults and search /// List command, used to list the vaults and search
pub struct List { pub struct List {
/// Return the password with spesifc index
#[arg(short, long, value_name = "INDEX")]
get: Option<NonZeroU64>,
/// Filter the select list /// Filter the select list
#[arg(short, long, value_name = "TEXT")] #[arg(short, long, value_name = "TEXT")]
filter: Option<String>, filter: Option<String>,
@ -43,80 +38,66 @@ impl LprsCommand for List {
"Looks like there is no vaults 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) {
log::info!("Getting the vault at index: {user_vault_index}"); let pattern = if self.regex || self.filter.is_none() {
if user_vault_index >= vault_manager.vaults.len() { self.filter.unwrap_or_else(|| ".".to_owned())
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 { } else {
let pattern = if self.regex || self.filter.is_none() { format!(
self.filter.unwrap_or_else(|| ".".to_owned()) ".*{}.*",
} else { regex::escape(self.filter.as_deref().unwrap_or(""))
format!( )
".*{}.*", };
regex::escape(self.filter.as_deref().unwrap_or("")) log::debug!("Listing vaults filtered by: {pattern}");
)
};
log::debug!("Listing vaults filtered by: {pattern}");
let re = regex::Regex::new(&pattern)?; let re = regex::Regex::new(&pattern)?;
let vaults_list = vault_manager let vaults_list = vault_manager
.vaults .vaults
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(idx, v)| { .filter_map(|(idx, v)| {
if re.is_match(&v.name) if re.is_match(&v.name)
|| v.username.as_deref().is_some_and(|u| re.is_match(u)) || 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.service.as_deref().is_some_and(|s| re.is_match(s))
|| v.note.as_deref().is_some_and(|n| re.is_match(n)) || v.note.as_deref().is_some_and(|n| re.is_match(n))
{ {
return Some(format!("{}) {}", idx + 1, v.list_name())); return Some(format!("{}) {}", idx + 1, v.list_name()));
} }
None None
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if vaults_list.is_empty() { if vaults_list.is_empty() {
return Err(LprsError::Other( return Err(LprsError::Other(
"There is no result match your filter".to_owned(), "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();
log::debug!("The user selected the vault at index: {vault_idx}");
println!(
"{}",
vault_manager
.vaults
.get(vault_idx - 1)
.expect("The index is correct")
);
} }
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();
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(()) 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(), "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(()) Ok(())
} }
} }

View file

@ -31,6 +31,8 @@ pub mod edit_command;
pub mod export_command; pub mod export_command;
/// Generate command, used to generate a password /// Generate command, used to generate a password
pub mod gen_command; 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 /// Import command, used to import vaults from the exported files, `lprs` or
/// `BitWarden` /// `BitWarden`
pub mod import_command; pub mod import_command;
@ -56,13 +58,15 @@ pub enum Commands {
Edit(edit_command::Edit), Edit(edit_command::Edit),
/// Generate a password /// Generate a password
Gen(gen_command::Gen), Gen(gen_command::Gen),
/// Get a entire vault or single field from it
Get(get_command::Get),
/// Export the vaults /// Export the vaults
Export(export_command::Export), Export(export_command::Export),
/// Import vaults /// Import vaults
Import(import_command::Import), 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)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]