nedots 0.1.2

A tool to manage configuration files/(ne)dots.
use std::path::Path;

#[derive(Debug, clap::Args)]
pub(crate) struct GatherCmd {
    /// Only gather this source. Any unique portion of a path in `sources` is
    /// valid. E.g. given a list of [ "/home/user/.bashrc", "/home/user/.zshrc" ],
    /// ".bashrc" or ".zshrc" may be used as a key.
    key: Option<String>,
}

impl super::Command for GatherCmd {
    fn run(
        &self,
        _: &super::nedots::RootCmd,
        config: &crate::config::Config,
    ) -> Result<(), super::CommandError> {
        let pb = indicatif::ProgressBar::new_spinner();
        if let Some(key) = &self.key {
            match config.get_sources_as_hashmap().get(key.as_str()) {
                Some(val) => {
                    let src = Path::new(val);
                    let dst = config.get_dst_from_src(src)?;

                    pb.tick();
                    pb.set_message(format!("Gathering {}...", val.to_string_lossy()));
                    gather(src, &dst)?;
                }
                None => {
                    log::error!("{} not found", key);
                }
            }
        } else {
            for source in &config.sources {
                // Joining absolute paths doesn't work - `dots_dir` is replaced with
                // `src`. We have to strip the prefix, resulting in a relative path
                // being joined instead.
                let src = Path::new(source);
                let dst = config.get_dst_from_src(src)?;

                pb.tick();
                pb.set_message(format!("Gathering {}...", source.to_string_lossy()));
                gather(src, &dst)?;
            }
        }

        pb.finish_and_clear();
        Ok(())
    }
}

pub(crate) fn gather(src: &Path, dst: &Path) -> Result<(), super::CommandError> {
    log::debug!(
        "Gathering {} -> {}",
        src.to_string_lossy(),
        dst.to_string_lossy()
    );

    if src.is_dir() {
        // When given a directory as `src`, we've been asked to copy the
        // contents of a directory into the `dst` path - we want to create
        // the same directory structure as defined in `src`, so we call `gather`
        // once again, this time with the `src` directory name appended to
        // `dst`.
        for entry in src.read_dir()? {
            let path = entry?.path();
            gather(&path, &dst.join(&path.file_name().unwrap()))?;
        }
    } else {
        // When we have a file as `src`, we'll quickly sanity check that the
        // file has a file_name. We're going to panic here because this should
        // never happen.
        if let None = src.file_name() {
            log::error!(
                "{} is not a directory, and has no file_name!",
                src.to_string_lossy()
            );
            panic!()
        };

        let parent = dst.parent().unwrap();
        if !parent.exists() {
            std::fs::create_dir_all(parent)?;
        }

        if let Err(err) = std::fs::copy(src, dst) {
            log::warn!("{}", err);
            log::warn!("{} => {}", src.to_string_lossy(), dst.to_string_lossy());
        }
    }

    Ok(())
}