proplate_core/gen/
bootstrap.rs

1use std::{collections::HashMap, fs, path::Path};
2
3use crate::{
4  fs as pfs,
5  template::{config::analyze_dyn_files, interpolation::Interpolate, op::Execute, Template},
6};
7
8use proplate_errors::{ProplateError, ProplateErrorKind, ProplateResult};
9use proplate_tui::logger;
10
11/// typealias for template ctx
12pub type Context = HashMap<String, String>;
13
14/// Processes the given `template` using the `ctx` and outputs the result to `dest` directory
15pub fn bootstrap(template: &mut Template, dest: &str, ctx: &Context) -> ProplateResult<()> {
16  (|| -> ProplateResult<()> {
17    process_template(template, ctx)?;
18    prepare_dest(dest)?;
19    copy_files(template, dest)?;
20    cleanup(template)?;
21    Ok(())
22  })()
23  .map_err(|e| -> ProplateError {
24    if let Err(_) = cleanup(template) {
25      logger::warn("Unable to cleanup");
26    }
27    e
28  })
29}
30
31/// Executes hook and bind ctx onto dynamic_files.
32pub fn process_template(template: &mut Template, ctx: &Context) -> ProplateResult<()> {
33  println!("{}", logger::step("Running additional operations..."));
34
35  // run "additional_operations" in order to process the dynamically
36  // added file in the extra operation.
37  for op in &template.conf.additional_operations {
38    op.execute(&ctx)?;
39  }
40
41  println!(
42    "{}",
43    logger::step("Verifying whether analysis of dyn files is necessary...")
44  );
45  if template.conf.require_dyn_file_analysis {
46    analyze_dyn_files(&mut template.conf, &template.base_path);
47  }
48
49  println!("{}", logger::step("Binding ctx to dynamic_files..."));
50
51  for filepath in &template.conf.dynamic_files {
52    println!("      {}", logger::step(&format!("processing...")));
53    bind_ctx_to_file(Path::new(&filepath), ctx);
54  }
55
56  Ok(())
57}
58
59/// Replaces dynamic var "$var" with their actual value
60pub fn bind_ctx_to_file(path: &Path, ctx: &Context) {
61  match pfs::map_file(path, |s| s.to_string().interpolate(ctx)) {
62    Err(_) => {
63      // TODO: warn if not found but wasn't removed in additional_op either
64    }
65    _ => (),
66  }
67}
68
69/// Create project dest dir
70fn prepare_dest(dest: &str) -> ProplateResult<()> {
71  println!("{}", logger::title("Finalizing"));
72  fs::create_dir_all(dest).map_err(|e| {
73    ProplateError::create(ProplateErrorKind::Fs {
74      concerned_paths: vec![dest.into()],
75      operation: "create_dir_all".into(),
76    })
77    .with_ctx("gen:bootstrap:prepare")
78    .with_cause(&e.to_string())
79  })?;
80  Ok(())
81}
82
83/// Copies template file to the provided dest
84/// Files under "meta.exclude" won't be copied
85pub fn copy_files(template: &Template, dest: &str) -> ProplateResult<()> {
86  let src = &template.base_path;
87  let dest = Path::new(dest);
88
89  println!("{}", logger::step("Copying..."));
90
91  pfs::copy_fdir(
92    src,
93    dest,
94    Some(
95      template
96        .conf
97        .exclude
98        .iter()
99        .map(|s| s.into())
100        .collect::<Vec<_>>(),
101    ),
102  )
103  .map_err(|e| {
104    ProplateError::create(ProplateErrorKind::Fs {
105      concerned_paths: vec![src.display().to_string(), dest.display().to_string()],
106      operation: "copy_fdir".into(),
107    })
108    .with_ctx("gen:bootstrap:copy_files")
109    .with_cause(&e.to_string())
110  })
111}
112
113pub fn cleanup(template: &Template) -> ProplateResult<()> {
114  println!("{}", logger::step("cleaning up..."));
115  fs::remove_dir_all(&template.base_path).map_err(|e| {
116    ProplateError::create(ProplateErrorKind::Fs {
117      concerned_paths: vec![template.base_path.display().to_string()],
118      operation: "remove_dir_all".into(),
119    })
120    .with_ctx("gen:bootstrap:cleanup")
121    .with_cause(&e.to_string())
122  })?;
123  Ok(())
124}