wholesum 0.2.1

Wholesome collection of file hashing algorithms all bundled in a single tool. Like sha256sum, b3sum and others, but one: the whole-sum.
Documentation
use std::{
    fmt::{self, Display},
    fs::File,
    io::{BufReader, Result, Write},
    ops::Add,
    path::Path,
};

use clap::ArgEnum;
use digest::{generic_array::ArrayLength, Digest, OutputSizeUser};
use strum::{Display, EnumString};

pub mod hashfile;

#[derive(Debug, Clone, Copy, ArgEnum, EnumString)]
#[strum(serialize_all = "kebab-case")]
pub enum Mode {
    Text,
    Binary,
}

impl Display for Mode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use std::fmt::Write;

        f.write_char(match *self {
            Self::Text => ' ',
            Self::Binary => '*',
        })
    }
}

#[derive(Debug, Clone, Copy, ArgEnum, EnumString, Display)]
#[strum(serialize_all = "kebab-case")]
pub enum Algorithm {
    Blake3,
    Blake2s,
    Blake2b,
    Sha3_512,
    Sha3_384,
    Sha3_256,
    Sha3_224,
    Sha2_512,
    Sha2_384,
    Sha2_256,
    Sha2_224,
    Sha1,
    Md5,
    #[strum(serialize = "fsb-512")]
    Fsb512,
    #[strum(serialize = "fsb-384")]
    Fsb384,
    #[strum(serialize = "fsb-256")]
    Fsb256,
    #[strum(serialize = "fsb-224")]
    Fsb224,
    #[strum(serialize = "fsb-160")]
    Fsb160,
    Gost94,
    #[strum(serialize = "groestl-512")]
    Groestl512,
    #[strum(serialize = "groestl-384")]
    Groestl384,
    #[strum(serialize = "groestl-256")]
    Groestl256,
    #[strum(serialize = "groestl-224")]
    Groestl224,
    Md4,
    Md2,
    Ripemd320,
    Ripemd256,
    Ripemd160,
    #[strum(serialize = "shabal-512")]
    Shabal512,
    #[strum(serialize = "shabal-384")]
    Shabal384,
    #[strum(serialize = "shabal-256")]
    Shabal256,
    #[strum(serialize = "shabal-224")]
    Shabal224,
    #[strum(serialize = "shabal-192")]
    Shabal192,
    Sm3,
    #[strum(serialize = "streebog-512")]
    Streebog512,
    #[strum(serialize = "streebog-256")]
    Streebog256,
    Tiger,
    Whirlpool,
}

impl Algorithm {
    pub fn into_hasher(self) -> fn(&Path) -> Result<String> {
        match self {
            Self::Blake3 => hash::<blake3::Hasher>,
            Self::Blake2s => hash::<blake2::Blake2s256>,
            Self::Blake2b => hash::<blake2::Blake2b512>,
            Self::Sha3_512 => hash::<sha3::Sha3_512>,
            Self::Sha3_384 => hash::<sha3::Sha3_384>,
            Self::Sha3_256 => hash::<sha3::Sha3_256>,
            Self::Sha3_224 => hash::<sha3::Sha3_224>,
            Self::Sha2_512 => hash::<sha2::Sha512>,
            Self::Sha2_384 => hash::<sha2::Sha384>,
            Self::Sha2_256 => hash::<sha2::Sha256>,
            Self::Sha2_224 => hash::<sha2::Sha224>,
            Self::Sha1 => hash::<sha1::Sha1>,
            Self::Md5 => hash::<md5::Md5>,
            Self::Fsb512 => hash::<fsb::Fsb512>,
            Self::Fsb384 => hash::<fsb::Fsb384>,
            Self::Fsb256 => hash::<fsb::Fsb256>,
            Self::Fsb224 => hash::<fsb::Fsb224>,
            Self::Fsb160 => hash::<fsb::Fsb160>,
            Self::Gost94 => hash::<gost94::Gost94Test>,
            Self::Groestl512 => hash::<groestl::Groestl512>,
            Self::Groestl384 => hash::<groestl::Groestl384>,
            Self::Groestl256 => hash::<groestl::Groestl256>,
            Self::Groestl224 => hash::<groestl::Groestl224>,
            Self::Md4 => hash::<md4::Md4>,
            Self::Md2 => hash::<md2::Md2>,
            Self::Ripemd320 => hash::<ripemd::Ripemd320>,
            Self::Ripemd256 => hash::<ripemd::Ripemd256>,
            Self::Ripemd160 => hash::<ripemd::Ripemd160>,
            Self::Shabal512 => hash::<shabal::Shabal512>,
            Self::Shabal384 => hash::<shabal::Shabal384>,
            Self::Shabal256 => hash::<shabal::Shabal256>,
            Self::Shabal224 => hash::<shabal::Shabal224>,
            Self::Shabal192 => hash::<shabal::Shabal192>,
            Self::Sm3 => hash::<sm3::Sm3>,
            Self::Streebog512 => hash::<streebog::Streebog512>,
            Self::Streebog256 => hash::<streebog::Streebog256>,
            Self::Tiger => hash::<tiger::Tiger>,
            Self::Whirlpool => hash::<whirlpool::Whirlpool>,
        }
    }
}

fn hash<H: SimpleHasher>(input: &Path) -> Result<String> {
    let mut reader = BufReader::new(File::open(input)?);
    let mut hasher = H::new();

    std::io::copy(&mut reader, &mut WriteWrapper(&mut hasher))?;

    Ok(hasher.finalize())
}

trait SimpleHasher {
    fn new() -> Self;
    fn update(&mut self, data: &[u8]);
    fn finalize(self) -> String;
}

impl<D> SimpleHasher for D
where
    D: Digest,
    <D as OutputSizeUser>::OutputSize: Add,
    <<D as OutputSizeUser>::OutputSize as Add>::Output: ArrayLength<u8>,
{
    fn new() -> Self {
        D::new()
    }

    fn update(&mut self, data: &[u8]) {
        D::update(self, data);
    }

    fn finalize(self) -> String {
        format!("{:x}", D::finalize(self))
    }
}

struct WriteWrapper<'a, T: SimpleHasher>(&'a mut T);

impl<'a, T: SimpleHasher> Write for WriteWrapper<'a, T> {
    fn write(&mut self, buf: &[u8]) -> Result<usize> {
        self.0.update(buf);
        Ok(buf.len())
    }

    fn flush(&mut self) -> Result<()> {
        Ok(())
    }
}