humanhash 0.2.1

Deterministic, BIP39-encoded human-readable fingerprints of digests. Same hash always renders the same short, memorable, dash-separated word string.
Documentation
use humanhash::{DEFAULT_WORDS, HumanizeOptions, MAX_WORDS, humanize_with};
use std::process::ExitCode;

fn usage() -> ExitCode {
    eprintln!(
        "humanhash - deterministic BIP39-encoded fingerprints of digests\n\
         \n\
         Usage:\n\
           humanhash <hash> [--words <n>] [--separator <s>]\n\
         \n\
         Options:\n\
           -w, --words <n>      number of words in the output (1..={MAX_WORDS}, default {DEFAULT_WORDS})\n\
           -s, --separator <s>  joiner between words (default '-')\n\
         \n\
         Input is treated as a hex string. Tolerates a leading 0x or urn:uuid: prefix,\n\
         UUID dashes, and whitespace. Same input always produces the same output.\n\
         \n\
         Examples:\n\
           humanhash ac84a4a\n\
           humanhash 0123456789abcdef0123456789abcdef\n\
           humanhash 550e8400-e29b-41d4-a716-446655440000\n\
           humanhash $(git rev-parse HEAD)\n\
           humanhash --words 3 ac84a4a"
    );
    ExitCode::from(2)
}

fn main() -> ExitCode {
    let args: Vec<String> = std::env::args().skip(1).collect();
    let mut hash: Option<String> = None;
    let mut separator: String = "-".to_string();
    let mut words: u8 = DEFAULT_WORDS;
    let mut i = 0;
    while i < args.len() {
        let a = &args[i];
        match a.as_str() {
            "--separator" | "-s" => {
                let Some(next) = args.get(i + 1) else {
                    return usage();
                };
                separator = next.clone();
                i += 2;
            }
            "--words" | "-w" => {
                let Some(next) = args.get(i + 1) else {
                    return usage();
                };
                let Ok(n) = next.parse::<u8>() else {
                    eprintln!("error: --words expects an integer in 1..={MAX_WORDS}");
                    return ExitCode::from(2);
                };
                words = n;
                i += 2;
            }
            "--help" | "-h" => return usage(),
            _ if a.starts_with('-') && a.len() > 1 => return usage(),
            _ => {
                if hash.is_some() {
                    return usage();
                }
                hash = Some(a.clone());
                i += 1;
            }
        }
    }
    let Some(hash) = hash else { return usage() };
    match humanize_with(
        &hash,
        HumanizeOptions {
            words,
            separator: separator.as_str(),
        },
    ) {
        Ok(s) => {
            println!("{s}");
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("error: {e}");
            ExitCode::FAILURE
        }
    }
}