entroll 0.1.2

Generate random passwords and print entropy in bits.
Documentation
use argh::FromArgs;
use entroll_core::Faces;
use entroll::ErasedMkpasswd;
use entroll_dictionaries::*;

/// generate random passwords with a given entropy.
#[derive(FromArgs, Debug)]
struct Args {
    // -l length
    // -d digits
    // -c lower
    // -C upper
    // -s special
    // -p program
    //
    // -m
    // -H
    // -h
    // -P
    // -s
    // -S
    // -R
    // -V version
    // -5
    //
    // -w wordlist
    // -n NUM word
    // -V verbose
    //
    //
    /// print passwond and entropy for each dictionary.
    #[argh(switch, short = 'a')]
    all: bool,

    /// the entropy that a generated password should have.
    /// Default is 16 bytes.
    #[argh(option, default = "16.", from_str_fn(parse_bytes))]
    entropy_bytes: f64,
}

/// parse a bytes or bits string into a f64 bytes value.
/// 1B denotes 1 bits, 1b denotes 1 bit.
fn parse_bytes(s: &str) -> Result<f64, String> {
    let s = s.trim();
    match s.chars().next_back() {
        Some('B') | Some('b') => {
            let (value, unit) = s.split_at(s.len() - 1);
            let value = value.parse::<f64>().map_err(|e| e.to_string())?;
            match unit {
                "B" => Ok(value),
                "b" => Ok(value / 8.),
                _ => Err(format!("invalid unit: {}", unit)),
            }
        }
        _ => s.parse::<f64>().map_err(|e| e.to_string()),
    }
}

type Row = (String, f64, String);
trait Dict: ErasedMkpasswd + Dictionary + Faces {}

impl<const N: usize> Dict for WordList<N> {}
impl<T: AsciiDictionary> Dict for AsciiDictionaryWrapper<T> {}

fn main() {
    let args: Args = argh::from_env();
    let mut rng = Box::new(rand::rngs::OsRng);

    let mut dicts: Vec<Box<dyn Dict>> = vec![];

    if args.all {
        dicts.push(Box::new(Hexadecimal::default()));
        dicts.push(Box::new(Alphanumeric::default()));
        dicts.push(Box::new(Expect::default()));
        dicts.push(Box::new(EFFDiceware5::default()));
    } else {
        dicts.push(Box::new(Hexadecimal::default()));
    }

    let entropy_bits = args.entropy_bytes * 8.;
    let rows = dicts
        .into_iter()
        .map(|dict| {
            let entropy = dict.entropy_bits_per_face() * dict.needs_faces(entropy_bits) as f64;
            let password = dict.mkpasswd_entropy_bits(entropy_bits, &mut rng);
            (dict.canonical().to_string(), entropy, password)
        })
        .collect::<Vec<Row>>();

    let verbose = args.all;

    let name_size = rows
        .iter()
        .map(|(name, _, _)| name.len())
        .max()
        .unwrap_or(0);

    if verbose {
        println!("{:name_size$} {:16} password", "name", "entropy(bits)");
    }
    for (name, entropy, password) in rows {
        if verbose {
            println!("{:name_size$} {:>16.2} {}", name, entropy, password);
        } else {
            println!("{}", password);
        }
    }
}