From ba15b7c06468cb6c52c8d2a53419fd83f9ebcb8b Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Mon, 29 Jun 2026 03:34:41 +0800 Subject: refactor: promote project to workspace with macros sub-crate --- Cargo.lock | 16 --- Cargo.toml | 20 ++-- just_template/Cargo.lock | 16 +++ just_template/Cargo.toml | 13 +++ just_template/src/deprecated.rs | 45 +++++++++ just_template/src/expand.rs | 212 ++++++++++++++++++++++++++++++++++++++++ just_template/src/lib.rs | 58 +++++++++++ just_template/src/template.rs | 54 ++++++++++ just_template/src/test.rs | 187 +++++++++++++++++++++++++++++++++++ src/deprecated.rs | 45 --------- src/expand.rs | 212 ---------------------------------------- src/lib.rs | 58 ----------- src/template.rs | 54 ---------- src/test.rs | 187 ----------------------------------- 14 files changed, 594 insertions(+), 583 deletions(-) delete mode 100644 Cargo.lock create mode 100644 just_template/Cargo.lock create mode 100644 just_template/Cargo.toml create mode 100644 just_template/src/deprecated.rs create mode 100644 just_template/src/expand.rs create mode 100644 just_template/src/lib.rs create mode 100644 just_template/src/template.rs create mode 100644 just_template/src/test.rs delete mode 100644 src/deprecated.rs delete mode 100644 src/expand.rs delete mode 100644 src/lib.rs delete mode 100644 src/template.rs delete mode 100644 src/test.rs diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index d6a39f0..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,16 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "just_fmt" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e" - -[[package]] -name = "just_template" -version = "0.2.0" -dependencies = [ - "just_fmt", -] diff --git a/Cargo.toml b/Cargo.toml index 5ffa63a..157065c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,11 @@ -[package] -name = "just_template" -authors = ["Weicao-CatilGrass"] -description = "a tool for code gen via templates" +[workspace] +members = [ + "just_template", + "just_template_macros", +] + +[workspace.package] version = "0.2.0" +description = "A tool for code gen via templatese" +authors = ["Weicao-CatilGrass "] edition = "2024" - -readme = "README.md" -license = "MIT OR Apache-2.0" -repository = "https://github.com/catilgrass/just_template" - -[dependencies] -just_fmt = "0.1.2" diff --git a/just_template/Cargo.lock b/just_template/Cargo.lock new file mode 100644 index 0000000..d6a39f0 --- /dev/null +++ b/just_template/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "just_fmt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e" + +[[package]] +name = "just_template" +version = "0.2.0" +dependencies = [ + "just_fmt", +] diff --git a/just_template/Cargo.toml b/just_template/Cargo.toml new file mode 100644 index 0000000..66217b4 --- /dev/null +++ b/just_template/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "just_template" +version.workspace = true +description.workspace = true +authors.workspace = true +edition.workspace = true + +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/catilgrass/just_template" + +[dependencies] +just_fmt = "0.1.2" diff --git a/just_template/src/deprecated.rs b/just_template/src/deprecated.rs new file mode 100644 index 0000000..b218969 --- /dev/null +++ b/just_template/src/deprecated.rs @@ -0,0 +1,45 @@ +#[macro_export] +macro_rules! tmpl_param { + ($template:ident, $($key:ident = $value:expr),* $(,)?) => {{ + $( + $template.insert_param(stringify!($key).to_string(), $value.to_string()); + )* + }}; +} + +#[macro_export] +macro_rules! tmpl { + ($template:ident, $($name:ident { + $($key:ident = $value:expr),* $(,)? + }),* $(,)?) => {{ + $( + let $name = $template.add_impl(stringify!($name).to_string()); + $( + $name.push({ + let mut params = std::collections::HashMap::new(); + params.insert(stringify!($key).to_string(), $value.to_string()); + params + }); + )* + )* + }}; + + // Old syntax + ($template:ident += { + $($name:ident { + $(($($key:ident = $value:expr),* $(,)?)),* + $(,)? + }),* + }) => {{ + $( + let $name = $template.add_impl(stringify!($name).to_string()); + $( + $name.push({ + let mut params = std::collections::HashMap::new(); + $(params.insert(stringify!($key).to_string(), $value.to_string());)* + params + }); + )* + )* + }}; +} 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 { + // 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) +fn read_impl_areas(content: String) -> Option<(String, HashMap)> { + let mut striped_content = String::new(); + let mut impl_areas: HashMap = HashMap::new(); + + let mut current_area_name = String::default(); + let mut current_area_codes: Vec = 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, +) -> Option { + let mut applied_content = String::new(); + + let mut impled_areas: HashMap> = 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, 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 { + let mut content = content; + for (k, v) in template.params.iter() { + content = content.replace(&format!("{}{}{}", PARAM_BEGIN, k, PARAM_BEND), v); + } + Some(content) +} diff --git a/just_template/src/lib.rs b/just_template/src/lib.rs new file mode 100644 index 0000000..7ff77f5 --- /dev/null +++ b/just_template/src/lib.rs @@ -0,0 +1,58 @@ +//! Template struct for storing template strings and their parameters. +//! +//! The template supports two types of parameters: +//! - Simple parameters: key-value pairs used to replace simple placeholders (`<<>>` format) in the template. +//! - Implementation parameters: for implementation blocks (`>>>>>>>>> block_name` and `@@@ >>> block_name` format), +//! can contain multiple parameter sets, each corresponding to an implementation instance. +//! +//! # Examples +//! ``` +//! use just_template::Template; +//! +//! let mut tmpl = Template::from("Hello, <<>>!".to_string()); +//! tmpl.insert_param("name".to_string(), "World".to_string()); +//! assert_eq!(tmpl.to_string(), "Hello, World!"); +//! ``` +//! +//! Using the `tmpl_param!` macro makes it easier to add simple parameters: +//! ``` +//! use just_template::{Template, tmpl_param}; +//! +//! let mut tmpl = Template::from("<<>> + <<>> = <<>>".to_string()); +//! tmpl_param!(tmpl, a = 1, b = 2, c = 3); +//! assert_eq!(tmpl.to_string(), "1 + 2 = 3"); +//! ``` +//! +//! Using the `tmpl!` macro adds implementation block parameters: +//! ``` +//! use just_template::{Template, tmpl}; +//! +//! let mut tmpl = Template::from(" +//! >>>>>>>>>> arms +//! @@@ >>> arms +//! <<>> => Some(<<>>::exec(data, params).await), +//! @@@ <<< +//! ".trim().to_string()); +//! tmpl!(tmpl, +//! arms { +//! crate_name = "my", +//! crate_name = "you", +//! } +//! ); +//! // Output the expanded template +//! let expanded = tmpl.to_string(); +//! assert_eq!(expanded, " +//! my => Some(my::exec(data, params).await), +//! you => Some(you::exec(data, params).await), +//! ".trim().to_string()); +//! ``` +mod template; +pub use template::*; // Re-export template to just_template + +pub mod expand; + +#[cfg(test)] +pub mod test; + +#[deprecated] +pub mod deprecated; diff --git a/just_template/src/template.rs b/just_template/src/template.rs new file mode 100644 index 0000000..e368917 --- /dev/null +++ b/just_template/src/template.rs @@ -0,0 +1,54 @@ +use std::collections::HashMap; + +#[derive(Default, Clone)] +pub struct Template { + pub(crate) template_str: String, + pub(crate) params: HashMap, + pub(crate) impl_params: HashMap>>, +} + +impl Template { + /// Add a parameter + pub fn insert_param(&mut self, name: String, value: String) { + self.params.insert(name, value); + } + + /// Add an implementation block and return a HashMap to set its parameters + pub fn add_impl(&mut self, impl_name: String) -> &mut Vec> { + self.impl_params.entry(impl_name).or_default() + } +} + +impl From for Template { + fn from(s: String) -> Self { + Template { + template_str: s, + ..Default::default() + } + } +} + +impl<'a> From<&'a str> for Template { + fn from(s: &'a str) -> Self { + Template { + template_str: s.to_string(), + ..Default::default() + } + } +} + +impl<'a> From> for Template { + fn from(s: std::borrow::Cow<'a, str>) -> Self { + Template { + template_str: s.into_owned(), + ..Default::default() + } + } +} + +impl std::fmt::Display for Template { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let cloned = self.clone(); + write!(f, "{}", cloned.expand().unwrap_or_default()) + } +} diff --git a/just_template/src/test.rs b/just_template/src/test.rs new file mode 100644 index 0000000..ca7b357 --- /dev/null +++ b/just_template/src/test.rs @@ -0,0 +1,187 @@ +use std::collections::HashMap; + +use crate::template::Template; + +#[test] +fn basic_param() { + let mut tmpl = Template::from("Hello, <<>>!".to_string()); + tmpl.insert_param("name".to_string(), "World".to_string()); + assert_eq!(tmpl.expand().unwrap(), "Hello, World!"); +} + +#[test] +fn multi_param() { + let mut tmpl = Template::from("<<>> + <<>> = <<>>".to_string()); + tmpl.insert_param("a".to_string(), "1".to_string()); + tmpl.insert_param("b".to_string(), "2".to_string()); + tmpl.insert_param("c".to_string(), "3".to_string()); + assert_eq!(tmpl.expand().unwrap(), "1 + 2 = 3"); +} + +#[test] +fn impl_blocks() { + let mut tmpl = Template::from( + r#" +>>>>>>>>>> arms +@@@ >>> arms + "<<>>" => Some(<<>>::exec(data, params).await), +@@@ <<< +"# + .trim() + .to_string(), + ); + + let arms = tmpl.add_impl("arms".to_string()); + arms.push(HashMap::from([( + "crate_name".to_string(), + "my".to_string(), + )])); + arms.push(HashMap::from([( + "crate_name".to_string(), + "you".to_string(), + )])); + + let expanded = tmpl.expand().unwrap(); + assert!(expanded.contains(r#""my" => Some(my::exec(data, params).await)"#)); + assert!(expanded.contains(r#""you" => Some(you::exec(data, params).await)"#)); +} + +#[test] +fn display_block_global_hidden_by_default() { + let tmpl = Template::from( + r#" +visible line +??? >>> debug + hidden line +??? <<< +visible end +"# + .trim() + .to_string(), + ); + + let expanded = tmpl.expand().unwrap(); + assert!(expanded.contains("visible line")); + assert!(expanded.contains("visible end")); + assert!(!expanded.contains("hidden line")); +} + +#[test] +fn display_block_global_shown_via_param() { + let mut tmpl = Template::from( + r#" +visible line +??? >>> debug + shown line +??? <<< +visible end +"# + .trim() + .to_string(), + ); + + tmpl.insert_param("debug".to_string(), "".to_string()); + + let expanded = tmpl.expand().unwrap(); + assert!(expanded.contains("visible line")); + assert!(expanded.contains("visible end")); + assert!(expanded.contains("shown line")); +} + +#[test] +fn display_block_inside_impl_area_hidden_by_default() { + let mut tmpl = Template::from( + r#" +>>>>>>>>>> arms +@@@ >>> arms + <<>> => exec, +??? >>> extra + <<>> => metrics, +??? <<< +@@@ <<< +"# + .trim() + .to_string(), + ); + + let arms = tmpl.add_impl("arms".to_string()); + arms.push(HashMap::from([( + "crate_name".to_string(), + "my".to_string(), + )])); + + let expanded = tmpl.expand().unwrap(); + assert!(expanded.contains(r#"my => exec"#)); + assert!(!expanded.contains(r#"my => metrics"#)); +} + +#[test] +fn display_block_inside_impl_area_shown_by_global_param() { + let mut tmpl = Template::from( + r#" +>>>>>>>>>> arms +@@@ >>> arms + <<>> => exec, +??? >>> extra + <<>> => metrics, +??? <<< +@@@ <<< +"# + .trim() + .to_string(), + ); + + // Enable via global param — shows for ALL arms + tmpl.insert_param("extra".to_string(), "".to_string()); + + let arms = tmpl.add_impl("arms".to_string()); + arms.push(HashMap::from([( + "crate_name".to_string(), + "my".to_string(), + )])); + arms.push(HashMap::from([( + "crate_name".to_string(), + "you".to_string(), + )])); + + let expanded = tmpl.expand().unwrap(); + assert!(expanded.contains(r#"my => exec"#)); + assert!(expanded.contains(r#"my => metrics"#)); + assert!(expanded.contains(r#"you => exec"#)); + assert!(expanded.contains(r#"you => metrics"#)); +} + +#[test] +fn display_block_inside_impl_area_shown_by_arm_param() { + let mut tmpl = Template::from( + r#" +>>>>>>>>>> arms +@@@ >>> arms + <<>> => exec, +??? >>> extra + <<>> => metrics, +??? <<< +@@@ <<< +"# + .trim() + .to_string(), + ); + + let arms = tmpl.add_impl("arms".to_string()); + // Arm 1: no "extra" → hidden + arms.push(HashMap::from([( + "crate_name".to_string(), + "my".to_string(), + )])); + // Arm 2: has "extra" → shown for this arm only + arms.push(HashMap::from([ + ("crate_name".to_string(), "you".to_string()), + ("extra".to_string(), "".to_string()), + ])); + + let expanded = tmpl.expand().unwrap(); + assert!(expanded.contains(r#"my => exec"#)); + assert!(!expanded.contains(r#"my => metrics"#)); + assert!(expanded.contains(r#"you => exec"#)); + assert!(expanded.contains(r#"you => metrics"#)); +} diff --git a/src/deprecated.rs b/src/deprecated.rs deleted file mode 100644 index b218969..0000000 --- a/src/deprecated.rs +++ /dev/null @@ -1,45 +0,0 @@ -#[macro_export] -macro_rules! tmpl_param { - ($template:ident, $($key:ident = $value:expr),* $(,)?) => {{ - $( - $template.insert_param(stringify!($key).to_string(), $value.to_string()); - )* - }}; -} - -#[macro_export] -macro_rules! tmpl { - ($template:ident, $($name:ident { - $($key:ident = $value:expr),* $(,)? - }),* $(,)?) => {{ - $( - let $name = $template.add_impl(stringify!($name).to_string()); - $( - $name.push({ - let mut params = std::collections::HashMap::new(); - params.insert(stringify!($key).to_string(), $value.to_string()); - params - }); - )* - )* - }}; - - // Old syntax - ($template:ident += { - $($name:ident { - $(($($key:ident = $value:expr),* $(,)?)),* - $(,)? - }),* - }) => {{ - $( - let $name = $template.add_impl(stringify!($name).to_string()); - $( - $name.push({ - let mut params = std::collections::HashMap::new(); - $(params.insert(stringify!($key).to_string(), $value.to_string());)* - params - }); - )* - )* - }}; -} diff --git a/src/expand.rs b/src/expand.rs deleted file mode 100644 index b609113..0000000 --- a/src/expand.rs +++ /dev/null @@ -1,212 +0,0 @@ -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 { - // 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) -fn read_impl_areas(content: String) -> Option<(String, HashMap)> { - let mut striped_content = String::new(); - let mut impl_areas: HashMap = HashMap::new(); - - let mut current_area_name = String::default(); - let mut current_area_codes: Vec = 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, -) -> Option { - let mut applied_content = String::new(); - - let mut impled_areas: HashMap> = 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, 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 { - let mut content = content; - for (k, v) in template.params.iter() { - content = content.replace(&format!("{}{}{}", PARAM_BEGIN, k, PARAM_BEND), v); - } - Some(content) -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 7ff77f5..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! Template struct for storing template strings and their parameters. -//! -//! The template supports two types of parameters: -//! - Simple parameters: key-value pairs used to replace simple placeholders (`<<>>` format) in the template. -//! - Implementation parameters: for implementation blocks (`>>>>>>>>> block_name` and `@@@ >>> block_name` format), -//! can contain multiple parameter sets, each corresponding to an implementation instance. -//! -//! # Examples -//! ``` -//! use just_template::Template; -//! -//! let mut tmpl = Template::from("Hello, <<>>!".to_string()); -//! tmpl.insert_param("name".to_string(), "World".to_string()); -//! assert_eq!(tmpl.to_string(), "Hello, World!"); -//! ``` -//! -//! Using the `tmpl_param!` macro makes it easier to add simple parameters: -//! ``` -//! use just_template::{Template, tmpl_param}; -//! -//! let mut tmpl = Template::from("<<>> + <<>> = <<>>".to_string()); -//! tmpl_param!(tmpl, a = 1, b = 2, c = 3); -//! assert_eq!(tmpl.to_string(), "1 + 2 = 3"); -//! ``` -//! -//! Using the `tmpl!` macro adds implementation block parameters: -//! ``` -//! use just_template::{Template, tmpl}; -//! -//! let mut tmpl = Template::from(" -//! >>>>>>>>>> arms -//! @@@ >>> arms -//! <<>> => Some(<<>>::exec(data, params).await), -//! @@@ <<< -//! ".trim().to_string()); -//! tmpl!(tmpl, -//! arms { -//! crate_name = "my", -//! crate_name = "you", -//! } -//! ); -//! // Output the expanded template -//! let expanded = tmpl.to_string(); -//! assert_eq!(expanded, " -//! my => Some(my::exec(data, params).await), -//! you => Some(you::exec(data, params).await), -//! ".trim().to_string()); -//! ``` -mod template; -pub use template::*; // Re-export template to just_template - -pub mod expand; - -#[cfg(test)] -pub mod test; - -#[deprecated] -pub mod deprecated; diff --git a/src/template.rs b/src/template.rs deleted file mode 100644 index e368917..0000000 --- a/src/template.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::collections::HashMap; - -#[derive(Default, Clone)] -pub struct Template { - pub(crate) template_str: String, - pub(crate) params: HashMap, - pub(crate) impl_params: HashMap>>, -} - -impl Template { - /// Add a parameter - pub fn insert_param(&mut self, name: String, value: String) { - self.params.insert(name, value); - } - - /// Add an implementation block and return a HashMap to set its parameters - pub fn add_impl(&mut self, impl_name: String) -> &mut Vec> { - self.impl_params.entry(impl_name).or_default() - } -} - -impl From for Template { - fn from(s: String) -> Self { - Template { - template_str: s, - ..Default::default() - } - } -} - -impl<'a> From<&'a str> for Template { - fn from(s: &'a str) -> Self { - Template { - template_str: s.to_string(), - ..Default::default() - } - } -} - -impl<'a> From> for Template { - fn from(s: std::borrow::Cow<'a, str>) -> Self { - Template { - template_str: s.into_owned(), - ..Default::default() - } - } -} - -impl std::fmt::Display for Template { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let cloned = self.clone(); - write!(f, "{}", cloned.expand().unwrap_or_default()) - } -} diff --git a/src/test.rs b/src/test.rs deleted file mode 100644 index ca7b357..0000000 --- a/src/test.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::collections::HashMap; - -use crate::template::Template; - -#[test] -fn basic_param() { - let mut tmpl = Template::from("Hello, <<>>!".to_string()); - tmpl.insert_param("name".to_string(), "World".to_string()); - assert_eq!(tmpl.expand().unwrap(), "Hello, World!"); -} - -#[test] -fn multi_param() { - let mut tmpl = Template::from("<<>> + <<>> = <<>>".to_string()); - tmpl.insert_param("a".to_string(), "1".to_string()); - tmpl.insert_param("b".to_string(), "2".to_string()); - tmpl.insert_param("c".to_string(), "3".to_string()); - assert_eq!(tmpl.expand().unwrap(), "1 + 2 = 3"); -} - -#[test] -fn impl_blocks() { - let mut tmpl = Template::from( - r#" ->>>>>>>>>> arms -@@@ >>> arms - "<<>>" => Some(<<>>::exec(data, params).await), -@@@ <<< -"# - .trim() - .to_string(), - ); - - let arms = tmpl.add_impl("arms".to_string()); - arms.push(HashMap::from([( - "crate_name".to_string(), - "my".to_string(), - )])); - arms.push(HashMap::from([( - "crate_name".to_string(), - "you".to_string(), - )])); - - let expanded = tmpl.expand().unwrap(); - assert!(expanded.contains(r#""my" => Some(my::exec(data, params).await)"#)); - assert!(expanded.contains(r#""you" => Some(you::exec(data, params).await)"#)); -} - -#[test] -fn display_block_global_hidden_by_default() { - let tmpl = Template::from( - r#" -visible line -??? >>> debug - hidden line -??? <<< -visible end -"# - .trim() - .to_string(), - ); - - let expanded = tmpl.expand().unwrap(); - assert!(expanded.contains("visible line")); - assert!(expanded.contains("visible end")); - assert!(!expanded.contains("hidden line")); -} - -#[test] -fn display_block_global_shown_via_param() { - let mut tmpl = Template::from( - r#" -visible line -??? >>> debug - shown line -??? <<< -visible end -"# - .trim() - .to_string(), - ); - - tmpl.insert_param("debug".to_string(), "".to_string()); - - let expanded = tmpl.expand().unwrap(); - assert!(expanded.contains("visible line")); - assert!(expanded.contains("visible end")); - assert!(expanded.contains("shown line")); -} - -#[test] -fn display_block_inside_impl_area_hidden_by_default() { - let mut tmpl = Template::from( - r#" ->>>>>>>>>> arms -@@@ >>> arms - <<>> => exec, -??? >>> extra - <<>> => metrics, -??? <<< -@@@ <<< -"# - .trim() - .to_string(), - ); - - let arms = tmpl.add_impl("arms".to_string()); - arms.push(HashMap::from([( - "crate_name".to_string(), - "my".to_string(), - )])); - - let expanded = tmpl.expand().unwrap(); - assert!(expanded.contains(r#"my => exec"#)); - assert!(!expanded.contains(r#"my => metrics"#)); -} - -#[test] -fn display_block_inside_impl_area_shown_by_global_param() { - let mut tmpl = Template::from( - r#" ->>>>>>>>>> arms -@@@ >>> arms - <<>> => exec, -??? >>> extra - <<>> => metrics, -??? <<< -@@@ <<< -"# - .trim() - .to_string(), - ); - - // Enable via global param — shows for ALL arms - tmpl.insert_param("extra".to_string(), "".to_string()); - - let arms = tmpl.add_impl("arms".to_string()); - arms.push(HashMap::from([( - "crate_name".to_string(), - "my".to_string(), - )])); - arms.push(HashMap::from([( - "crate_name".to_string(), - "you".to_string(), - )])); - - let expanded = tmpl.expand().unwrap(); - assert!(expanded.contains(r#"my => exec"#)); - assert!(expanded.contains(r#"my => metrics"#)); - assert!(expanded.contains(r#"you => exec"#)); - assert!(expanded.contains(r#"you => metrics"#)); -} - -#[test] -fn display_block_inside_impl_area_shown_by_arm_param() { - let mut tmpl = Template::from( - r#" ->>>>>>>>>> arms -@@@ >>> arms - <<>> => exec, -??? >>> extra - <<>> => metrics, -??? <<< -@@@ <<< -"# - .trim() - .to_string(), - ); - - let arms = tmpl.add_impl("arms".to_string()); - // Arm 1: no "extra" → hidden - arms.push(HashMap::from([( - "crate_name".to_string(), - "my".to_string(), - )])); - // Arm 2: has "extra" → shown for this arm only - arms.push(HashMap::from([ - ("crate_name".to_string(), "you".to_string()), - ("extra".to_string(), "".to_string()), - ])); - - let expanded = tmpl.expand().unwrap(); - assert!(expanded.contains(r#"my => exec"#)); - assert!(!expanded.contains(r#"my => metrics"#)); - assert!(expanded.contains(r#"you => exec"#)); - assert!(expanded.contains(r#"you => metrics"#)); -} -- cgit