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}