fix tagspecs

This commit is contained in:
Josh Thomas 2025-01-06 16:47:43 -06:00
parent a078faabf7
commit 17f3fb43f7
3 changed files with 61 additions and 72 deletions

View file

@ -125,7 +125,7 @@ impl Parser {
} }
// If we get here, either there was no expected closing tag or it didn't match // If we get here, either there was no expected closing tag or it didn't match
if let Some(branches) = &spec.branches { 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_tag = tag.clone();
let mut branch_nodes = Vec::new(); let mut branch_nodes = Vec::new();
let mut found_closing = false; let mut found_closing = false;
@ -147,8 +147,7 @@ impl Parser {
} }
} }
// Check if this is another branch tag // 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 // Push the current branch and start a new one
nodes.push(Node::Block(Block::Branch { nodes.push(Node::Block(Block::Branch {
tag: branch_tag.clone(), tag: branch_tag.clone(),

View file

@ -10,63 +10,15 @@ pub struct TagSpec {
#[serde(rename = "type")] #[serde(rename = "type")]
pub tag_type: TagType, pub tag_type: TagType,
pub closing: Option<String>, pub closing: Option<String>,
pub branches: Option<Vec<BranchSpec>>, #[serde(default)]
pub branches: Option<Vec<String>>,
pub args: Option<Vec<ArgSpec>>, pub args: Option<Vec<ArgSpec>>,
} }
#[derive(Debug, Clone, Deserialize)]
pub struct BranchSpec {
pub name: String,
pub args: bool,
}
impl TagSpec { impl TagSpec {
pub fn load_builtin_specs() -> Result<HashMap<String, TagSpec>> { pub fn load_builtin_specs() -> Result<HashMap<String, TagSpec>> {
let mut specs = HashMap::new(); 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"); let specs_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tagspecs");
for entry in fs::read_dir(&specs_dir)? { for entry in fs::read_dir(&specs_dir)? {
@ -80,36 +32,39 @@ impl TagSpec {
let value: Value = toml::from_str(&content) let value: Value = toml::from_str(&content)
.with_context(|| format!("Failed to parse {:?}", path))?; .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) Ok(specs)
} }
fn extract_specs( fn extract_specs(
value: &Value, value: &Value,
prefix: &str, prefix: Option<&str>,
specs: &mut HashMap<String, TagSpec>, specs: &mut HashMap<String, TagSpec>,
) -> Result<()> { ) -> Result<()> {
if let Value::Table(table) = value { // Try to deserialize as a tag spec first
// If this table has a 'type' field, try to parse it as a TagSpec match TagSpec::deserialize(value.clone()) {
if table.contains_key("type") { Ok(tag_spec) => {
if let Ok(tag_spec) = TagSpec::deserialize(value.clone()) { let name = prefix.map_or_else(String::new, |p| {
let name = prefix.split('.').last().unwrap_or(prefix); p.split('.').last().unwrap_or(p).to_string()
specs.insert(name.to_string(), tag_spec); });
return Ok(()); eprintln!("Found tag spec at '{}', using name '{}'", prefix.unwrap_or(""), name);
} specs.insert(name, tag_spec);
} }
Err(_) => {
// Otherwise, recursively process each field // Not a tag spec, try recursing into any table values
for (key, value) in table { for (key, value) in value.as_table().iter().flat_map(|t| t.iter()) {
let new_prefix = if prefix.is_empty() { let new_prefix = match prefix {
key.clone() None => key.clone(),
} else { Some(p) => format!("{}.{}", p, key),
format!("{}.{}", prefix, key) };
}; eprintln!("Recursing into prefix: {}", new_prefix);
Self::extract_specs(value, &new_prefix, specs)?; Self::extract_specs(value, Some(&new_prefix), specs)?;
}
} }
} }
Ok(()) Ok(())
@ -162,4 +117,34 @@ mod tests {
Ok(()) 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(())
}
} }

View file

@ -1,3 +1,4 @@
# Django built-in template tags
[django.template.defaulttags.if] [django.template.defaulttags.if]
branches = ["elif", "else"] branches = ["elif", "else"]
closing = "endif" closing = "endif"
@ -23,3 +24,7 @@ required = true
[[django.template.defaulttags.for.args]] [[django.template.defaulttags.for.args]]
name = "{iterable}" name = "{iterable}"
required = true required = true
[django.template.defaulttags.block]
closing = "endblock"
type = "block"