smart-password-manager-cli 0.1.0

Command line interface for smart password management built on smartpasslib. Generate deterministic passwords from secret phrases without storing them.
use anyhow::Result;
use serde::Serialize;
use smartpasslib::{SmartPassword, SmartPasswordManager};

use crate::output;

use super::short_key;

#[derive(Serialize)]
struct ListJson<'a> {
    description: &'a str,
    public_key: &'a str,
    length: usize,
}

pub fn run(json: bool) -> Result<()> {
    let manager = SmartPasswordManager::new()?;

    let mut entries: Vec<&SmartPassword> = manager.passwords().values().collect();
    entries.sort_by(|a, b| {
        a.description
            .to_lowercase()
            .cmp(&b.description.to_lowercase())
    });

    if json {
        let list: Vec<ListJson> = entries
            .iter()
            .map(|e| ListJson {
                description: &e.description,
                public_key: &e.public_key,
                length: e.length,
            })
            .collect();
        let s = serde_json::to_string_pretty(&list)?;
        println!("{s}");
        return Ok(());
    }

    if entries.is_empty() {
        output::note("No entries.");
        return Ok(());
    }

    let max_desc = entries
        .iter()
        .map(|e| e.description.chars().count())
        .max()
        .unwrap_or(20)
        .min(60);

    for entry in entries {
        let desc = pad_right(&entry.description, max_desc);
        println!(
            "{desc}  {:>3} chars  {}",
            entry.length,
            short_key(&entry.public_key)
        );
    }

    Ok(())
}

fn pad_right(s: &str, width: usize) -> String {
    let count = s.chars().count();
    if count >= width {
        return s.chars().take(width).collect();
    }
    let mut out = s.to_string();
    for _ in count..width {
        out.push(' ');
    }
    out
}