vscli 1.3.3

A CLI tool to launch vscode projects, which supports devcontainers.
use color_eyre::eyre::{Result, WrapErr, bail};
use std::path::{Path, PathBuf};

#[derive(Debug)]
enum CopyKind {
    Directory(Vec<CopyPlan>),
    File,
}

#[derive(Debug)]
pub(super) struct CopyPlan {
    source: PathBuf,
    destination: PathBuf,
    kind: CopyKind,
}

impl CopyPlan {
    fn build(source: PathBuf, destination: PathBuf) -> Result<Self> {
        if destination.exists() {
            bail!(
                "Refusing to overwrite existing path: {}",
                destination.display()
            );
        }

        let kind = if source.is_dir() {
            CopyKind::Directory(Self::children(&source, &destination)?)
        } else if source.is_file() {
            CopyKind::File
        } else {
            bail!("Unsupported config entry: {}", source.display());
        };

        Ok(Self {
            source,
            destination,
            kind,
        })
    }

    pub(super) fn children(source: &Path, destination: &Path) -> Result<Vec<Self>> {
        std::fs::read_dir(source)
            .wrap_err_with(|| format!("Failed to read source directory: {}", source.display()))?
            .map(|entry| {
                let entry = entry.wrap_err("Failed to read source directory entry")?;
                Self::build(entry.path(), destination.join(entry.file_name()))
            })
            .collect()
    }

    pub(super) fn execute(self) -> Result<()> {
        match self.kind {
            CopyKind::Directory(children) => {
                std::fs::create_dir_all(&self.destination).wrap_err_with(|| {
                    format!(
                        "Failed to create destination directory: {}",
                        self.destination.display()
                    )
                })?;

                for child in children {
                    child.execute()?;
                }
            }
            CopyKind::File => {
                std::fs::copy(&self.source, &self.destination).wrap_err_with(|| {
                    format!(
                        "Failed to copy file from {} to {}",
                        self.source.display(),
                        self.destination.display()
                    )
                })?;
            }
        }

        Ok(())
    }
}