dsc 0.1.3

dsc is a cli tool for finding and removing duplicate files on one or multiple file systems, while respecting your gitignore rules.
use clap::{crate_version, App, AppSettings, Arg, SubCommand};

pub fn build_app() -> App<'static, 'static> {
    let debug_arg = Arg::with_name("debug")
        .long("debug")
        .short("d")
        .takes_value(false)
        .hidden(true);

    let quiet_arg = Arg::with_name("quiet")
        .long("quiet")
        .short("q")
        .takes_value(false)
        .long_help("Hide the progress bar and other output from stderr (defaults to true if filenames are piped in)");

    let force_arg = Arg::with_name("force")
        .long("force")
        .short("f")
        .takes_value(false)
        .long_help("Don't prompt before removing and linking files");

    let dry_run_arg = Arg::with_name("dry-run")
        .long("dry-run")
        .takes_value(false)
        .help("Don't actually remove and link files, but show what would be done");

    let prefer_arg = Arg::with_name("prefer")
        .long("prefer")
        .takes_value(true)
        .number_of_values(1)
        .possible_values(&["oldest", "newest"])
        .help(
            "When selecting a target for linking prefer keeping the given target (default oldest)",
        );

    let estimate_arg = Arg::with_name("estimate")
        .long("estimate")
        .takes_value(false)
        .long_help("Only perform an sampling analysis to determine duplicates");

    let output_format_arg = Arg::with_name("output-format")
        .long("output-format")
        .takes_value(true)
        .possible_values(&["json", "csv"])
        .long_help("Only perform an sampling analysis to determine duplicates");

    let no_sampling_arg = Arg::with_name("no-sampling")
        .long("no-sampling")
        .takes_value(false)
        .help("Replace the first sampling analysis with a complete read analysis");

    let fail_on_duplicate_arg = Arg::with_name("error-on-duplicate")
        .long("error-on-duplicate")
        .short("E")
        .takes_value(false)
        .long_help("Exits with exit code 2 if any duplicate is found");

    let threads_arg = Arg::with_name("threads")
        .long("threads")
        .short("j")
        .takes_value(true)
        .value_name("num")
        .hidden_short_help(true)
        .long_help(
            "The number of threads to use for reading files (default: number \
                         of available CPU cores * 2)",
        );

    let read_buffer_arg = Arg::with_name("read-buffer-size")
        .long("buffer-size")
        .hidden(true)
        .takes_value(true)
        .value_name("bytes")
        .number_of_values(1)
        .help("The size of the read buffer")
        .long_help(
            "Change the size of the read buffer to align with the block size of your file system. \
                        Can currently only be set globally, and defaults to 16KiB. \n
                        Examples:\n\
                            --buffer-size 10MiB\n\
                            --buffer-size 4096b",
        );

    let start_block_size_arg = Arg::with_name("start-block-size")
        .long("start-size")
        .hidden(true)
        .takes_value(true)
        .value_name("bytes")
        .number_of_values(1)
        .help("The initial amount of bytes to read before comparing files");

    let path_arg = Arg::with_name("path")
        .multiple(true)
        .help("The root directory for the filesystem search (optional)")
        .long_help(
            "The directory where the filesystem search is rooted (optional). If \
                         omitted, search the current working directory.",
        );

    let min_size_arg = Arg::with_name("min-size")
        .long("min-size")
        .takes_value(true)
        .value_name("bytes")
        .number_of_values(1)
        .help("The minimum size of files to consider for comparison (inclusive)")
        .long_help(
            "Set the minimum size of the files to consider for comparison.\n\
                       Currently all files, including empty ones, are scanned which may yield\n\
                       too many results. By setting a minimum the search space can be reduced.\n\n\
                       Examples:\n    \
                            --min-size 1B\n    \
                            --min-size 100MB",
        );

    let max_size_arg = Arg::with_name("max-size")
        .long("max-size")
        .takes_value(true)
        .value_name("bytes")
        .number_of_values(1)
        .help("The maximum size of files to consider for comparison (inclusive)")
        .long_help(
            "Set the maximum size of the files to consider for comparison.\n\
                       Examples:\n    \
                            --max-size 1B\n    \
                            --max-size 100MB",
        );

    let shared_args = vec![
        debug_arg,
        quiet_arg,
        threads_arg,
        read_buffer_arg,
        start_block_size_arg,
        path_arg,
        min_size_arg,
        max_size_arg,
        no_sampling_arg,
    ];

    let cmp = SubCommand::with_name("cmp")
        .about("Compare files and summarize the amount of duplicate data between the provided locations")
        .usage("dsc cmp [FLAGS/OPTIONS] [<path>...]")
        .setting(AppSettings::ColoredHelp)
        .setting(AppSettings::DeriveDisplayOrder)
        .args(&shared_args)
        .arg(fail_on_duplicate_arg)
        .arg(estimate_arg);

    let link = SubCommand::with_name("link")
        .about("Remove duplicate files on the same device by replacing duplicates with hard links")
        .usage("dsc link [FLAGS/OPTIONS] [<path>...]")
        .setting(AppSettings::ColoredHelp)
        .setting(AppSettings::DeriveDisplayOrder)
        .args(&shared_args)
        .arg(force_arg)
        .arg(prefer_arg)
        .arg(dry_run_arg);

    let report = SubCommand::with_name("report")
        .about("Output the duplicates in a machine readable format")
        .usage("dsc report [FLAGS/OPTIONS] [<path>...]")
        .setting(AppSettings::ColoredHelp)
        .setting(AppSettings::DeriveDisplayOrder)
        .args(&shared_args)
        .arg(output_format_arg);

    App::new("dsc")
        .subcommands(vec![cmp, report, link])
        .version(crate_version!())
        .usage("dsc <subcommand> [FLAGS/OPTIONS] [<path>...]")
        .setting(AppSettings::ColoredHelp)
        .setting(AppSettings::DeriveDisplayOrder)
        .after_help(
            "Note: `dsc <subcommand> -h` prints a short and concise overview while `dsc <subcommand> --help` gives all \
                 details.",
        )
}