diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-06-29 03:34:41 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-06-29 03:34:41 +0800 |
| commit | ba15b7c06468cb6c52c8d2a53419fd83f9ebcb8b (patch) | |
| tree | cdaa1c71585d10dd73945cefe95f2c25f27924ed /just_template/src/expand.rs | |
| parent | ff70307869a547b13850d1eec3f72e8ca3bca265 (diff) | |
refactor: promote project to workspace with macros sub-crate
Diffstat (limited to 'just_template/src/expand.rs')
| -rw-r--r-- | just_template/src/expand.rs | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/just_template/src/expand.rs b/just_template/src/expand.rs new file mode 100644 index 0000000..b609113 --- /dev/null +++ b/just_template/src/expand.rs @@ -0,0 +1,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) +} |
