kpy 0.1.0-alpha

A reimplentation of linux's cp in rust
Documentation
use std::collections::HashMap;
use std::error::Error;
use std::fs::create_dir_all;
use std::io;
use std::io::ErrorKind;
use std::path::Path;

use clap::ArgMatches;
use log::debug;
use strum::AsStaticRef;
use walkdir::WalkDir;

use crate::app::{AppOpt, OptNames};
use crate::error::BaseError;
use crate::strats::build_destination::{BuildDestinationStratTrait, NoopDestinationStrat};
use crate::strats::copy::{CopyStratTrait, FileCopyStrat};
use crate::strats::prep_destination::{CreateParentDirStrat, PrepDestinationStratTrait};

/// The core that initiates the process of handling arguments and starting the [`Copier`]
pub fn handle_args(
    matches: &ArgMatches,
    opts: &HashMap<OptNames, AppOpt>,
) -> Result<(), Box<dyn Error>> {
    let paths: Vec<&str> = matches
        .values_of(OptNames::Paths.as_static())
        .ok_or("No paths?")?
        .collect();
    let (destination, sources) = paths
        .split_last()
        .ok_or("Couldn't split sources from destination")?;

    let copier = Copier::new(matches, opts);
    let destination_path = Path::new(destination);

    for source in sources.iter() {
        let source_path = Path::new(source);
        copier.run(source_path, destination_path)?;
    }
    Ok(())
}

/// Takes over the process of calling the [`crate::strats`] that will do the actual copying
///  of the actual arguments provided to it.
pub struct Copier {
    pub copy_strategy: Box<dyn CopyStratTrait>,
    pub pre_destination_strategy: Box<dyn PrepDestinationStratTrait>,
    pub build_destination_strategy: Box<dyn BuildDestinationStratTrait>,
}

impl Copier {
    pub fn new(matches: &ArgMatches, opts: &HashMap<OptNames, AppOpt>) -> Self {
        let mut copier = Copier {
            copy_strategy: Box::new(FileCopyStrat {}),
            pre_destination_strategy: Box::new(CreateParentDirStrat {}),
            build_destination_strategy: Box::new(NoopDestinationStrat {}),
        };
        for (opt_name, app_opt) in opts.iter() {
            let opt_str = opt_name.as_static();

            debug!("Checking for {} option", opt_str);
            if matches.is_present(opt_str) {
                (app_opt.copier_modifier)(&mut copier, matches.value_of(opt_str));
                debug!("Applied option {}", opt_name.as_static())
            }
        }
        copier
    }
    pub fn run(&self, source: &Path, destination: &Path) -> Result<(), Box<dyn Error>> {
        if !source.exists() {
            throw!("Source must exist")
        } else if source.is_file() {
            let dest_buf = self.build_destination_strategy.build(source, destination)?;
            let actual_destination = dest_buf.as_path();
            self.pre_destination_strategy.prep(actual_destination)?;
            self.copy_strategy.run(source, actual_destination)?;
        } else if source.is_dir() {
            let dest_root = self.build_destination_strategy.build(source, destination)?;
            for entry in WalkDir::new(source)
                .into_iter()
                .into_iter()
                .filter_map(|e| e.ok())
            {
                let entry_path = entry.path();
                let rel_source = entry_path.strip_prefix(source).map_err(|e| {
                    io::Error::new(ErrorKind::Other, format!("Could not strip prefix: {:?}", e))
                })?;
                let entry_destination = dest_root.join(rel_source);

                if entry_path.is_file() {
                    self.copy_strategy
                        .run(entry_path, entry_destination.as_path())?;
                } else {
                    create_dir_all(entry_destination)?;
                }
            }
        } else {
            throw_fmt!("Cannot handle source {}", source.display())
        }
        Ok(())
    }
}