proplate_core/template/
config.rs1use serde::{Deserialize, Serialize};
2use std::{
3 fs,
4 path::{Path, PathBuf},
5};
6
7use proplate_errors::{ProplateError, ProplateErrorKind, TemplateErrorKind};
8
9use crate::fs::walk::{walk_dir, walk_dir_skip};
10
11use super::{
12 op::{AdditionalOperation, Operation},
13 META_CONF,
14};
15
16#[derive(Serialize, Deserialize, Debug)]
17pub enum ArgType {
18 Text,
19 Select,
20}
21
22#[derive(Serialize, Deserialize, Debug)]
23pub struct Arg {
24 pub key: String,
25 pub q_type: ArgType,
26 pub label: String,
27 pub default_value: Option<String>,
28 pub options: Option<Vec<String>>,
30}
31
32#[derive(Serialize, Deserialize, Debug)]
33pub struct TemplateConf {
34 pub id: String,
36 #[serde(default = "Vec::new")]
39 pub exclude: Vec<String>,
40 pub args: Vec<Arg>,
42 #[serde(default = "Vec::new")]
45 pub dynamic_files: Vec<String>,
46 #[serde(default = "Vec::new")]
47 pub additional_operations: Vec<AdditionalOperation>,
48
49 #[serde(default = "TemplateConf::default_keep_meta")]
50 pub keep_meta: bool,
51
52 #[serde(skip)]
54 pub require_dyn_file_analysis: bool,
55}
56
57impl TemplateConf {
58 pub fn new(path: &Path) -> TemplateConf {
59 let conf = path.join(META_CONF);
60 let meta_json = fs::read_to_string(conf).unwrap();
61 let mut config = parse_config(&meta_json, path.display().to_string().as_str());
62
63 normalize(&mut config, path);
64
65 config
66 }
67
68 fn default_keep_meta() -> bool {
69 false
70 }
71}
72
73fn parse_config(meta_json: &str, location: &str) -> TemplateConf {
74 serde_json::from_str(meta_json)
75 .map_err(|e| {
76 ProplateError::create(ProplateErrorKind::Template {
77 kind: TemplateErrorKind::Invalid,
78 location: location.into(),
79 })
80 .with_ctx("template:parse_config")
81 .with_cause(&e.to_string())
82 })
83 .unwrap()
84}
85
86fn normalize(config: &mut TemplateConf, base: &Path) {
87 set_exclude_files(config, base);
88 set_additional_ops_files(config, base);
89
90 config.require_dyn_file_analysis = true;
91 if config.additional_operations.is_empty() {
95 analyze_dyn_files(config, base);
96 config.require_dyn_file_analysis = false;
97 }
98}
99
100fn set_exclude_files(config: &mut TemplateConf, base: &Path) {
101 let files = &mut config.exclude;
102
103 files.extend([".proplate_aux_utils".into(), ".git".into()]);
105
106 if !config.keep_meta {
107 files.push(META_CONF.into());
108 }
109
110 to_relative_all(files, base);
111}
112
113fn set_additional_ops_files(config: &mut TemplateConf, base: &Path) {
114 for additional_op in &mut config.additional_operations {
115 for op in &mut additional_op.operations {
116 match op {
117 Operation::Copy { file, dest } => {
118 *file = to_relative(PathBuf::from(&file), base);
119 *dest = to_relative(PathBuf::from(&dest), base);
120 }
121 Operation::CopyDir { path, dest } => {
122 *path = to_relative(PathBuf::from(&path), base);
123 *dest = to_relative(PathBuf::from(&dest), base);
124 }
125 Operation::Remove { files } => {
126 to_relative_all(files, base);
127 }
128 }
129 }
130 }
131}
132
133pub fn analyze_dyn_files(config: &mut TemplateConf, base: &Path) {
134 if config.dynamic_files.is_empty() {
135 populate_dynamic_files(config, base);
136 } else {
137 update_dynamic_files(config, base);
138 }
139}
140
141fn populate_dynamic_files(config: &mut TemplateConf, base: &Path) {
143 let TemplateConf {
144 dynamic_files,
145 exclude,
146 ..
147 } = config;
148 let exclude_paths = exclude.iter().map(|s| PathBuf::from(s)).collect::<Vec<_>>();
149 *dynamic_files = walk_dir_skip(base, exclude_paths)
150 .expect("Walk dir")
151 .iter()
152 .map(|(file, _)| file.display().to_string())
153 .collect::<Vec<_>>();
154}
155
156fn update_dynamic_files(config: &mut TemplateConf, base: &Path) {
157 let TemplateConf {
158 dynamic_files,
159 exclude,
160 ..
161 } = config;
162 to_relative_all(dynamic_files, base );
163
164 let mut expanded = Vec::new();
165
166 for path in dynamic_files.iter() {
168 if let Ok(files) = walk_dir(Path::new(path)) {
169 let paths = files
170 .into_iter()
171 .filter_map(|(file, _)| {
172 let file = file.display().to_string();
173 if exclude.contains(&file) {
174 return None;
175 }
176 Some(file)
177 })
178 .collect::<Vec<_>>();
179 expanded.extend(paths);
180 }
181 }
182
183 dynamic_files.extend(expanded);
184}
185
186fn to_relative_all(files: &mut Vec<String>, to: &Path) {
187 for file in files.into_iter() {
188 *file = to_relative(PathBuf::from(&file), to);
189 }
190}
191
192fn to_relative(path: PathBuf, to: &Path) -> String {
193 to.join(path).display().to_string()
194}