Skip to content

Commit

Permalink
use clap derive
Browse files Browse the repository at this point in the history
  • Loading branch information
edwardgeorge committed Dec 2, 2024
1 parent 999af18 commit 4db0419
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 67 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = "2018"

[dependencies]
blake3 = { version = "1.5.4", features = ["mmap", "rayon"] }
clap = { version = "4.5.21", features = ["cargo"] }
clap = { version = "4.5.21", features = ["cargo", "derive"] }
rayon = "1.10.0"
thiserror = "2.0.3"
walkdir = "2.5.0"
92 changes: 27 additions & 65 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,47 @@
use blake3::{Hash, Hasher};
use clap::{arg, command, value_parser, Arg};
use clap::{arg, ArgAction, Parser, ValueEnum};
use rayon::prelude::*;
use std::collections::HashMap;

use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use std::vec::Vec;

mod sorting;
mod types;

use sorting::{SortKeys, SortOptions};
use sorting::SortOptions;
use types::{Error, FileInfo};

#[derive(Debug, Clone)]
use ArgAction::SetTrue;

#[derive(Debug, Clone, Default, ValueEnum)]
enum DeleteOptions {
#[default]
KeepAll,
//DeleteDuplicates,
//DryRun,
//Prompt,
}

#[derive(Debug, Clone, Parser)]
struct Options {
#[arg(short = 'r', long, action = SetTrue, default_value_t = false)]
recurse: bool,
#[arg(short = 'f', long = "follow", action = SetTrue, default_value_t = false)]
follow_symlinks: bool,
#[arg(long = "min-size", default_value_t = 0, value_name = "BYTES")]
min_size: u64,
#[arg(long = "max-depth", value_name = "DEPTH")]
max_depth: Option<u64>,
#[clap(flatten)]
sort_options: SortOptions,
#[arg(long = "delete", value_enum, default_value_t)]
delete: DeleteOptions,
#[arg(value_name = "DIRECTORY", required = true)]
paths: Vec<PathBuf>,
}

fn find_same_sized_files<I>(
Expand Down Expand Up @@ -100,11 +119,7 @@ fn find_duplicates<'a>(
Ok(r)
}

fn run<I, J>(dirs: I, options: &Options) -> Result<(), Error>
where
I: IntoIterator<Item = J>,
J: AsRef<Path>,
{
fn run(options: &Options) -> Result<(), Error> {
let num_hashes = Arc::new(AtomicUsize::new(0));
let num_duplicates = Arc::new(AtomicUsize::new(0));
let num_groups = Arc::new(AtomicUsize::new(0));
Expand All @@ -119,7 +134,7 @@ where
let mut seen_counter = 0;
let mut files_counter = 0;
let mut skipped_counter = 0;
for dir in dirs {
for dir in options.paths.iter() {
let mut iter = walkdir::WalkDir::new(dir);
if let Some(d) = depth {
iter = iter.max_depth(d as usize + 1);
Expand Down Expand Up @@ -185,61 +200,8 @@ where
}

fn main() {
let matches = command!()
.arg(
arg!(recursive: -r "recurse into directories"),
)
.arg(
arg!(follow: -f --follow "follow symlinks"),
)
.arg(
arg!(--"min-size" <BYTES> "minimum size of files (in bytes) to find duplicates for")
.value_parser(value_parser!(u64)),
)
.arg(
arg!(--"max-depth" <DEPTH> "maximum depth to recurse (0 is no recursion). implies -r.")
.value_parser(value_parser!(u64)),
)
.arg(
arg!(--"sort-by" <PROPS> "properties to sort by, comma-separated. depth,mtime,path")
.value_parser(SortKeys::from_str),
)
.arg(
arg!(--"prefer-within" <PATH> "prefer files within this path")
.value_parser(value_parser!(PathBuf)),
)
.arg(
Arg::new("directory")
.required(true)
.num_args(1..)
.value_parser(value_parser!(PathBuf)),
)
.get_matches();
let dirs: Vec<&PathBuf> = matches.get_many("directory").unwrap().collect();
let recurse = matches.get_flag("recursive") || matches.contains_id("max-depth");
let follow_symlinks = matches.get_flag("follow");
let min_size: u64 = matches.get_one("min-size").copied().unwrap_or(1);
let max_depth = matches.get_one::<u64>("max-depth").copied();
let prefer_location = matches.get_one::<PathBuf>("prefer-within").map(|p| p.canonicalize().expect("could not canonicalize path"));
let sort_by = matches
.get_one::<SortKeys>("sort-opts")
.cloned()
.unwrap_or_else(SortKeys::default);
let sort_options = SortOptions {
prefer_location,
sort_by,
};
let result = run(
dirs,
&Options {
recurse,
follow_symlinks,
min_size,
max_depth,
sort_options,
},
);
if let Err(e) = result {
let opts = Options::parse();
if let Err(e) = run(&opts) {
eprintln!("{}", e);
std::process::exit(1);
}
Expand Down
34 changes: 33 additions & 1 deletion src/sorting.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::types::{Error, FileInfo};
use clap::Parser;
use std::cmp::Ordering;
use std::collections::HashSet;
use std::fmt::Display;
use std::hash::Hash;
use std::path::{Path, PathBuf};

Expand Down Expand Up @@ -45,6 +47,20 @@ impl std::str::FromStr for SortBy {
}
}

impl Display for SortBy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
SortBy::Depth => "depth",
SortBy::ModificationTime => "mtime",
SortBy::PathParts => "path",
}
)
}
}

#[derive(Debug, Clone)]
pub struct SortKeys {
keys: Vec<SortBy>,
Expand Down Expand Up @@ -84,6 +100,20 @@ impl std::str::FromStr for SortKeys {
}
}

impl Display for SortKeys {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.keys
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join(",")
)
}
}

impl SortKeys {
#[inline]
pub fn cmp_for_fileinfos(&self, left: &FileInfo, right: &FileInfo) -> Ordering {
Expand All @@ -97,9 +127,11 @@ impl SortKeys {
}
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Parser)]
pub struct SortOptions {
#[arg(long = "prefer-within", value_name = "PATH")]
pub prefer_location: Option<PathBuf>,
#[arg(long = "sort-by", value_name = "PROPS", default_value_t)]
pub sort_by: SortKeys,
}

Expand Down

0 comments on commit 4db0419

Please sign in to comment.