orcs 0.0.8

Microservices monorepo orchestration tool
Documentation
use crate::Error;
use git2::Repository;
use handlebars::Handlebars;
use serde::Serialize;
use std::collections::HashMap;
use std::fs::{create_dir_all, read_dir, File};
use std::io::prelude::*;
use std::path::Path;
use tempfile::tempdir;

/// Template types
pub enum Template {
    /// Git repositories
    Git(String),

    /// Local folders
    Folder(String),

    /// Default template
    Map(HashMap<String, String>),
}

impl Template {
    pub fn from_string(s: &str) -> Self {
        match s {
            s if s.starts_with("git:") => Self::Git(s[4..].to_string()),
            s => Self::Folder(s.to_string()),
        }
    }

    pub fn from_map(map: HashMap<String, String>) -> Self {
        Self::Map(map)
    }

    /// Render a template
    ///
    /// A template could consist of multiple files.
    pub fn render<T>(&self, path: &Path, values: &T) -> Result<(), Error>
    where
        T: Serialize,
    {
        let reg = Handlebars::new();

        match self {
            Self::Git(link) => {
                // Clone repository
                let temp_dir = tempdir()?;
                let folder_path = temp_dir.path();
                Repository::clone(link, folder_path)?;

                // Loop through the folder
                self.visit_path(folder_path, folder_path, &reg, path, values)?;

                // TODO
                Ok(())
            }
            Self::Folder(folder) => {
                let folder_path = Path::new(folder);
                if !folder_path.is_dir() {
                    return Err(Error::RenderTemplateError(
                        "template folder does not exist",
                        folder.clone(),
                    ));
                }

                // Loop through the folder
                self.visit_path(folder_path, folder_path, &reg, path, values)?;

                Ok(())
            }
            Self::Map(files) => {
                for (name, template) in files {
                    let file_path = path.join(name);
                    self.render_file(&reg, file_path.as_path(), template, values)?;
                }
                Ok(())
            }
        }
    }

    /// Render a single file
    fn render_file<T>(
        &self,
        reg: &Handlebars,
        path: &Path,
        template: &str,
        values: &T,
    ) -> Result<(), Error>
    where
        T: Serialize,
    {
        // Create folder if necessary
        if let Some(folder) = path.parent() {
            if !folder.is_dir() {
                create_dir_all(folder)?;
            }
        }

        // Create file
        let file = File::create(path)?;

        // Render template
        reg.render_template_to_write(template, values, file)?;

        Ok(())
    }

    fn visit_path<T>(
        &self,
        root: &Path,
        dest_path: &Path,
        reg: &Handlebars,
        output_path: &Path,
        values: &T,
    ) -> Result<(), Error>
    where
        T: Serialize,
    {
        // Folder, recursively scan
        if dest_path.is_dir() {
            for entry in read_dir(dest_path)? {
                let entry = entry?;
                let entry_path = entry.path();

                // Create folder in destination path
                let relative_path = dest_path.strip_prefix(root)?;
                let entry_output_path = output_path.join(relative_path);
                create_dir_all(entry_output_path)?;

                // Visit the folder
                self.visit_path(root, &entry_path, reg, output_path, values)?;
            }

        // Individual item
        } else {
            // Read template file
            let mut file = File::open(&dest_path)?;
            let mut data = String::new();
            file.read_to_string(&mut data)?;

            // Find path
            let relative_path = dest_path.strip_prefix(root)?;
            let entry_output_path = output_path.join(relative_path);

            // Render file
            self.render_file(reg, &entry_output_path, &data, values)?;
        }

        Ok(())
    }
}