shasum: Support `-o` in hash mode

Fixes #32
This commit is contained in:
Luke Street 2024-01-24 23:21:55 -07:00
parent f71931838c
commit 26cc6a13b4
1 changed files with 32 additions and 16 deletions

View File

@ -1,15 +1,16 @@
use std::{ use std::{
fs::File, fs::File,
io::{BufRead, BufReader, Read}, io::{stdout, BufRead, BufReader, Read, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use argp::FromArgs; use argp::FromArgs;
use owo_colors::{OwoColorize, Stream}; use owo_colors::{OwoColorize, Stream};
use path_slash::PathExt;
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use crate::util::file::{open_file, process_rsp, touch}; use crate::util::file::{buf_writer, open_file, process_rsp, touch};
#[derive(FromArgs, PartialEq, Eq, Debug)] #[derive(FromArgs, PartialEq, Eq, Debug)]
/// Print or check SHA1 (160-bit) checksums. /// Print or check SHA1 (160-bit) checksums.
@ -22,7 +23,8 @@ pub struct Args {
/// path to input file(s) /// path to input file(s)
files: Vec<PathBuf>, files: Vec<PathBuf>,
#[argp(option, short = 'o')] #[argp(option, short = 'o')]
/// touch output file on successful check /// (check) touch output file on successful check
/// (hash) write hash(es) to output file
output: Option<PathBuf>, output: Option<PathBuf>,
#[argp(switch, short = 'q')] #[argp(switch, short = 'q')]
/// only print failures and a summary /// only print failures and a summary
@ -32,17 +34,28 @@ pub struct Args {
const DEFAULT_BUF_SIZE: usize = 8192; const DEFAULT_BUF_SIZE: usize = 8192;
pub fn run(args: Args) -> Result<()> { pub fn run(args: Args) -> Result<()> {
for path in process_rsp(&args.files)? { if args.check {
let mut file = open_file(&path)?; for path in process_rsp(&args.files)? {
if args.check { let file = open_file(&path)?;
check(&args, &mut BufReader::new(file))? check(&args, &mut BufReader::new(file))?;
} else { }
hash(&mut file, &path)? if let Some(out_path) = &args.output {
touch(out_path)
.with_context(|| format!("Failed to touch output file '{}'", out_path.display()))?;
}
} else {
let mut w: Box<dyn Write> =
if let Some(out_path) = &args.output {
Box::new(buf_writer(out_path).with_context(|| {
format!("Failed to open output file '{}'", out_path.display())
})?)
} else {
Box::new(stdout())
};
for path in process_rsp(&args.files)? {
let mut file = open_file(&path)?;
hash(w.as_mut(), &mut file, &path)?
} }
}
if let Some(out_path) = args.output {
touch(&out_path)
.with_context(|| format!("Failed to touch output file '{}'", out_path.display()))?;
} }
Ok(()) Ok(())
} }
@ -98,13 +111,16 @@ where R: BufRead + ?Sized {
Ok(()) Ok(())
} }
fn hash<R>(reader: &mut R, path: &Path) -> Result<()> fn hash<R, W>(w: &mut W, reader: &mut R, path: &Path) -> Result<()>
where R: Read + ?Sized { where
R: Read + ?Sized,
W: Write + ?Sized,
{
let hash = file_sha1(reader)?; let hash = file_sha1(reader)?;
let mut hash_buf = [0u8; 40]; let mut hash_buf = [0u8; 40];
let hash_str = base16ct::lower::encode_str(&hash, &mut hash_buf) let hash_str = base16ct::lower::encode_str(&hash, &mut hash_buf)
.map_err(|e| anyhow!("Failed to encode hash: {e}"))?; .map_err(|e| anyhow!("Failed to encode hash: {e}"))?;
println!("{} {}", hash_str, path.display()); writeln!(w, "{} {}", hash_str, path.to_slash_lossy())?;
Ok(()) Ok(())
} }