diff --git a/crates/djls-template-ast/src/parser.rs b/crates/djls-template-ast/src/parser.rs index 8cd523b..4888740 100644 --- a/crates/djls-template-ast/src/parser.rs +++ b/crates/djls-template-ast/src/parser.rs @@ -125,7 +125,7 @@ impl Parser { } // If we get here, either there was no expected closing tag or it didn't match if let Some(branches) = &spec.branches { - if branches.iter().any(|b| b.name == tag.name) { + if branches.iter().any(|b| b == &tag.name) { let mut branch_tag = tag.clone(); let mut branch_nodes = Vec::new(); let mut found_closing = false; @@ -147,8 +147,7 @@ impl Parser { } } // Check if this is another branch tag - if branches.iter().any(|b| b.name == next_tag.name) - { + if branches.iter().any(|b| b == &next_tag.name) { // Push the current branch and start a new one nodes.push(Node::Block(Block::Branch { tag: branch_tag.clone(), diff --git a/crates/djls-template-ast/src/tagspecs.rs b/crates/djls-template-ast/src/tagspecs.rs index fc190cd..062a085 100644 --- a/crates/djls-template-ast/src/tagspecs.rs +++ b/crates/djls-template-ast/src/tagspecs.rs @@ -10,63 +10,15 @@ pub struct TagSpec { #[serde(rename = "type")] pub tag_type: TagType, pub closing: Option, - pub branches: Option>, + #[serde(default)] + pub branches: Option>, pub args: Option>, } -#[derive(Debug, Clone, Deserialize)] -pub struct BranchSpec { - pub name: String, - pub args: bool, -} - impl TagSpec { pub fn load_builtin_specs() -> Result> { let mut specs = HashMap::new(); - // Add built-in tag specs - specs.insert( - "if".to_string(), - TagSpec { - tag_type: TagType::Block, - closing: Some("endif".to_string()), - branches: Some(vec![ - BranchSpec { - name: "elif".to_string(), - args: true, - }, - BranchSpec { - name: "else".to_string(), - args: false, - }, - ]), - args: None, - }, - ); - - specs.insert( - "for".to_string(), - TagSpec { - tag_type: TagType::Block, - closing: Some("endfor".to_string()), - branches: Some(vec![BranchSpec { - name: "empty".to_string(), - args: false, - }]), - args: None, - }, - ); - - specs.insert( - "block".to_string(), - TagSpec { - tag_type: TagType::Block, - closing: Some("endblock".to_string()), - branches: None, - args: None, - }, - ); - let specs_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tagspecs"); for entry in fs::read_dir(&specs_dir)? { @@ -80,36 +32,39 @@ impl TagSpec { let value: Value = toml::from_str(&content) .with_context(|| format!("Failed to parse {:?}", path))?; - Self::extract_specs(&value, "", &mut specs)?; + Self::extract_specs(&value, None, &mut specs)?; } } + eprintln!("specs: {:?}", specs); + Ok(specs) } fn extract_specs( value: &Value, - prefix: &str, + prefix: Option<&str>, specs: &mut HashMap, ) -> Result<()> { - if let Value::Table(table) = value { - // If this table has a 'type' field, try to parse it as a TagSpec - if table.contains_key("type") { - if let Ok(tag_spec) = TagSpec::deserialize(value.clone()) { - let name = prefix.split('.').last().unwrap_or(prefix); - specs.insert(name.to_string(), tag_spec); - return Ok(()); - } + // Try to deserialize as a tag spec first + match TagSpec::deserialize(value.clone()) { + Ok(tag_spec) => { + let name = prefix.map_or_else(String::new, |p| { + p.split('.').last().unwrap_or(p).to_string() + }); + eprintln!("Found tag spec at '{}', using name '{}'", prefix.unwrap_or(""), name); + specs.insert(name, tag_spec); } - - // Otherwise, recursively process each field - for (key, value) in table { - let new_prefix = if prefix.is_empty() { - key.clone() - } else { - format!("{}.{}", prefix, key) - }; - Self::extract_specs(value, &new_prefix, specs)?; + Err(_) => { + // Not a tag spec, try recursing into any table values + for (key, value) in value.as_table().iter().flat_map(|t| t.iter()) { + let new_prefix = match prefix { + None => key.clone(), + Some(p) => format!("{}.{}", p, key), + }; + eprintln!("Recursing into prefix: {}", new_prefix); + Self::extract_specs(value, Some(&new_prefix), specs)?; + } } } Ok(()) @@ -162,4 +117,34 @@ mod tests { Ok(()) } + + #[test] + fn test_builtin_django_tags() -> Result<()> { + let specs = TagSpec::load_builtin_specs()?; + + let if_tag = specs.get("if").expect("if tag should be present"); + assert_eq!(if_tag.tag_type, TagType::Block); + assert_eq!(if_tag.closing, Some("endif".to_string())); + let if_branches = if_tag + .branches + .as_ref() + .expect("if tag should have branches"); + assert!(if_branches.iter().any(|b| b == "elif")); + assert!(if_branches.iter().any(|b| b == "else")); + + let for_tag = specs.get("for").expect("for tag should be present"); + assert_eq!(for_tag.tag_type, TagType::Block); + assert_eq!(for_tag.closing, Some("endfor".to_string())); + let for_branches = for_tag + .branches + .as_ref() + .expect("for tag should have branches"); + assert!(for_branches.iter().any(|b| b == "empty")); + + let block_tag = specs.get("block").expect("block tag should be present"); + assert_eq!(block_tag.tag_type, TagType::Block); + assert_eq!(block_tag.closing, Some("endblock".to_string())); + + Ok(()) + } } diff --git a/crates/djls-template-ast/tagspecs/django.toml b/crates/djls-template-ast/tagspecs/django.toml index 418376a..0f1d68c 100644 --- a/crates/djls-template-ast/tagspecs/django.toml +++ b/crates/djls-template-ast/tagspecs/django.toml @@ -1,3 +1,4 @@ +# Django built-in template tags [django.template.defaulttags.if] branches = ["elif", "else"] closing = "endif" @@ -23,3 +24,7 @@ required = true [[django.template.defaulttags.for.args]] name = "{iterable}" required = true + +[django.template.defaulttags.block] +closing = "endblock" +type = "block"