handlebars_magic/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
mod cli;

use std::{
    collections::VecDeque,
    fs::{self, OpenOptions},
    process::Command,
};

use anyhow::{anyhow, Result};
use clap::Parser;
use handlebars::{
    handlebars_helper, Context, Handlebars, Helper, HelperResult, JsonRender, Output,
    RenderContext, RenderErrorReason,
};
use log::info;

use cli::Cli;

fn render(
    h: &Helper,
    hbs: &Handlebars,
    context: &Context,
    _: &mut RenderContext,
    out: &mut dyn Output,
) -> HelperResult {
    let param = h.param(0).ok_or_else(|| {
        RenderErrorReason::ParamNotFoundForIndex("Param 0 is required for format helper.", 0)
    })?;
    let rendered = hbs
        .render_template(param.value().render().as_str(), &context.data())
        .map_err(|_err| RenderErrorReason::Other("Cannot render template".into()))?;
    out.write(rendered.as_ref())?;
    Ok(())
}

fn exec(
    h: &Helper,
    _hbs: &Handlebars,
    _context: &Context,
    _: &mut RenderContext,
    out: &mut dyn Output,
) -> HelperResult {
    let exe = h
        .param(0)
        .ok_or_else(|| {
            RenderErrorReason::ParamNotFoundForIndex("Param 0 is required for format helper.", 0)
        })?
        .value()
        .render();

    let cmd: Vec<&str> = exe.split(' ').collect();

    if let Some((exe, args)) = cmd.split_first() {
        let output = Command::new(exe).args(args).output()?;
        if output.status.success() {
            out.write(&String::from_utf8_lossy(&output.stdout))
                .map_err(RenderErrorReason::from)
        } else {
            Err(RenderErrorReason::Other(format!(
                "Cannot run '{}': {}",
                exe,
                String::from_utf8_lossy(&output.stderr)
            )))
        }
    } else {
        Err(RenderErrorReason::Other("Cannot render template".into()))
    }
    .map_err(From::from)
}

pub fn process() -> Result<()> {
    env_logger::init();

    let cli = Cli::parse();

    if !cli.input.is_dir() {
        return Err(anyhow!(
            "Input must be an existing directory: {}",
            cli.input.to_string_lossy()
        ));
    }

    fs::create_dir_all(&cli.output)?;

    let mut dirs = VecDeque::new();
    dirs.push_back(cli.input.clone());

    let mut handlebars = handlebars_misc_helpers::new_hbs();

    handlebars_helper!(from: |f: str, c: str| {
        if let Some(pos) = c.find(f) {
            &c[pos..]
        } else {
            c
        }
    });
    handlebars.register_helper("from", Box::new(from));
    handlebars_helper!(to: |f: str, c: str| {
        if let Some(pos) = c.find(f) {
            &c[..pos]
        } else {
            c
        }
    });
    handlebars.register_helper("to", Box::new(to));

    handlebars.register_helper("render", Box::new(render));

    handlebars_helper!(codeblock: |codeblock_type: str, block: str| {
        format!("```{}\n{}\n```", codeblock_type, block.trim())
    });
    handlebars.register_helper("codeblock", Box::new(codeblock));

    handlebars.register_helper("exec", Box::new(exec));

    while !dirs.is_empty() {
        let dir = dirs.pop_front().unwrap();
        for entry in dir.read_dir()?.flatten() {
            let path = entry.path();
            let suffix = path.strip_prefix(&cli.input)?;
            let target = cli.output.join(suffix);
            if path.is_dir() {
                dirs.push_back(path);
                fs::create_dir_all(target)?;
            } else {
                info!("{:?} -> {:?}", path, target);
                let output = OpenOptions::new()
                    .create(true)
                    .write(true)
                    .truncate(true)
                    .open(target)?;
                handlebars.render_template_to_write(&fs::read_to_string(path)?, &(), output)?;
            }
        }
    }

    Ok(())
}