This commit is contained in:
danhanson committed 2026-09-07 15:14:14 -05:00
1 parent 7eb95e4e84
commit 7e71c5e3ae
8 files changed
+40812 -33902

No files matched your search

+3 -3
View File
@@ -106,7 +106,7 @@ which Django itself enforces while it parses the template ("add requires 2 argum
### First-party libraries ### First-party libraries
Django ships `i18n`, `l10n`, `static`, `cache` and `tz` as libraries rather than builtins. Their tags are Django ships `i18n`, `l10n`, `static`, `cache` and `tz` as libraries rather than builtins. Their tags are
modelled as ordinary tags (so far: `static`, `l10n`), and the grammar **never requires the `{% load %}`**, because modelled as ordinary tags (so far: `static`, `l10n`, `tz`), and the grammar **never requires the `{% load %}`**, because
an engine can preload a library through `OPTIONS: {"builtins": [...]}`, which makes `{% static "a" %}` valid an engine can preload a library through `OPTIONS: {"builtins": [...]}`, which makes `{% static "a" %}` valid
with no load at all. with no load at all.
@@ -121,8 +121,8 @@ no ordering predicate to express it. What such a tool needs to know about Django
this one. this one.
- `{% load trans from i18n %}` makes only `trans` available, not the rest of the library. - `{% load trans from i18n %}` makes only `trans` available, not the rest of the library.
A library's filters go in their own arity table (`L10N_FILTERS_WITHOUT_ARGUMENT`) rather than into the A library's filters go in their own arity tables (`L10N_FILTERS_WITHOUT_ARGUMENT`,
builtin ones, so that the builtin parity check still has an exact list to compare against. `TZ_FILTERS_WITH_ARGUMENT`, …) rather than into the builtin ones, so that the builtin parity check still has an exact list to compare against.
Modelling a library name makes it keyword-extracted, so it no longer reaches `custom_tag`. A project that Modelling a library name makes it keyword-extracted, so it no longer reaches `custom_tag`. A project that
registers its own tag under one of these names and a different signature therefore gets a parse error. That registers its own tag under one of these names and a different signature therefore gets a parse error. That
+38 -2
View File
@@ -229,6 +229,10 @@ const FILTERS_WITH_OPTIONAL_ARGUMENT = [
*/ */
const L10N_FILTERS_WITHOUT_ARGUMENT = ["localize", "unlocalize"]; const L10N_FILTERS_WITHOUT_ARGUMENT = ["localize", "unlocalize"];
/** django.templatetags.tz. */
const TZ_FILTERS_WITHOUT_ARGUMENT = ["localtime", "utc"];
const TZ_FILTERS_WITH_ARGUMENT = ["timezone"];
/** /**
* The words that head an end tag or a part of a tag group. Each is a token * The words that head an end tag or a part of a tag group. Each is a token
* only inside the group it belongs to, so anywhere else the lexer reads it as * only inside the group it belongs to, so anywhere else the lexer reads it as
@@ -249,8 +253,10 @@ const NAMES_INSIDE_A_TAG_GROUP = [
"endif", "endif",
"endifchanged", "endifchanged",
"endlocalize", "endlocalize",
"endlocaltime",
"endpartialdef", "endpartialdef",
"endspaceless", "endspaceless",
"endtimezone",
"endverbatim", "endverbatim",
"endwith", "endwith",
]; ];
@@ -382,6 +388,7 @@ const django = grammar({
$.filter_group, $.filter_group,
$.firstof, $.firstof,
$.for_group, $.for_group,
$.get_current_timezone,
$.get_media_prefix, $.get_media_prefix,
$.get_static_prefix, $.get_static_prefix,
$.if_group, $.if_group,
@@ -389,6 +396,7 @@ const django = grammar({
$.include, $.include,
$.load, $.load,
$.localize_group, $.localize_group,
$.localtime_group,
$.lorem, $.lorem,
$.now, $.now,
$.partial, $.partial,
@@ -399,6 +407,7 @@ const django = grammar({
$.spaceless_group, $.spaceless_group,
$.static, $.static,
$.template_tag_block, $.template_tag_block,
$.timezone_group,
$.url_block, $.url_block,
$.verbatim_group, $.verbatim_group,
$.width_ratio, $.width_ratio,
@@ -414,10 +423,17 @@ const django = grammar({
choice( choice(
field( field(
"name", "name",
choice(...FILTERS_WITHOUT_ARGUMENT, ...L10N_FILTERS_WITHOUT_ARGUMENT), choice(
...FILTERS_WITHOUT_ARGUMENT,
...L10N_FILTERS_WITHOUT_ARGUMENT,
...TZ_FILTERS_WITHOUT_ARGUMENT,
),
), ),
seq( seq(
field("name", choice(...FILTERS_WITH_ARGUMENT)), field(
"name",
choice(...FILTERS_WITH_ARGUMENT, ...TZ_FILTERS_WITH_ARGUMENT),
),
":", ":",
field("argument", $.value), field("argument", $.value),
), ),
@@ -519,6 +535,12 @@ const django = grammar({
optional(seq(block("empty"), optional($.template))), optional(seq(block("empty"), optional($.template))),
block("endfor"), block("endfor"),
), ),
/**
* django.templatetags.tz. get_current_timezone_tag requires exactly
* "as <name>", so unlike get_static_prefix the clause is not optional and
* nothing may follow it.
*/
get_current_timezone: ($) => block("get_current_timezone", asVariable($)),
get_media_prefix: ($) => block("get_media_prefix", optional(asVariable($))), get_media_prefix: ($) => block("get_media_prefix", optional(asVariable($))),
get_static_prefix: ($) => get_static_prefix: ($) =>
block("get_static_prefix", optional(asVariable($))), block("get_static_prefix", optional(asVariable($))),
@@ -557,6 +579,13 @@ const django = grammar({
optional($.template), optional($.template),
block("endlocalize"), block("endlocalize"),
), ),
/** django.templatetags.tz, taking "on" or "off" as localize does. */
localtime_group: ($) =>
seq(
block("localtime", optional(part(choice("on", "off")))),
optional($.template),
block("endlocaltime"),
),
lorem: ($) => lorem: ($) =>
block( block(
"lorem", "lorem",
@@ -603,6 +632,13 @@ const django = grammar({
), ),
), ),
), ),
/** django.templatetags.tz. timezone_tag takes the one argument. */
timezone_group: ($) =>
seq(
block("timezone", part($.filtered_value)),
optional($.template),
block("endtimezone"),
),
url_block: ($) => url_block: ($) =>
block( block(
"url", "url",
+8
View File
@@ -66,6 +66,7 @@ tag: [
("for") ("for")
("empty") ("empty")
("endfor") ("endfor")
("get_current_timezone")
("get_media_prefix") ("get_media_prefix")
("get_static_prefix") ("get_static_prefix")
("if") ("if")
@@ -79,6 +80,8 @@ tag: [
("load") ("load")
("localize") ("localize")
("endlocalize") ("endlocalize")
("localtime")
("endlocaltime")
("now") ("now")
("partial") ("partial")
("partialdef") ("partialdef")
@@ -90,6 +93,8 @@ tag: [
("endspaceless") ("endspaceless")
("static") ("static")
("templatetag") ("templatetag")
("timezone")
("endtimezone")
("url") ("url")
("verbatim") ("verbatim")
("endverbatim") ("endverbatim")
@@ -127,6 +132,7 @@ tag: [
("linenumbers") ("linenumbers")
("ljust") ("ljust")
("localize") ("localize")
("localtime")
("lower") ("lower")
("make_list") ("make_list")
("phone2numeric") ("phone2numeric")
@@ -143,6 +149,7 @@ tag: [
("time") ("time")
("timesince") ("timesince")
("timeuntil") ("timeuntil")
("timezone")
("title") ("title")
("truncatechars") ("truncatechars")
("truncatechars_html") ("truncatechars_html")
@@ -154,6 +161,7 @@ tag: [
("urlencode") ("urlencode")
("urlize") ("urlize")
("urlizetrunc") ("urlizetrunc")
("utc")
("wordcount") ("wordcount")
("wordwrap") ("wordwrap")
("yesno")] @function) ("yesno")] @function)
+15
View File
@@ -32,6 +32,21 @@
("localize") ("localize")
("unlocalize")] @library.l10n) ("unlocalize")] @library.l10n)
(localtime_group
tag: ("localtime") @library.tz)
(timezone_group
tag: ("timezone") @library.tz)
(get_current_timezone
tag: ("get_current_timezone") @library.tz)
(filter
name: [
("localtime")
("timezone")
("utc")] @library.tz)
; "{% load static %}" makes every name in the library available; "{% load ; "{% load static %}" makes every name in the library available; "{% load
; static from staticfiles %}" makes only the names listed available. ; static from staticfiles %}" makes only the names listed available.
(load (load
+364
View File
@@ -582,6 +582,10 @@
"type": "SYMBOL", "type": "SYMBOL",
"name": "for_group" "name": "for_group"
}, },
{
"type": "SYMBOL",
"name": "get_current_timezone"
},
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "get_media_prefix" "name": "get_media_prefix"
@@ -610,6 +614,10 @@
"type": "SYMBOL", "type": "SYMBOL",
"name": "localize_group" "name": "localize_group"
}, },
{
"type": "SYMBOL",
"name": "localtime_group"
},
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "lorem" "name": "lorem"
@@ -650,6 +658,10 @@
"type": "SYMBOL", "type": "SYMBOL",
"name": "template_tag_block" "name": "template_tag_block"
}, },
{
"type": "SYMBOL",
"name": "timezone_group"
},
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "url_block" "name": "url_block"
@@ -796,6 +808,14 @@
{ {
"type": "STRING", "type": "STRING",
"value": "unlocalize" "value": "unlocalize"
},
{
"type": "STRING",
"value": "localtime"
},
{
"type": "STRING",
"value": "utc"
} }
] ]
} }
@@ -888,6 +908,10 @@
{ {
"type": "STRING", "type": "STRING",
"value": "wordwrap" "value": "wordwrap"
},
{
"type": "STRING",
"value": "timezone"
} }
] ]
} }
@@ -2441,6 +2465,81 @@
} }
] ]
}, },
"get_current_timezone": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{%"
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "tag",
"content": {
"type": "STRING",
"value": "get_current_timezone"
}
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "as"
},
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "FIELD",
"name": "variable",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
}
]
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "%}"
}
]
},
"get_media_prefix": { "get_media_prefix": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
@@ -3406,6 +3505,143 @@
} }
] ]
}, },
"localtime_group": {
"type": "SEQ",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{%"
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "tag",
"content": {
"type": "STRING",
"value": "localtime"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "on"
},
{
"type": "STRING",
"value": "off"
}
]
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "%}"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "template"
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{%"
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "tag",
"content": {
"type": "STRING",
"value": "endlocaltime"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "%}"
}
]
}
]
},
"lorem": { "lorem": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
@@ -4411,6 +4647,126 @@
} }
] ]
}, },
"timezone_group": {
"type": "SEQ",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{%"
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "tag",
"content": {
"type": "STRING",
"value": "timezone"
}
},
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "SYMBOL",
"name": "filtered_value"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "%}"
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "template"
},
{
"type": "BLANK"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{%"
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "tag",
"content": {
"type": "STRING",
"value": "endtimezone"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "%}"
}
]
}
]
},
"url_block": { "url_block": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
@@ -5091,6 +5447,10 @@
"type": "STRING", "type": "STRING",
"value": "endlocalize" "value": "endlocalize"
}, },
{
"type": "STRING",
"value": "endlocaltime"
},
{ {
"type": "STRING", "type": "STRING",
"value": "endpartialdef" "value": "endpartialdef"
@@ -5099,6 +5459,10 @@
"type": "STRING", "type": "STRING",
"value": "endspaceless" "value": "endspaceless"
}, },
{
"type": "STRING",
"value": "endtimezone"
},
{ {
"type": "STRING", "type": "STRING",
"value": "endverbatim" "value": "endverbatim"
+138
View File
@@ -51,6 +51,10 @@
"type": "for_group", "type": "for_group",
"named": true "named": true
}, },
{
"type": "get_current_timezone",
"named": true
},
{ {
"type": "get_media_prefix", "type": "get_media_prefix",
"named": true "named": true
@@ -79,6 +83,10 @@
"type": "localize_group", "type": "localize_group",
"named": true "named": true
}, },
{
"type": "localtime_group",
"named": true
},
{ {
"type": "lorem", "type": "lorem",
"named": true "named": true
@@ -119,6 +127,10 @@
"type": "template_tag_block", "type": "template_tag_block",
"named": true "named": true
}, },
{
"type": "timezone_group",
"named": true
},
{ {
"type": "url_block", "type": "url_block",
"named": true "named": true
@@ -576,6 +588,10 @@
"type": "localize", "type": "localize",
"named": false "named": false
}, },
{
"type": "localtime",
"named": false
},
{ {
"type": "lower", "type": "lower",
"named": false "named": false
@@ -640,6 +656,10 @@
"type": "timeuntil", "type": "timeuntil",
"named": false "named": false
}, },
{
"type": "timezone",
"named": false
},
{ {
"type": "title", "type": "title",
"named": false "named": false
@@ -684,6 +704,10 @@
"type": "urlizetrunc", "type": "urlizetrunc",
"named": false "named": false
}, },
{
"type": "utc",
"named": false
},
{ {
"type": "wordcount", "type": "wordcount",
"named": false "named": false
@@ -878,6 +902,32 @@
] ]
} }
}, },
{
"type": "get_current_timezone",
"named": true,
"fields": {
"tag": {
"multiple": false,
"required": true,
"types": [
{
"type": "get_current_timezone",
"named": false
}
]
},
"variable": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
}
},
{ {
"type": "get_media_prefix", "type": "get_media_prefix",
"named": true, "named": true,
@@ -1134,6 +1184,36 @@
] ]
} }
}, },
{
"type": "localtime_group",
"named": true,
"fields": {
"tag": {
"multiple": true,
"required": true,
"types": [
{
"type": "endlocaltime",
"named": false
},
{
"type": "localtime",
"named": false
}
]
}
},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "template",
"named": true
}
]
}
},
{ {
"type": "lorem", "type": "lorem",
"named": true, "named": true,
@@ -1507,6 +1587,40 @@
] ]
} }
}, },
{
"type": "timezone_group",
"named": true,
"fields": {
"tag": {
"multiple": true,
"required": true,
"types": [
{
"type": "endtimezone",
"named": false
},
{
"type": "timezone",
"named": false
}
]
}
},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "filtered_value",
"named": true
},
{
"type": "template",
"named": true
}
]
}
},
{ {
"type": "translated_string", "type": "translated_string",
"named": true, "named": true,
@@ -1938,6 +2052,10 @@
"type": "endlocalize", "type": "endlocalize",
"named": false "named": false
}, },
{
"type": "endlocaltime",
"named": false
},
{ {
"type": "endpartialdef", "type": "endpartialdef",
"named": false "named": false
@@ -1946,6 +2064,10 @@
"type": "endspaceless", "type": "endspaceless",
"named": false "named": false
}, },
{
"type": "endtimezone",
"named": false
},
{ {
"type": "endverbatim", "type": "endverbatim",
"named": false "named": false
@@ -2002,6 +2124,10 @@
"type": "from", "type": "from",
"named": false "named": false
}, },
{
"type": "get_current_timezone",
"named": false
},
{ {
"type": "get_digit", "type": "get_digit",
"named": false "named": false
@@ -2086,6 +2212,10 @@
"type": "localize", "type": "localize",
"named": false "named": false
}, },
{
"type": "localtime",
"named": false
},
{ {
"type": "lorem", "type": "lorem",
"named": false "named": false
@@ -2266,6 +2396,10 @@
"type": "timeuntil", "type": "timeuntil",
"named": false "named": false
}, },
{
"type": "timezone",
"named": false
},
{ {
"type": "title", "type": "title",
"named": false "named": false
@@ -2314,6 +2448,10 @@
"type": "urlizetrunc", "type": "urlizetrunc",
"named": false "named": false
}, },
{
"type": "utc",
"named": false
},
{ {
"type": "verbatim", "type": "verbatim",
"named": false "named": false
+39990 -33897
View File
File diff suppressed because it is too large. Load diff
+256
View File
@@ -0,0 +1,256 @@
==================
Localtime
==================
{% localtime %}x{% endlocaltime %}
---
(template
(content)
(localtime_group
(template
(content)))
(content))
==================
Localtime On
==================
{% localtime on %}x{% endlocaltime %}
---
(template
(content)
(localtime_group
(template
(content)))
(content))
==================
Localtime Off
==================
{% localtime off %}x{% endlocaltime %}
---
(template
(content)
(localtime_group
(template
(content)))
(content))
==================
Localtime With Another Argument
:error
==================
{% localtime maybe %}x{% endlocaltime %}
---
==================
Localtime With Two Arguments
:error
==================
{% localtime on off %}x{% endlocaltime %}
---
==================
Timezone
==================
{% timezone "Europe/Paris" %}x{% endtimezone %}
---
(template
(content)
(timezone_group
(filtered_value
(value
(literal
(string))))
(template
(content)))
(content))
==================
Timezone With A Variable
==================
{% timezone tz %}x{% endtimezone %}
---
(template
(content)
(timezone_group
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content)))
(content))
==================
Timezone With A Filtered Argument
==================
{% timezone tz|upper %}x{% endtimezone %}
---
(template
(content)
(timezone_group
(filtered_value
(value
(variable_attribute
(identifier)))
(filter_expression
(filter)))
(template
(content)))
(content))
==================
Timezone Without An Argument
:error
==================
{% timezone %}x{% endtimezone %}
---
==================
Timezone With Two Arguments
:error
==================
{% timezone "a" "b" %}x{% endtimezone %}
---
==================
Get Current Timezone
==================
{% get_current_timezone as tz %}
---
(template
(content)
(get_current_timezone
variable: (identifier))
(content))
==================
Get Current Timezone Without As
:error
==================
{% get_current_timezone %}
---
==================
Get Current Timezone With Anything After As
:error
==================
{% get_current_timezone as tz junk %}
---
==================
Localtime Filter
==================
{{ x|localtime }}
---
(template
(content)
(template_variable
(filtered_value
(value
(variable_attribute
(identifier)))
(filter_expression
(filter))))
(content))
==================
Utc Filter
==================
{{ x|utc }}
---
(template
(content)
(template_variable
(filtered_value
(value
(variable_attribute
(identifier)))
(filter_expression
(filter))))
(content))
==================
Timezone Filter
==================
{{ x|timezone:"Europe/Paris" }}
---
(template
(content)
(template_variable
(filtered_value
(value
(variable_attribute
(identifier)))
(filter_expression
(filter
argument: (value
(literal
(string)))))))
(content))
==================
Timezone Filter Without Its Argument
:error
==================
{{ x|timezone }}
---
==================
Localtime Filter With An Argument
:error
==================
{{ x|localtime:1 }}
---