pub mod add;
pub mod delete;
pub mod edit;
pub mod get;
pub mod list;
pub mod verify;
use anyhow::{Result, bail};
use smartpasslib::{SmartPassword, SmartPasswordManager};
pub fn find_entry(
manager: &SmartPasswordManager,
key: Option<String>,
description: Option<String>,
) -> Result<SmartPassword> {
if let Some(k) = key {
if let Some(entry) = manager.get(&k) {
return Ok(entry.clone());
}
let matches = resolve_short_key(manager, &k);
match matches.len() {
0 => bail!("No entry found for key '{k}'"),
1 => return Ok(matches[0].clone()),
_ => bail!("Ambiguous key '{k}' — matches {} entries", matches.len()),
}
}
if let Some(d) = description {
let mut matches: Vec<SmartPassword> = manager
.passwords()
.values()
.filter(|e| e.description == d)
.cloned()
.collect();
match matches.len() {
0 => bail!("No entry found with description '{d}'"),
1 => return Ok(matches.remove(0)),
_ => bail!(
"Ambiguous description '{d}' — matches {} entries. Use --key.",
matches.len() + 1
),
}
}
bail!("Either --key or --description is required");
}
fn resolve_short_key(manager: &SmartPasswordManager, short: &str) -> Vec<SmartPassword> {
let lower = short.to_lowercase();
manager
.passwords()
.values()
.filter(|e| {
let k = e.public_key.to_lowercase();
k == lower || k.starts_with(&lower) || k.ends_with(&lower) || short_matches(&k, &lower)
})
.cloned()
.collect()
}
fn short_matches(full: &str, short: &str) -> bool {
if let Some((head, tail)) = short.split_once('…') {
full.starts_with(head) && full.ends_with(tail)
} else {
false
}
}
pub fn short_key(key: &str) -> String {
if key.len() <= 8 {
return key.to_string();
}
format!("{}…{}", &key[..4], &key[key.len() - 4..])
}
pub fn confirm(prompt: &str) -> bool {
use std::io::{self, Write};
print!("{prompt} [y/N]: ");
let _ = io::stdout().flush();
let mut buf = String::new();
if io::stdin().read_line(&mut buf).is_err() {
return false;
}
matches!(buf.trim().to_lowercase().as_str(), "y" | "yes")
}