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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
use std::{collections::HashMap, mem::replace};
use just_fmt::snake_case;
use crate::Template;
const IMPL_AREA_BEGIN: &str = "@@@ >>> ";
const IMPL_AREA_END: &str = "@@@ <<<";
const IMPL_BEGIN: &str = ">>>>>>>>>>";
const PARAM_BEGIN: &str = "<<<";
const PARAM_BEND: &str = ">>>";
impl Template {
pub fn expand(mut self) -> Option<String> {
// Extract template text
let expanded = replace(&mut self.template_str, String::default());
let (expanded, impl_areas) = read_impl_areas(expanded)?;
let expanded = apply_impls(&self, expanded, impl_areas)?;
let expanded = apply_param(&self, expanded)?;
Some(expanded.trim().to_string())
}
}
/// Read all ImplAreas (HashMap<Name, Codes>)
fn read_impl_areas(content: String) -> Option<(String, HashMap<String, String>)> {
let mut striped_content = String::new();
let mut impl_areas: HashMap<String, String> = HashMap::new();
let mut current_area_name = String::default();
let mut current_area_codes: Vec<String> = Vec::new();
for line in content.split("\n") {
let trimmed_line = line.trim();
// Implementation block end
if trimmed_line.starts_with(IMPL_AREA_END) {
// If the current ImplArea name length is less than 1, it means no block is being matched,
// so matching fails, exit early
if current_area_name.len() < 1 {
return None;
}
// Submit Impl Area
let name = replace(&mut current_area_name, String::default());
impl_areas.insert(name, current_area_codes.join("\n"));
current_area_codes.clear();
continue;
}
// Implementation block start
if trimmed_line.starts_with(IMPL_AREA_BEGIN) {
// If the current ImplArea name length is greater than 0, it means we are already inside a block,
// since nesting is not allowed, matching fails, exit early
if current_area_name.len() > 0 {
return None;
}
// Get a snake_case name
let snake_name = snake_case!(line.trim_start_matches(IMPL_AREA_BEGIN).trim());
current_area_name = snake_name;
// Continue to next line
continue;
}
// During implementation block
if current_area_name.len() > 0 {
// Add to current block code
current_area_codes.push(line.to_string());
continue;
} else {
// Add to remaining content
striped_content += "\n";
striped_content += line;
}
}
Some((striped_content, impl_areas))
}
/// Apply Template parameters to implementation block areas
fn apply_impls(
template: &Template,
content: String,
impl_areas: HashMap<String, String>,
) -> Option<String> {
let mut applied_content = String::new();
let mut impled_areas: HashMap<String, Vec<String>> = HashMap::new();
for (impl_area_name, impl_area_template) in impl_areas {
// Get user-provided parameters
let impl_items = template.impl_params.get(&impl_area_name);
// No parameters, return early
let Some(impl_items) = impl_items else {
impled_areas.insert(impl_area_name, Vec::new());
continue;
};
let mut impled_area_code_applied = Vec::new();
// Split items
for item in impl_items {
// Get base template
let mut template = impl_area_template.clone();
// Extract parameters
for (param_name, param_value) in item {
// Apply parameter
template = template.replace(
&format!("{}{}{}", PARAM_BEGIN, param_name, PARAM_BEND),
param_value,
);
}
// Add applied template
impled_area_code_applied.push(template);
}
impled_areas.insert(impl_area_name, impled_area_code_applied);
}
for line in content.split("\n") {
let trimmed_line = line.trim();
// Recognize implementation line
if trimmed_line.starts_with(IMPL_BEGIN) {
let impl_name = snake_case!(trimmed_line.trim_start_matches(IMPL_BEGIN).trim());
// Try to get implementation code block
let Some(impled_code) = impled_areas.get(&impl_name) else {
continue;
};
applied_content += "\n";
applied_content += impled_code.join("\n").as_str();
} else {
// Other content directly appended
applied_content += "\n";
applied_content += line;
}
}
Some(applied_content)
}
fn apply_param(template: &Template, content: String) -> Option<String> {
let mut content = content;
for (k, v) in template.params.iter() {
content = content.replace(&format!("{}{}{}", PARAM_BEGIN, k, PARAM_BEND), v);
}
Some(content)
}
|