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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
|
use std::collections::HashMap;
use just_fmt::snake_case;
use crate::template::Template;
const DISPLAY_BLOCK_BEGIN: &str = "??? >>> ";
const DISPLAY_BLOCK_END: &str = "??? <<<";
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 = std::mem::take(&mut self.template_str);
let (expanded, impl_areas) = read_impl_areas(expanded)?;
let expanded = apply_impls(&self, expanded, impl_areas)?;
let expanded = apply_display_blocks(&self.params, expanded);
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.is_empty() {
return None;
}
// Submit Impl Area
let name = std::mem::take(&mut current_area_name);
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.is_empty() {
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.is_empty() {
// 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 applied = impl_area_template.clone();
// Merge global params with arm-specific params for display block check
let mut display_params = template.params.clone();
for (k, v) in item {
display_params.insert(k.clone(), v.clone());
}
applied = apply_display_blocks(&display_params, applied);
// Extract parameters
for (param_name, param_value) in item {
// Apply parameter
applied = applied.replace(
&format!("{}{}{}", PARAM_BEGIN, param_name, PARAM_BEND),
param_value,
);
}
// Add applied template
impled_area_code_applied.push(applied);
}
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;
};
if !impled_code.is_empty() {
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)
}
/// Process display blocks (`??? >>> name` / `??? <<<`).
///
/// If `params` contains a key matching the block name, the block content is
/// included (with markers removed). Otherwise the entire block is omitted.
fn apply_display_blocks(params: &HashMap<String, String>, content: String) -> String {
let mut result = String::new();
let lines: Vec<&str> = content.split("\n").collect();
let mut i = 0;
let mut first = true;
while i < lines.len() {
let line = lines[i];
let trimmed = line.trim();
if trimmed.starts_with(DISPLAY_BLOCK_BEGIN) {
let block_name = trimmed.trim_start_matches(DISPLAY_BLOCK_BEGIN).trim();
let show = params.contains_key(block_name);
i += 1;
while i < lines.len() && !lines[i].trim().starts_with(DISPLAY_BLOCK_END) {
if show {
if !first {
result += "\n";
}
result += lines[i];
first = false;
}
i += 1;
}
} else if !trimmed.starts_with(DISPLAY_BLOCK_END) {
if !first {
result += "\n";
}
result += line;
first = false;
}
i += 1;
}
result
}
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)
}
|