anesis 0.12.5

CLI for scaffolding projects from remote templates and extending them with project addons
Documentation
use std::{collections::HashMap, fs, path::Path};

use anyhow::{Context, Result, anyhow};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct StackManifest {
  pub schema_version: String,
  pub id: String,
  pub name: String,
  #[serde(default)]
  pub description: String,
  pub template: String,
  #[serde(default)]
  pub addons: Vec<StackAddon>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct StackAddon {
  pub id: String,
  #[serde(default = "default_command")]
  pub command: String,

  #[serde(default)]
  pub inputs: HashMap<String, String>,
}

fn default_command() -> String {
  "install".to_string()
}

pub fn load_stack(path: &Path) -> Result<StackManifest> {
  let file = if path.is_dir() {
    path.join("anesis.stack.json")
  } else {
    path.to_path_buf()
  };
  let raw = fs::read_to_string(&file)
    .with_context(|| format!("could not read stack manifest at {}", file.display()))?;
  let stack: StackManifest = serde_json::from_str(&raw)
    .with_context(|| format!("invalid stack manifest {}", file.display()))?;
  validate(&stack)?;
  Ok(stack)
}

pub fn validate(stack: &StackManifest) -> Result<()> {
  if stack.template.trim().is_empty() {
    return Err(anyhow!("stack '{}' declares no template", stack.id));
  }
  for (i, addon) in stack.addons.iter().enumerate() {
    if addon.id.trim().is_empty() {
      return Err(anyhow!(
        "stack '{}': addon #{} has an empty id",
        stack.id,
        i + 1
      ));
    }
  }
  Ok(())
}