hfile 0.4.0

Hfile is a command-line interface (CLI) tool for generating hash values of files
Documentation
use anyhow::{Context, Result, bail};
use clap::Parser;
use hfile::{
    command::{Algorithm, Args},
    hash, walkdir,
};
use path_clean::PathClean;
use std::{fs, path::Path};

fn hash_file(algo: Algorithm, file: &Path) -> Result<String> {
    let hash = match algo {
        Algorithm::Md5 => hash::md5(file),
        Algorithm::Sha1 => hash::sha1(file),
        Algorithm::Sha256 => hash::sha256(file),
        Algorithm::Sha384 => hash::sha384(file),
        Algorithm::Sha512 => hash::sha512(file),
        Algorithm::Blake => hash::blake3(file),
    };

    hash.with_context(|| format!("failed to hash {}", file.display()))
}

fn hash_single_file(args: &Args) -> Result<()> {
    let Some(file_arg) = &args.file else {
        return Ok(());
    };

    let file = Path::new(file_arg);
    let metadata = fs::metadata(file)
        .with_context(|| format!("failed to read metadata for {}", file.display()))?;
    if metadata.is_dir() {
        bail!("Use option -p to pass a directory");
    }

    let hash = hash_file(args.algorithm, file)?;
    if args.size {
        println!(
            "{}\t{}\t{}",
            hash,
            file.clean().display(),
            bytesize::to_string(metadata.len(), true)
        );
    } else {
        println!("{hash}\t{}", file.clean().display());
    }

    Ok(())
}

fn hash_path(args: &Args, path: &str) -> Result<()> {
    if args.duplicates {
        let duplicates = walkdir::find_duplicates(path, args.algorithm)?;
        for (index, (hash, paths)) in duplicates.iter().enumerate() {
            println!("{hash}");
            for path in paths {
                println!("\t{}", path.clean().display());
            }

            if index + 1 < duplicates.len() {
                println!();
            }
        }
    } else {
        walkdir::read(path, args.algorithm, args.size)?;
    }

    Ok(())
}

fn run() -> Result<()> {
    let args = Args::parse();

    match &args.path {
        Some(path) => hash_path(&args, path),
        None => hash_single_file(&args),
    }
}

fn main() {
    if let Err(error) = run() {
        eprintln!("{error}");
        std::process::exit(1);
    }
}