diff options
| -rw-r--r-- | src/deprecated.rs | 45 | ||||
| -rw-r--r-- | src/expand.rs | 74 | ||||
| -rw-r--r-- | src/lib.rs | 49 | ||||
| -rw-r--r-- | src/template.rs | 4 | ||||
| -rw-r--r-- | src/test.rs | 213 | ||||
| -rw-r--r-- | src/test_expect.txt | 14 | ||||
| -rw-r--r-- | src/test_input.txt | 16 |
7 files changed, 300 insertions, 115 deletions
diff --git a/src/deprecated.rs b/src/deprecated.rs new file mode 100644 index 0000000..b218969 --- /dev/null +++ b/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/src/expand.rs b/src/expand.rs index 5933b2c..b609113 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -1,9 +1,12 @@ -use std::{collections::HashMap, mem::replace}; +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 = "@@@ <<<"; @@ -15,10 +18,11 @@ 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 = 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()) } @@ -39,12 +43,12 @@ fn read_impl_areas(content: String) -> Option<(String, HashMap<String, String>)> 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 { + if current_area_name.is_empty() { return None; } // Submit Impl Area - let name = replace(&mut current_area_name, String::default()); + let name = std::mem::take(&mut current_area_name); impl_areas.insert(name, current_area_codes.join("\n")); current_area_codes.clear(); continue; @@ -54,7 +58,7 @@ fn read_impl_areas(content: String) -> Option<(String, HashMap<String, String>)> 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 { + if !current_area_name.is_empty() { return None; } @@ -67,7 +71,7 @@ fn read_impl_areas(content: String) -> Option<(String, HashMap<String, String>)> } // During implementation block - if current_area_name.len() > 0 { + if !current_area_name.is_empty() { // Add to current block code current_area_codes.push(line.to_string()); continue; @@ -105,19 +109,26 @@ fn apply_impls( // Split items for item in impl_items { // Get base template - let mut template = impl_area_template.clone(); + 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 - template = template.replace( + applied = applied.replace( &format!("{}{}{}", PARAM_BEGIN, param_name, PARAM_BEND), param_value, ); } // Add applied template - impled_area_code_applied.push(template); + impled_area_code_applied.push(applied); } impled_areas.insert(impl_area_name, impled_area_code_applied); @@ -135,7 +146,7 @@ fn apply_impls( continue; }; - if impled_code.len() > 0 { + if !impled_code.is_empty() { applied_content += "\n"; applied_content += impled_code.join("\n").as_str(); } @@ -149,6 +160,49 @@ fn apply_impls( 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() { @@ -50,50 +50,9 @@ mod template; pub use template::*; // Re-export template to just_template pub mod expand; -pub mod test; - -#[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 - }); - )* - )* - }}; +#[cfg(test)] +pub mod test; - // 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 - }); - )* - )* - }}; -} +#[deprecated] +pub mod deprecated; diff --git a/src/template.rs b/src/template.rs index 27e14df..e368917 100644 --- a/src/template.rs +++ b/src/template.rs @@ -15,9 +15,7 @@ impl Template { /// Add an implementation block and return a HashMap to set its parameters pub fn add_impl(&mut self, impl_name: String) -> &mut Vec<HashMap<String, String>> { - self.impl_params - .entry(impl_name) - .or_insert_with(|| Vec::<HashMap<String, String>>::new()) + self.impl_params.entry(impl_name).or_default() } } diff --git a/src/test.rs b/src/test.rs index 91407fc..ca7b357 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,28 +1,187 @@ -#[cfg(test)] -mod tests { - use crate::{template::Template, tmpl, tmpl_param}; - - #[test] - fn expand() { - let input = std::fs::read_to_string("./src/test_input.txt") - .unwrap() - .trim() - .to_string(); - let expect = std::fs::read_to_string("./src/test_expect.txt") - .unwrap() - .trim() - .to_string(); - - let mut tmpl = Template::from(input); - tmpl_param!(tmpl, func_name = "my_func"); - tmpl!(tmpl, - arms { - crate_name = "my", - crate_name = "you", - } - ); - - let expanded = tmpl.expand().unwrap(); - assert_eq!(expanded, expect); - } +use std::collections::HashMap; + +use crate::template::Template; + +#[test] +fn basic_param() { + let mut tmpl = Template::from("Hello, <<<name>>>!".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("<<<a>>> + <<<b>>> = <<<c>>>".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 + "<<<crate_name>>>" => Some(<<<crate_name>>>::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 + <<<crate_name>>> => exec, +??? >>> extra + <<<crate_name>>> => 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 + <<<crate_name>>> => exec, +??? >>> extra + <<<crate_name>>> => 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 + <<<crate_name>>> => exec, +??? >>> extra + <<<crate_name>>> => 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/test_expect.txt b/src/test_expect.txt deleted file mode 100644 index c967707..0000000 --- a/src/test_expect.txt +++ /dev/null @@ -1,14 +0,0 @@ -// Auto generated -use std::collections::HashMap; - -pub async fn my_func( - name: &str, - data: &[u8], - params: &HashMap<String, String>, -) -> Option<Result<Vec<u32>, std::io::Error>> { - match name { - "my" => Some(my::exec(data, params).await), - "you" => Some(you::exec(data, params).await), - _ => None, - } -} diff --git a/src/test_input.txt b/src/test_input.txt deleted file mode 100644 index 97c91c4..0000000 --- a/src/test_input.txt +++ /dev/null @@ -1,16 +0,0 @@ -// Auto generated -use std::collections::HashMap; - -pub async fn <<<func_name>>>( - name: &str, - data: &[u8], - params: &HashMap<String, String>, -) -> Option<Result<Vec<u32>, std::io::Error>> { - match name { ->>>>>>>>>> arms -@@@ >>> arms - "<<<crate_name>>>" => Some(<<<crate_name>>>::exec(data, params).await), -@@@ <<< - _ => None, - } -} |
