loco 0.16.3

Loco new app generator
Documentation
use std::{
    collections::BTreeMap,
    path::{Path, PathBuf},
    sync::Mutex,
};

use super::Executer;
use crate::{generator, settings::Settings};

pub struct Inmem {
    pub source_path: PathBuf,
    pub file_store: Mutex<BTreeMap<PathBuf, String>>,
    pub template_engine: generator::template::Template,
}

impl Inmem {
    #[must_use]
    pub fn new(source: &Path) -> Self {
        Self::with_template_engine(source, generator::template::Template::default())
    }

    #[must_use]
    pub fn with_template_engine(
        source: &Path,
        template_engine: generator::template::Template,
    ) -> Self {
        Self {
            source_path: source.to_path_buf(),
            file_store: Mutex::new(BTreeMap::default()),
            template_engine,
        }
    }

    pub fn get_file_content(&self, path: &Path) -> Option<String> {
        self.file_store
            .lock()
            .ok()
            .and_then(|store| store.get(path).cloned())
    }
}

impl Executer for Inmem {
    fn copy_file(&self, file_path: &Path) -> super::Result<PathBuf> {
        let file_content = fs_extra::file::read_to_string(self.source_path.join(file_path))?;
        self.file_store
            .lock()
            .unwrap()
            .insert(file_path.to_path_buf(), file_content);
        Ok(file_path.to_path_buf())
    }

    fn create_file(&self, path: &Path, content: String) -> super::Result<PathBuf> {
        self.file_store
            .lock()
            .unwrap()
            .insert(path.to_path_buf(), content);
        Ok(path.to_path_buf())
    }

    fn copy_dir(&self, directory_path: &Path) -> super::Result<()> {
        let directory_content = fs_extra::dir::get_dir_content(directory_path)?;
        for file in directory_content.files {
            let mut store = self.file_store.lock().unwrap();
            store.insert(PathBuf::from(&file), fs_extra::file::read_to_string(file)?);
        }
        Ok(())
    }

    fn copy_template(&self, file_path: &Path, settings: &Settings) -> super::Result<()> {
        let copied_path = self.copy_file(file_path)?;

        if self.template_engine.is_template(&copied_path) {
            let template_content = {
                let store = self.file_store.lock().unwrap();
                store.get(&copied_path).cloned()
            };

            if let Some(content) = template_content {
                let rendered_content = self.template_engine.render(&content, settings)?;
                self.file_store
                    .lock()
                    .unwrap()
                    .insert(file_path.to_path_buf(), rendered_content);
                Ok(())
            } else {
                Err(super::Error::msg("Template content not found"))
            }
        } else {
            Err(super::Error::msg("File is not a template"))
        }
    }

    fn copy_template_dir(&self, _path: &Path, _data: &Settings) -> super::Result<()> {
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use tree_fs::{Tree, TreeBuilder};

    use super::*;

    fn init_in_memory_store() -> (Inmem, Tree) {
        let tree = TreeBuilder::default()
            .drop(true)
            .add("test/foo.txt", "bar")
            .add("test/bar.txt.t", "crate: {{settings.package_name}}")
            .create()
            .expect("Failed to create mock data");
        (Inmem::new(&tree.root), tree)
    }

    #[test]
    fn can_copy_file() {
        let (store, source_dir) = init_in_memory_store();
        let test_file_path = source_dir.root.join("test").join("foo.txt");

        let copied_path = store.copy_file(&test_file_path).unwrap();

        assert_eq!(copied_path, test_file_path);
        assert_eq!(
            store
                .file_store
                .lock()
                .unwrap()
                .get(&test_file_path)
                .unwrap(),
            "bar"
        );
    }

    #[test]
    fn test_copy_directory() {
        let (store, source_dir) = init_in_memory_store();
        let dir_path = source_dir.root.join("test");

        store.copy_dir(&dir_path).unwrap();

        let file1_path = dir_path.join("foo.txt");
        let file2_path = dir_path.join("bar.txt.t");

        assert_eq!(
            store.file_store.lock().unwrap().get(&file1_path).unwrap(),
            "bar"
        );
        assert_eq!(
            store.file_store.lock().unwrap().get(&file2_path).unwrap(),
            "crate: {{settings.package_name}}"
        );
    }

    #[test]
    fn can_copy_template_file() {
        let (store, source_dir) = init_in_memory_store();
        let test_file_path = source_dir.root.join("test").join("bar.txt.t");

        let settings = Settings {
            package_name: "loco-app".to_string(),
            ..Default::default()
        };

        store
            .copy_template(&test_file_path, &settings)
            .expect("copy template");

        assert_eq!(
            store
                .file_store
                .lock()
                .unwrap()
                .get(&test_file_path)
                .unwrap(),
            "crate: loco-app"
        );
    }
}