kpy 0.1.0-alpha

A reimplentation of linux's cp in rust
Documentation
use std::collections::HashMap;
use std::str::FromStr;

use clap::{crate_authors, crate_version, App, Arg};
use strum::AsStaticRef;
use strum_macros::AsStaticStr;

use crate::copier::Copier;
use crate::strats::build_destination::PrefixStrippedCountParentStrat;
use crate::strats::copy::{FileHardlinkStrat, FileSymlinkStrat};

#[derive(AsStaticStr, Eq, PartialOrd, PartialEq, Hash)]
pub enum OptNames {
    #[strum(serialize = "link")]
    Link,
    #[strum(serialize = "paths")]
    Paths,
    #[strum(serialize = "parents-stripped")]
    ParentsStripped,
    #[strum(serialize = "symbolic-link")]
    Symbolic,
}

/// A container for application flags and options that will modify how the [`crate::copier::Copier`] works.
/// This is only necessary for those that actually do change the Copier's behavior
pub struct AppOpt<'a> {
    pub arg: Arg<'a, 'a>,

    /// The closure that will modify the [`crate::copier::Copier`]
    ///
    /// The second arg is the value of the option.
    /// Flags, of course, don't have a value.
    pub copier_modifier: Box<dyn Fn(&mut Copier, Option<&str>)>,
}

fn validate_positive_number(value: String) -> Result<(), String> {
    let result = usize::from_str(value.as_str());
    if result.is_ok() {
        Ok(())
    } else {
        Err(String::from("Must be a positive number"))
    }
}

/// Builds a hashmap of flags and options that will modify the [`crate::copier::Copier`]
///
/// Pretty much a helper in order to iterate over all of them
pub fn get_opts() -> HashMap<OptNames, AppOpt<'static>> {
    let mut hashmap = HashMap::new();
    hashmap.insert(
        OptNames::Link,
        AppOpt {
            arg: Arg::with_name(OptNames::Link.as_static())
                .long(OptNames::Link.as_static())
                .short("l")
                .help("Make hard links instead of copying"),
            copier_modifier: Box::new(|copier, _| {
                copier.copy_strategy = Box::new(FileHardlinkStrat {});
            }),
        },
    );
    hashmap.insert(
        OptNames::Symbolic,
        AppOpt {
            arg: Arg::with_name(OptNames::Symbolic.as_static())
                .long(OptNames::Symbolic.as_static())
                .short("s")
                .help("Make symbolic links instead of copying"),
            copier_modifier: Box::new(|copier, _| {
                copier.copy_strategy = Box::new(FileSymlinkStrat {});
            }),
        },
    );
    hashmap.insert(
        OptNames::ParentsStripped,
        AppOpt {
            arg: Arg::with_name(OptNames::ParentsStripped.as_static())
                .long(OptNames::ParentsStripped.as_static())
                .takes_value(true)
                .validator(validate_positive_number)
                .help(
                    "Remove X components from the parent and tack onto destination (use as prefix)",
                ),
            copier_modifier: Box::new(|copier, value_opt| {
                let count = value_opt
                    .and_then(|value| usize::from_str(value).ok())
                    .expect("Must be a positive number");
                copier.build_destination_strategy =
                    Box::new(PrefixStrippedCountParentStrat { count });
            }),
        },
    );
    hashmap
}

/// Builds the actual [`clap::App`] that will provide the CLI
pub fn get_app<'a, 'b>(opts: &HashMap<OptNames, AppOpt<'a>>) -> App<'a, 'b> {
    let mut app = App::new("kpy")
        .version(crate_version!())
        .author(crate_authors!())
        .about("An alternative to cp")
        .arg(
            Arg::with_name(OptNames::Paths.as_static())
                .help("FILE DIRECTORY | DIRECTORY DIRECTORY | FILE1 FILE2 ... DIRECTORY")
                .required(true)
                .index(1)
                .multiple(true)
                .min_values(2),
        );
    for app_opt in opts.values().into_iter() {
        app = app.arg(app_opt.arg.clone());
    }
    app
}