extern crate docopt;
extern crate rustc_serialize;
#[macro_use]
extern crate unwrap;
use docopt::Docopt;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::fs::{self, ReadDir};
use std::path::PathBuf;
static USAGE: &'static str = "
Usage:
cargo prune [options]
Options:
--target <path> Custom target directory to search for dependencies.
-v, --version Display the version and exit.
-h, --help Display this help message and exit.
";
const DEFAULT_TARGET: &'static str = "./target";
const ALLOWED_DURATION_SEC: u64 = 2 * 3600;
#[derive(Debug, RustcDecodable)]
struct Args {
flag_target: Option<String>,
flag_version: bool,
flag_help: bool,
}
macro_rules! dir_content_path {
($content_res:expr) => {{
let entry = match $content_res {
Ok(entry) => entry,
Err(e) => {
println!("WARN: Could not evaluate a dir content: {:?}", e);
continue;
}
};
entry.path()
}};
}
fn main() {
let args: Args = Docopt::new(USAGE)
.and_then(|d| d.decode())
.unwrap_or_else(|e| e.exit());
if args.flag_version {
return println!("{}", env!("CARGO_PKG_VERSION"));
}
let target = match args.flag_target {
Some(path) => PathBuf::from(path),
None => PathBuf::from(DEFAULT_TARGET),
};
search_for_deps(&target);
}
fn search_for_deps(path: &PathBuf) {
if !path.is_dir() {
return;
}
let dir = unwrap!(path.read_dir());
if path.ends_with("deps") {
println!("* Processing {:?}", path);
prune(dir);
} else {
for content in dir {
search_for_deps(&dir_content_path!(content));
}
}
}
fn prune(dir: ReadDir) {
let mut libs = HashMap::<String, Vec<PathBuf>>::with_capacity(100);
for content in dir {
let path = dir_content_path!(content);
let lib = match path.file_stem() {
Some(stem) => match stem.to_str() {
Some(stem) => {
let splits: Vec<_> = stem.rsplitn(2, '-').collect();
if splits.len() != 2 {
continue;
}
splits[1].to_string()
}
None => continue,
},
None => continue,
};
let lib_paths = libs.entry(lib).or_insert_with(|| Vec::with_capacity(2));
lib_paths.push(path);
lib_paths.sort_by(|a, b| {
if unwrap!(unwrap!(a.metadata()).modified()) < unwrap!(unwrap!(b.metadata()).modified())
{
Ordering::Less
} else {
Ordering::Greater
}
});
}
for (lib, mut lib_paths) in libs {
if lib_paths.len() < 2 {
println!(" No duplicates for {:?}", lib);
continue;
}
println!(" Pruning for lib {:?}", lib);
let latest = unwrap!(unwrap!(unwrap!(lib_paths.last()).metadata()).modified());
let rm = ALLOWED_DURATION_SEC == 0
|| lib_paths.iter().any(|lib_path| {
let earlier = unwrap!(unwrap!(lib_path.metadata()).modified());
match latest.duration_since(earlier) {
Ok(dur) => dur.as_secs() > ALLOWED_DURATION_SEC,
Err(_) => false, }
});
if rm {
if ALLOWED_DURATION_SEC == 0 {
let _ = lib_paths.pop();
}
lib_paths.iter().for_each(|lib_path| {
print!(" Deleting {:?} ... ", lib_path);
if let Err(e) = fs::remove_file(lib_path) {
println!("ERROR: {:?}", e);
} else {
println!("ok");
}
});
} else {
print!(
" NOT Deleting any duplicates for {:?} as difference with duplicate is <
{}secs... ",
lib, ALLOWED_DURATION_SEC
);
}
}
}