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> {
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())
}
}
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();
if trimmed_line.starts_with(IMPL_AREA_END) {
if current_area_name.is_empty() {
return None;
}
let name = std::mem::take(&mut current_area_name);
impl_areas.insert(name, current_area_codes.join("\n"));
current_area_codes.clear();
continue;
}
if trimmed_line.starts_with(IMPL_AREA_BEGIN) {
if !current_area_name.is_empty() {
return None;
}
let snake_name = snake_case!(line.trim_start_matches(IMPL_AREA_BEGIN).trim());
current_area_name = snake_name;
continue;
}
if !current_area_name.is_empty() {
current_area_codes.push(line.to_string());
continue;
} else {
striped_content += "\n";
striped_content += line;
}
}
Some((striped_content, impl_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 {
let impl_items = template.impl_params.get(&impl_area_name);
let Some(impl_items) = impl_items else {
impled_areas.insert(impl_area_name, Vec::new());
continue;
};
let mut impled_area_code_applied = Vec::new();
for item in impl_items {
let mut applied = impl_area_template.clone();
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);
for (param_name, param_value) in item {
applied = applied.replace(
&format!("{}{}{}", PARAM_BEGIN, param_name, PARAM_BEND),
param_value,
);
}
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();
if trimmed_line.starts_with(IMPL_BEGIN) {
let impl_name = snake_case!(trimmed_line.trim_start_matches(IMPL_BEGIN).trim());
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 {
applied_content += "\n";
applied_content += line;
}
}
Some(applied_content)
}
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)
}