#![doc(html_root_url = "https://docs.rs/elif/0.0.3")]
use std::error::Error;
use std::io::{Read, BufReader};
use std::fs;
use std::path::PathBuf;
use md5;
pub fn file_meta(p: &PathBuf) -> Result<fs::Metadata, Box<dyn Error>> {
let Ok(mf) = fs::metadata(p) else {
return Err(format!("cannot get metadata: {}", p.display()).into())
};
Ok(mf)
}
pub fn md5sum(p: &PathBuf, sz: u64) -> Result<String, Box<dyn Error>> {
let mut t: usize = 0;
let f = fs::File::open(p)?;
let mut rdr = BufReader::new(f);
let mut buf = vec![0u8; 8192]; let mut ctx = md5::Context::new();
loop {
match rdr.read(&mut buf) {
Err(_e) => { break; },
Ok(l) => { ctx.consume(&buf[..l]); t += l; if t >= sz as usize { break; } }
}
}
if t < sz as usize { return Err("can't read all".into()); }
let digest = ctx.compute();
Ok(format!("{:x}", digest))
}
pub fn read_dir_entries(bpath: &str) ->
Result<Vec<fs::DirEntry>, Box<dyn Error>> {
let mut entries: Vec<fs::DirEntry> = Vec::new();
match fs::read_dir(bpath) {
Err(e) => eprintln!("err: {}", e),
Ok(rdir) => for item in rdir.into_iter() { match item {
Err(e) => eprintln!("err: {}", e),
Ok(entry) => entries.push(entry) }
}
}
entries.sort_by(|a, b| a.path().cmp(&b.path()));
entries.sort_by(|a, b| a.path().is_file().cmp(&b.path().is_file()));
Ok(entries)
}
pub fn walk_dir_entries(ignores: &Vec<String>, bpaths: &Vec<&str>, dep: u64) ->
Result<u64, Box<dyn Error>> {
let mut total: u64 = 0;
let depth = String::from_utf8((0..dep).into_iter().map(|_| 0x20).collect())?;
let de = (0..2).into_iter().map(|i| {
let Ok(ent) = read_dir_entries(bpaths[i]) else {
panic!("no dir entries in [{}]", bpaths[i])
};
ent
}).collect::<Vec<_>>();
for pe in &de[0] {
let p = &pe.path();
let mut q = PathBuf::from(""); let mut f = false;
for qe in &de[1] {
let qpb = &qe.path();
if qpb.file_name() == p.file_name() { q = qpb.clone(); f = true; break; }
}
print!("{}", if f {"T"}else{"F"});
print!("{}{}", depth, if p.is_dir() {"+"}else{"-"});
println!("{}", p.display()); if p.is_file() {
let sz = [p, &q].iter().map(|p| {
let Ok(mf) = file_meta(p) else { return 0 };
mf.len()
}).collect::<Vec<_>>();
let digest = [p, &q].iter().enumerate().map(|(i, p)| {
let Ok(s) = md5sum(p, sz[i]) else { return "".to_string() };
s
}).collect::<Vec<_>>();
for i in 0..2 { println!(" {} {}", sz[i], digest[i]); }
total += sz[0];
}
if p.is_dir() {
let e = pe.file_name().to_str().expect("invalid_name").to_string();
if !ignores.contains(&e) {
let s = p.to_str().expect("invalid_path");
let t = q.to_str().expect("invalid_path");
total += walk_dir_entries(ignores, &vec![s, t], dep + 1)?;
}
}
}
Ok(total)
}
#[macro_export]
macro_rules! walk_dir_entries {
($ignores: expr, $bpaths: expr) => {
walk_dir_entries($ignores, $bpaths, 0)
};
($ignores: expr) => {
walk_dir_entries($ignores, vec![".", "."], 0)
};
}
pub fn walker(dirs: Vec<PathBuf>) -> Result<(), Box<dyn Error>> {
let ignores = vec![".git", "target"].into_iter().map(|s|
s.to_string()).collect();
let f = |pbs: &Vec<&str>| {
println!("[{}] - [{}]", pbs[0], pbs[1]);
let t = match walk_dir_entries!(&ignores, pbs) {
Err(e) => { eprintln!("{:?}", e); 0 },
Ok(t) => t
};
println!("total: {}", t);
};
let mut pbs = dirs.iter().map(|p| p.to_str().unwrap()).collect::<Vec<_>>();
f(&pbs);
pbs.reverse();
f(&pbs);
Ok(())
}
pub fn take2<T>(args: T) -> Vec<PathBuf> where T: Iterator<Item = String> {
let mut dirs = Vec::<PathBuf>::new();
for a in args {
let p = PathBuf::from(a);
if !p.exists() { continue; }
if !p.is_dir() { continue; }
dirs.push(p);
}
println!("dirs: {}", dirs.len());
for (i, p) in dirs.iter().enumerate() {
println!("dirs[{}]: {}", i, p.display());
}
if dirs.len() < 2 {
println!("Usage: {} a dir0 dir1 ...", env!("CARGO_PKG_NAME"));
return vec!["src", "src"].into_iter().map(|s| PathBuf::from(s)).collect();
}
dirs
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_walker() {
assert_eq!(walker(take2(std::env::args().skip(1))).unwrap(), ());
}
}