handlematters 0.2.1

Self-contained template system with Handlebars and inline shell scripts
Documentation
use std::{
    io::Write,
    process::{Command, Stdio},
};

use anyhow::{Context as _, Result};
use indoc::formatdoc;
use serde_yaml::Value;

use crate::chomp;

pub fn evaluate_context(ctx: Value) -> Result<Value> {
    match ctx {
        Value::String(s) => {
            let output = run_script(&s)?;

            Ok(Value::String(chomp(&output).to_string()))
        }
        Value::Sequence(seq) => {
            let seq = seq.into_iter().map(evaluate_context).collect::<Result<_>>()?;

            Ok(Value::Sequence(seq))
        }
        Value::Mapping(map) => {
            let map = map.into_iter().map(|(k, v)| Ok((k, evaluate_context(v)?))).collect::<Result<_>>()?;

            Ok(Value::Mapping(map))
        }
        v => Ok(v),
    }
}

fn run_script(script: &str) -> Result<String> {
    let mut shell = Command::new("sh")
        .args(["-s", "-e"])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .with_context(|| "failed to start shell: sh")?;

    shell.stdin.as_mut().unwrap().write_all(script.as_bytes())?;

    let output = shell.wait_with_output()?;
    let [stdout, stderr] = [output.stdout, output.stderr].map(|io| String::from_utf8_lossy(&io).to_string());

    output.status.exit_ok().with_context(|| {
        formatdoc! {"
            failed to execute script

            ---- script ----
            {}

            ---- stdout ----
            {}

            ---- stderr ----
            {}", chomp(script), chomp(&stdout), chomp(&stderr)
        }
    })?;

    eprint!("{stderr}");

    Ok(stdout)
}