kpy 0.1.0-alpha

A reimplentation of linux's cp in rust
Documentation
use std::path::{Path, PathBuf};

use crate::error::BaseError;

/// A strategy that helps construct the destination from the source
pub trait BuildDestinationStratTrait {
    fn build(&self, source: &Path, dest_root: &Path) -> Result<PathBuf, Box<BaseError>>;
}

/// The default strat to keep the destination as it is
pub struct NoopDestinationStrat {}

impl BuildDestinationStratTrait for NoopDestinationStrat {
    fn build(&self, _source: &Path, dest_root: &Path) -> Result<PathBuf, Box<BaseError>> {
        Ok(dest_root.to_path_buf())
    }
}

/// A strat to strip a given number of beginning components from the source's parents
///  to then tack the remaining parents onto the destination
///
/// See [`PrefixStrippedCountParentStrat::build()`] for examples
pub struct PrefixStrippedCountParentStrat {
    pub count: usize,
}

impl BuildDestinationStratTrait for PrefixStrippedCountParentStrat {
    /// Tries to build a new path without the given number of components in the prefix
    ///
    /// It works with absolute paths
    /// ```
    /// use kpy::strats::build_destination::*;
    /// use std::path::Path;
    ///
    /// let strat = PrefixStrippedCountParentStrat{count: 3};
    /// let res = strat.build(Path::new("/1/2/3/a/b/c/d"), Path::new("/"));
    ///
    /// assert!(res.is_ok());
    /// assert_eq!(res.unwrap(), Path::new("/a/b/c/d").to_path_buf());
    /// ```
    ///
    /// and relative paths
    /// ```
    /// # use kpy::strats::build_destination::*;
    /// # use std::path::Path;
    /// #
    /// # let strat = PrefixStrippedCountParentStrat{count: 3};
    /// let res = strat.build(Path::new("1/2/3/a/b/c/d"), Path::new("/"));
    ///
    /// assert!(res.is_ok());
    /// assert_eq!(res.unwrap(), Path::new("/a/b/c/d").to_path_buf());
    /// ```
    fn build(&self, source: &Path, dest_root: &Path) -> Result<PathBuf, Box<BaseError>> {
        // The root is counted as 1 component, which can be unexpected, so we have to skip it
        let count = if source.has_root() {
            self.count + 1
        } else {
            self.count
        };
        let final_destination = source
            .components()
            .skip(count)
            .fold(dest_root.to_path_buf(), |acc, component| {
                acc.join(Path::new(Path::new(component.as_os_str())))
            });
        if final_destination.as_path() == dest_root {
            throw_fmt!(
                "Can't strip {} components from {}",
                self.count,
                source.display()
            )
        }
        Ok(final_destination)
    }
}