diff --git a/crates/djls-template-ast/SPEC.md b/crates/djls-template-ast/SPEC.md index 29c4387..208fe22 100644 --- a/crates/djls-template-ast/SPEC.md +++ b/crates/djls-template-ast/SPEC.md @@ -127,9 +127,6 @@ pub enum Block { tag: Tag, template_name: String, }, - Variable { - tag: Tag, - }, Closing { tag: Tag, }, @@ -231,21 +228,6 @@ Examples: - `{% include "template.html" %}` - `{% extends "base.html" %}` -##### `Block::Variable` - -Represents tags that output a value directly. - -```rust -Block::Variable { - tag: Tag, // The Tag of the variable tag -} -``` - -Examples: - -- `{% cycle %}` -- `{% firstof %}` - ##### `Block::Closing` Represents closing tags corresponding to opening block tags. @@ -270,15 +252,21 @@ Tag Specifications (TagSpecs) define how tags are parsed and understood. They al ```toml [package.module.path.tag_name] # Path where tag is registered, e.g., django.template.defaulttags -type = "block" | "inclusion" | "tag" | "variable" +type = "block" | "inclusion" | "tag" closing = "closing_tag_name" # For block tags that require a closing tag branches = ["branch_tag_name", ...] # For block tags that support branches -[[package.module.path.tag_name.args]] -name = "argument_name" -required = true | false +# Arguments can be positional (matched by order) or keyword (matched by name) +args = [ + # Positional argument (position inferred from array index) + { name = "setting", required = true, allowed_values = ["on", "off"] }, + # Keyword argument + { name = "key", required = false, is_kwarg = true } +] ``` +The `name` field in args should match the internal name used in Django's node implementation. For example, the `autoescape` tag's argument is stored as `setting` in Django's `AutoEscapeControlNode`. + ### Tag Types - `block`: Tags that wrap content and require a closing tag @@ -301,13 +289,6 @@ required = true | false {% csrf_token %} ``` -- `variable`: Tags that output a value directly - - ```django - {% cycle 'odd' 'even' %} - {% firstof var1 var2 var3 %} - ``` - ### Configuration - **Built-in TagSpecs**: The parser includes TagSpecs for Django's built-in tags and popular third-party tags. @@ -322,10 +303,7 @@ required = true | false type = "block" closing = "endif" branches = ["elif", "else"] - -[[django.template.defaulttags.if.args]] -name = "condition" -required = true +args = [{ name = "condition", required = true }] ``` #### Include Tag @@ -333,19 +311,25 @@ required = true ```toml [django.template.defaulttags.includes] type = "inclusion" - -[[django.template.defaulttags.includes.args]] -name = "template_name" -required = true +args = [{ name = "template_name", required = true }] ``` -#### Custom Tag +#### Autoescape Tag + +```toml +[django.template.defaulttags.autoescape] +type = "block" +closing = "endautoescape" +args = [{ name = "setting", required = true, allowed_values = ["on", "off"] }] +``` + +#### Custom Tag with Kwargs ```toml [my_module.templatetags.my_tags.my_custom_tag] type = "tag" - -{[my_module.templatetags.my_tags.my_custom_tag.args]] -name = "arg1" -required = false +args = [ + { name = "arg1", required = true }, + { name = "kwarg1", required = false, is_kwarg = true } +] ``` diff --git a/crates/djls-template-ast/src/ast.rs b/crates/djls-template-ast/src/ast.rs index 6f70324..889e905 100644 --- a/crates/djls-template-ast/src/ast.rs +++ b/crates/djls-template-ast/src/ast.rs @@ -162,9 +162,6 @@ pub enum Block { tag: Tag, template_name: String, }, - Variable { - tag: Tag, - }, Closing { tag: Tag, }, @@ -177,7 +174,6 @@ impl Block { | Self::Branch { tag, .. } | Self::Tag { tag } | Self::Inclusion { tag, .. } - | Self::Variable { tag } | Self::Closing { tag } => tag, } } diff --git a/crates/djls-template-ast/src/parser.rs b/crates/djls-template-ast/src/parser.rs index 04c7d0d..1dc1913 100644 --- a/crates/djls-template-ast/src/parser.rs +++ b/crates/djls-template-ast/src/parser.rs @@ -110,7 +110,6 @@ impl Parser { Some(spec) => match spec.tag_type { TagType::Block => self.parse_block_tag(tag, spec), TagType::Tag => Ok(Node::Block(Block::Tag { tag })), - TagType::Variable => Ok(Node::Block(Block::Variable { tag })), TagType::Inclusion => { let template_name = tag.bits.get(1).cloned().unwrap_or_default(); Ok(Node::Block(Block::Inclusion { tag, template_name })) diff --git a/crates/djls-template-ast/src/tagspecs.rs b/crates/djls-template-ast/src/tagspecs.rs index 169c0ab..7e0c6ed 100644 --- a/crates/djls-template-ast/src/tagspecs.rs +++ b/crates/djls-template-ast/src/tagspecs.rs @@ -117,11 +117,6 @@ impl TagSpec { 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); } Err(_) => { @@ -131,7 +126,6 @@ impl TagSpec { None => key.clone(), Some(p) => format!("{}.{}", p, key), }; - eprintln!("Recursing into prefix: {}", new_prefix); Self::extract_specs(value, Some(&new_prefix), specs)?; } } @@ -146,13 +140,16 @@ pub enum TagType { Block, Tag, Inclusion, - Variable, } #[derive(Clone, Debug, Deserialize)] pub struct ArgSpec { pub name: String, pub required: bool, + #[serde(default)] + pub allowed_values: Option>, + #[serde(default)] + pub is_kwarg: bool, } impl ArgSpec { @@ -179,12 +176,8 @@ mod tests { assert!(!specs.0.is_empty(), "Should have loaded at least one spec"); - for (name, spec) in &specs.0 { + for name in specs.0.keys() { assert!(!name.is_empty(), "Tag name should not be empty"); - assert!( - spec.tag_type == TagType::Block || spec.tag_type == TagType::Variable, - "Tag type should be block or variable" - ); } Ok(()) } @@ -193,31 +186,35 @@ mod tests { fn test_builtin_django_tags() -> Result<(), anyhow::Error> { let specs = TagSpecs::load_builtin_specs()?; - let expected_tags = ["block", "for", "if"]; - let missing_tags = [ + let expected_tags = [ "autoescape", + "block", "comment", - "csrf_token", "cycle", "debug", "extends", "filter", + "for", "firstof", - "ifchanged", + "if", "include", "load", - "lorem", "now", - "querystring", // 5.1 - "regroup", - "resetcycle", "spaceless", "templatetag", "url", "verbatim", - "widthratio", "with", ]; + let missing_tags = [ + "csrf_token", + "ifchanged", + "lorem", + "querystring", // 5.1 + "regroup", + "resetcycle", + "widthratio", + ]; for tag in expected_tags { assert!(specs.get(tag).is_some(), "{} tag should be present", tag);