handlebars_magic/
lib.rs

1mod cli;
2
3use std::{
4    collections::VecDeque,
5    fs::{self, OpenOptions},
6    process::Command,
7};
8
9use anyhow::{anyhow, Result};
10use clap::Parser;
11use handlebars::{
12    handlebars_helper, Context, Handlebars, Helper, HelperResult, JsonRender, Output,
13    RenderContext, RenderErrorReason,
14};
15use log::info;
16
17use cli::Cli;
18
19fn render(
20    h: &Helper,
21    hbs: &Handlebars,
22    context: &Context,
23    _: &mut RenderContext,
24    out: &mut dyn Output,
25) -> HelperResult {
26    let param = h.param(0).ok_or_else(|| {
27        RenderErrorReason::ParamNotFoundForIndex("Param 0 is required for format helper.", 0)
28    })?;
29    let rendered = hbs
30        .render_template(param.value().render().as_str(), &context.data())
31        .map_err(|_err| RenderErrorReason::Other("Cannot render template".into()))?;
32    out.write(rendered.as_ref())?;
33    Ok(())
34}
35
36fn exec(
37    h: &Helper,
38    _hbs: &Handlebars,
39    _context: &Context,
40    _: &mut RenderContext,
41    out: &mut dyn Output,
42) -> HelperResult {
43    let exe = h
44        .param(0)
45        .ok_or_else(|| {
46            RenderErrorReason::ParamNotFoundForIndex("Param 0 is required for format helper.", 0)
47        })?
48        .value()
49        .render();
50
51    let cmd: Vec<&str> = exe.split(' ').collect();
52
53    if let Some((exe, args)) = cmd.split_first() {
54        let output = Command::new(exe).args(args).output()?;
55        if output.status.success() {
56            out.write(&String::from_utf8_lossy(&output.stdout))
57                .map_err(RenderErrorReason::from)
58        } else {
59            Err(RenderErrorReason::Other(format!(
60                "Cannot run '{}': {}",
61                exe,
62                String::from_utf8_lossy(&output.stderr)
63            )))
64        }
65    } else {
66        Err(RenderErrorReason::Other("Cannot render template".into()))
67    }
68    .map_err(From::from)
69}
70
71pub fn process() -> Result<()> {
72    env_logger::init();
73
74    let cli = Cli::parse();
75
76    if !cli.input.is_dir() {
77        return Err(anyhow!(
78            "Input must be an existing directory: {}",
79            cli.input.to_string_lossy()
80        ));
81    }
82
83    fs::create_dir_all(&cli.output)?;
84
85    let mut dirs = VecDeque::new();
86    dirs.push_back(cli.input.clone());
87
88    let mut handlebars = handlebars_misc_helpers::new_hbs();
89
90    handlebars_helper!(from: |f: str, c: str| {
91        if let Some(pos) = c.find(f) {
92            &c[pos..]
93        } else {
94            c
95        }
96    });
97    handlebars.register_helper("from", Box::new(from));
98    handlebars_helper!(to: |f: str, c: str| {
99        if let Some(pos) = c.find(f) {
100            &c[..pos]
101        } else {
102            c
103        }
104    });
105    handlebars.register_helper("to", Box::new(to));
106
107    handlebars.register_helper("render", Box::new(render));
108
109    handlebars_helper!(codeblock: |codeblock_type: str, block: str| {
110        format!("```{}\n{}\n```", codeblock_type, block.trim())
111    });
112    handlebars.register_helper("codeblock", Box::new(codeblock));
113
114    handlebars.register_helper("exec", Box::new(exec));
115
116    while !dirs.is_empty() {
117        let dir = dirs.pop_front().unwrap();
118        for entry in dir.read_dir()?.flatten() {
119            let path = entry.path();
120            let suffix = path.strip_prefix(&cli.input)?;
121            let target = cli.output.join(suffix);
122            if path.is_dir() {
123                dirs.push_back(path);
124                fs::create_dir_all(target)?;
125            } else {
126                info!("{:?} -> {:?}", path, target);
127                let output = OpenOptions::new()
128                    .create(true)
129                    .write(true)
130                    .truncate(true)
131                    .open(target)?;
132                handlebars.render_template_to_write(&fs::read_to_string(path)?, &(), output)?;
133            }
134        }
135    }
136
137    Ok(())
138}