spawn-cli 0.5.0

A command-line tool for creating files and folders from a template.
mod config;

use anyhow::{Error, Result};
use log::info;
use std::{path::PathBuf, sync::OnceLock};

use crate::config::cache_dir;
use crate::template::config::Config;

const IGNORE_FILENAME: &str = ".spwnignore";
const CONFIG_DIR: &str = ".spwn";
const CONFIG_FILENAME: &str = "config.toml";

pub(crate) struct Template {
    pub uri: String,
    pub hash: String,
    config: OnceLock<Config>,
}

impl Template {
    pub fn from_uri(uri: String) -> Self {
        let hash = create_hash(&uri);
        let config = OnceLock::new();

        Template { uri, hash, config }
    }

    pub fn init(&self) -> Result<&Self> {
        let cache_dir = self.cache_dir()?;

        if cache_dir.is_dir() {
            return Ok(self);
        }

        crate::repo::clone(&self.uri, &cache_dir)?;

        Ok(self)
    }

    pub fn cache_dir(&self) -> Result<PathBuf> {
        let Some(mut cache_dir) = cache_dir() else {
            return Err(Error::msg("No cache directory"));
        };

        cache_dir.push(&self.hash);

        Ok(cache_dir)
    }

    pub fn config_dir(&self) -> Result<PathBuf> {
        let config_dir = self.cache_dir()?.as_path().join(CONFIG_DIR);

        Ok(config_dir)
    }

    pub fn get_ignore(&self) -> Result<Vec<String>> {
        let template_ignore_file = self.cache_dir()?.as_path().join(IGNORE_FILENAME);

        if !template_ignore_file.is_file() {
            return Err(Error::msg("No template ignore file"));
        }

        info!(
            "Using template ignore file {:?}",
            template_ignore_file.display()
        );

        let lines = crate::fs::read_lines(template_ignore_file)?;

        Ok(lines)
    }

    pub fn get_config(&self) -> &Config {
        let config = self.config.get_or_init(|| {
            let config_path = self.config_dir().unwrap().as_path().join(CONFIG_FILENAME);

            if !config_path.is_file() {
                return Config::default();
            }

            info!("Using project config file {config_path:?}");

            let config = Config::try_from_file(&config_path).unwrap();

            config
        });

        config
    }
}

fn create_hash(path: &str) -> String {
    use sha2::{Digest, Sha256};

    let mut hasher = Sha256::new();

    hasher.update(&path);

    let result = hasher.finalize();
    let hash = format!("{:x}", result);

    hash
}