add support for custom tags and filters
This commit is contained in:
1 parent
10e52ace5d
commit
eb81b73104
7 files changed
+29216
-28773
No files matched your search
@@ -78,6 +78,23 @@ Django parses everything between `{{` and `}}` — and the entire remainder of `
|
|||||||
whitespace first, so a pipe there must be tight. Hence `_filtered_value_spaced` /
|
whitespace first, so a pipe there must be tight. Hence `_filtered_value_spaced` /
|
||||||
`_filter_expression_spaced`, aliased back to the plain node names so the tree shape stays identical.
|
`_filter_expression_spaced`, aliased back to the plain node names so the tree shape stays identical.
|
||||||
|
|
||||||
|
### Names a `{% load %}` brought in
|
||||||
|
|
||||||
|
A library's tags and filters are registered in Python, so the grammar cannot know their names, their
|
||||||
|
arity, or whether a tag is a block tag. Two fallback rules accept them:
|
||||||
|
|
||||||
|
- `custom_tag` — `simpleTag($, $.identifier)`, i.e. what `simple_tag`/`inclusion_tag` accept, which is how
|
||||||
|
a tag is registered unless it needs the parser itself.
|
||||||
|
- the last alternative of `filter`, whose name is `$.identifier` rather than one of the builtin literals.
|
||||||
|
|
||||||
|
**Builtin names must stay keyword-extractable, or the fallbacks swallow them.** `word: $.identifier` turns
|
||||||
|
each builtin's name into its own token, which the lexer prefers wherever it is valid, so a builtin commits
|
||||||
|
to its own alternative and its argument rules still apply. This is why a filter's name and its `:` are
|
||||||
|
separate tokens: while the name was spelled `"add:"` it was not word-shaped, so a bare `add` lexed as an
|
||||||
|
identifier and reached the fallback, losing the arity check for all 20 filters that require an argument.
|
||||||
|
`FILTERS_WITHOUT_ARGUMENT` / `FILTERS_WITH_ARGUMENT` / `FILTERS_WITH_OPTIONAL_ARGUMENT` carry that arity,
|
||||||
|
which Django itself enforces while it parses the template ("add requires 2 arguments, 1 provided").
|
||||||
|
|
||||||
### External scanner (`src/scanner.c`)
|
### External scanner (`src/scanner.c`)
|
||||||
|
|
||||||
Nine external tokens, for the constraints a context-free grammar cannot express:
|
Nine external tokens, for the constraints a context-free grammar cannot express:
|
||||||
@@ -99,7 +116,8 @@ do not, and belong in a linter instead.
|
|||||||
### Queries
|
### Queries
|
||||||
|
|
||||||
`queries/highlights.scm` lists tag and filter names explicitly, so adding either to the grammar means
|
`queries/highlights.scm` lists tag and filter names explicitly, so adding either to the grammar means
|
||||||
adding it there too. `queries/locals.scm` relies on the `variable:` field to tell a binding from a
|
adding it there too; filter names appear there without their colon. A name the grammar does not know is
|
||||||
|
captured through its field instead (`(filter name: (identifier))`, `(custom_tag tag: (identifier))`). `queries/locals.scm` relies on the `variable:` field to tell a binding from a
|
||||||
reference — another reason to keep `asVariable` uniform.
|
reference — another reason to keep `asVariable` uniform.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
@@ -137,5 +155,14 @@ documentation or memory. Both checks are worth repeating whenever tags, filters
|
|||||||
- `{% querystring page=2 page=3 %}` (a repeated `**kwargs` key) is a Django error but is not expressible in
|
- `{% querystring page=2 page=3 %}` (a repeated `**kwargs` key) is a Django error but is not expressible in
|
||||||
a context-free grammar. Repeats _are_ rejected wherever the signature names its arguments.
|
a context-free grammar. Repeats _are_ rejected wherever the signature names its arguments.
|
||||||
- `{% csp_nonce_attr "a" media="b" %}` is accepted, matching Django, which only rejects it at render time.
|
- `{% csp_nonce_attr "a" media="b" %}` is accepted, matching Django, which only rejects it at render time.
|
||||||
|
- A misspelled tag or filter cannot be caught: `{{ x|lenght }}` is indistinguishable from a filter some
|
||||||
|
library registered. Cross-referencing names against `{% load %}` needs the project's Python, so it
|
||||||
|
belongs in a linter; the `load` rule already parses the library and `from` names for one to use.
|
||||||
|
- `{% mytag %}…{% endmytag %}` parses as two sibling `custom_tag`s. Nesting them would mean guessing that
|
||||||
|
an unknown tag is a block tag.
|
||||||
|
- A custom tag's contents are only assumed to be the `simple_tag` shape. `@register.tag` receives the raw
|
||||||
|
token and may parse anything, so `{% mytag <<>> %}` is rejected here and not by Django.
|
||||||
|
- A library may override a builtin (`Parser.add_library` is `self.filters.update(...)`), so a project that
|
||||||
|
redefines `length` with a different arity gets a false error here.
|
||||||
- `partial`/`partialdef` are Django builtins as of Django 6; `elif`/`else`/`empty` are modelled as parts of
|
- `partial`/`partialdef` are Django builtins as of Django 6; `elif`/`else`/`empty` are modelled as parts of
|
||||||
their enclosing tag rather than as separate tags.
|
their enclosing tag rather than as separate tags.
|
||||||
+104
-59
@@ -38,7 +38,7 @@ function part(first, ...rest) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A tag, which may hug its delimiters ("{%cycle 1%}") or not.
|
* A tag, which may hug its delimiters ("{%cycle 1%}") or not.
|
||||||
* @param {string} tag
|
* @param {RuleOrLiteral} tag
|
||||||
* @param {...RuleOrLiteral} args
|
* @param {...RuleOrLiteral} args
|
||||||
* @returns {SeqRule}
|
* @returns {SeqRule}
|
||||||
*/
|
*/
|
||||||
@@ -121,7 +121,7 @@ function arrangements(items) {
|
|||||||
* @param {boolean|Record<string, RuleOrLiteral>} kwargs true for any
|
* @param {boolean|Record<string, RuleOrLiteral>} kwargs true for any
|
||||||
* "name=value" pair (**kwargs), false for none, or the accepted names mapped
|
* "name=value" pair (**kwargs), false for none, or the accepted names mapped
|
||||||
* to their value rules, each of which may be given at most once
|
* to their value rules, each of which may be given at most once
|
||||||
* @param {string} tag
|
* @param {RuleOrLiteral} tag
|
||||||
* @returns {SeqRule}
|
* @returns {SeqRule}
|
||||||
*/
|
*/
|
||||||
function simpleTag($, tag, args = true, kwargs = true) {
|
function simpleTag($, tag, args = true, kwargs = true) {
|
||||||
@@ -148,6 +148,79 @@ function simpleTag($, tag, args = true, kwargs = true) {
|
|||||||
return block(tag, ...parts, optional(asVariable($)));
|
return block(tag, ...parts, optional(asVariable($)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Django checks a filter's argument count against the registered function
|
||||||
|
* while it parses the template, so the arity of a builtin is syntax here:
|
||||||
|
* "length:2" and a bare "add" are both errors.
|
||||||
|
*/
|
||||||
|
const FILTERS_WITHOUT_ARGUMENT = [
|
||||||
|
"addslashes",
|
||||||
|
"capfirst",
|
||||||
|
"escape",
|
||||||
|
"escapejs",
|
||||||
|
"escapeseq",
|
||||||
|
"filesizeformat",
|
||||||
|
"first",
|
||||||
|
"force_escape",
|
||||||
|
"iriencode",
|
||||||
|
"last",
|
||||||
|
"length",
|
||||||
|
"linebreaks",
|
||||||
|
"linebreaksbr",
|
||||||
|
"linenumbers",
|
||||||
|
"lower",
|
||||||
|
"make_list",
|
||||||
|
"phone2numeric",
|
||||||
|
"pprint",
|
||||||
|
"random",
|
||||||
|
"safe",
|
||||||
|
"safeseq",
|
||||||
|
"slugify",
|
||||||
|
"striptags",
|
||||||
|
"title",
|
||||||
|
"unordered_list",
|
||||||
|
"upper",
|
||||||
|
"urlize",
|
||||||
|
"wordcount",
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Builtin filters that require an argument. */
|
||||||
|
const FILTERS_WITH_ARGUMENT = [
|
||||||
|
"add",
|
||||||
|
"center",
|
||||||
|
"cut",
|
||||||
|
"default",
|
||||||
|
"default_if_none",
|
||||||
|
"dictsort",
|
||||||
|
"dictsortreversed",
|
||||||
|
"divisibleby",
|
||||||
|
"get_digit",
|
||||||
|
"join",
|
||||||
|
"ljust",
|
||||||
|
"rjust",
|
||||||
|
"slice",
|
||||||
|
"stringformat",
|
||||||
|
"truncatechars",
|
||||||
|
"truncatechars_html",
|
||||||
|
"truncatewords",
|
||||||
|
"truncatewords_html",
|
||||||
|
"urlizetrunc",
|
||||||
|
"wordwrap",
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Builtin filters that take an argument or no argument. */
|
||||||
|
const FILTERS_WITH_OPTIONAL_ARGUMENT = [
|
||||||
|
"date",
|
||||||
|
"floatformat",
|
||||||
|
"json_script",
|
||||||
|
"pluralize",
|
||||||
|
"time",
|
||||||
|
"timesince",
|
||||||
|
"timeuntil",
|
||||||
|
"urlencode",
|
||||||
|
"yesno",
|
||||||
|
];
|
||||||
|
|
||||||
/** Django's string constant, which takes any escape but a line break. */
|
/** Django's string constant, which takes any escape but a line break. */
|
||||||
const STRING = /"(?:[^"\\]|\\[^\n])*"|'(?:[^'\\]|\\[^\n])*'/;
|
const STRING = /"(?:[^"\\]|\\[^\n])*"|'(?:[^'\\]|\\[^\n])*'/;
|
||||||
|
|
||||||
@@ -267,6 +340,7 @@ const django = grammar({
|
|||||||
$.comment_group,
|
$.comment_group,
|
||||||
$.csp_nonce_attr,
|
$.csp_nonce_attr,
|
||||||
$.csrf_token,
|
$.csrf_token,
|
||||||
|
$.custom_tag,
|
||||||
$.cycle,
|
$.cycle,
|
||||||
$.debug,
|
$.debug,
|
||||||
$.extends,
|
$.extends,
|
||||||
@@ -291,65 +365,30 @@ const django = grammar({
|
|||||||
$.width_ratio,
|
$.width_ratio,
|
||||||
$.with_group,
|
$.with_group,
|
||||||
),
|
),
|
||||||
|
/**
|
||||||
|
* A filter is a name and, after a colon, at most one argument. The name is
|
||||||
|
* a token of its own rather than part of a "name:" token so that Django's
|
||||||
|
* builtins are keyword-extracted: a builtin written with the wrong arity
|
||||||
|
* then cannot be read as a filter from a loaded library instead.
|
||||||
|
*/
|
||||||
filter: ($) =>
|
filter: ($) =>
|
||||||
choice(
|
choice(
|
||||||
seq("add:", $.value),
|
field("name", choice(...FILTERS_WITHOUT_ARGUMENT)),
|
||||||
"addslashes",
|
seq(
|
||||||
"capfirst",
|
field("name", choice(...FILTERS_WITH_ARGUMENT)),
|
||||||
seq("center:", $.value),
|
":",
|
||||||
seq("cut:", $.value),
|
field("argument", $.value),
|
||||||
choice(seq("date:", $.value), "date"),
|
),
|
||||||
seq("default:", $.value),
|
seq(
|
||||||
seq("default_if_none:", $.value),
|
field("name", choice(...FILTERS_WITH_OPTIONAL_ARGUMENT)),
|
||||||
seq("dictsort:", $.value),
|
optional(seq(":", field("argument", $.value))),
|
||||||
seq("dictsortreversed:", $.value),
|
),
|
||||||
seq("divisibleby:", $.value),
|
// a filter a "load" brought in: its name is not known here, and so
|
||||||
"escape",
|
// neither is whether it takes an argument
|
||||||
"escapejs",
|
seq(
|
||||||
"escapeseq",
|
field("name", $.identifier),
|
||||||
"filesizeformat",
|
optional(seq(":", field("argument", $.value))),
|
||||||
"first",
|
),
|
||||||
choice(seq("floatformat:", $.value), "floatformat"),
|
|
||||||
"force_escape",
|
|
||||||
seq("get_digit:", $.value),
|
|
||||||
"iriencode",
|
|
||||||
seq("join:", $.value),
|
|
||||||
choice(seq("json_script:", $.value), "json_script"),
|
|
||||||
"last",
|
|
||||||
"length",
|
|
||||||
"linebreaks",
|
|
||||||
"linebreaksbr",
|
|
||||||
"linenumbers",
|
|
||||||
seq("ljust:", $.value),
|
|
||||||
"lower",
|
|
||||||
"make_list",
|
|
||||||
"phone2numeric",
|
|
||||||
choice(seq("pluralize:", $.value), "pluralize"),
|
|
||||||
"pprint",
|
|
||||||
"random",
|
|
||||||
seq("rjust:", $.value),
|
|
||||||
"safe",
|
|
||||||
"safeseq",
|
|
||||||
seq("slice:", $.value),
|
|
||||||
"slugify",
|
|
||||||
seq("stringformat:", $.value),
|
|
||||||
"striptags",
|
|
||||||
choice(seq("time:", $.value), "time"),
|
|
||||||
choice(seq("timesince:", $.value), "timesince"),
|
|
||||||
choice(seq("timeuntil:", $.value), "timeuntil"),
|
|
||||||
"title",
|
|
||||||
seq("truncatechars:", $.value),
|
|
||||||
seq("truncatechars_html:", $.value),
|
|
||||||
seq("truncatewords:", $.value),
|
|
||||||
seq("truncatewords_html:", $.value),
|
|
||||||
"unordered_list",
|
|
||||||
"upper",
|
|
||||||
choice(seq("urlencode:", $.value), "urlencode"),
|
|
||||||
"urlize",
|
|
||||||
seq("urlizetrunc:", $.value),
|
|
||||||
"wordcount",
|
|
||||||
seq("wordwrap:", $.value),
|
|
||||||
choice(seq("yesno:", $.value), "yesno"),
|
|
||||||
),
|
),
|
||||||
autoescape_group: ($) =>
|
autoescape_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
@@ -374,6 +413,12 @@ const django = grammar({
|
|||||||
csp_nonce_attr: ($) =>
|
csp_nonce_attr: ($) =>
|
||||||
simpleTag($, "csp_nonce_attr", 1, { media: $.filtered_value }),
|
simpleTag($, "csp_nonce_attr", 1, { media: $.filtered_value }),
|
||||||
csrf_token: ($) => block("csrf_token"),
|
csrf_token: ($) => block("csrf_token"),
|
||||||
|
/**
|
||||||
|
* A tag a "load" brought in. Its arguments cannot be known here, so what
|
||||||
|
* is accepted is what Library.simple_tag and Library.inclusion_tag take,
|
||||||
|
* which is how a tag is registered unless it needs the parser itself.
|
||||||
|
*/
|
||||||
|
custom_tag: ($) => simpleTag($, $.identifier),
|
||||||
cycle: ($) =>
|
cycle: ($) =>
|
||||||
block(
|
block(
|
||||||
"cycle",
|
"cycle",
|
||||||
|
|||||||
+31
-32
@@ -25,6 +25,7 @@ variable: (identifier) @variable.parameter
|
|||||||
("in")
|
("in")
|
||||||
("=")
|
("=")
|
||||||
("|")
|
("|")
|
||||||
|
(":")
|
||||||
(".")] @operator
|
(".")] @operator
|
||||||
|
|
||||||
[
|
[
|
||||||
@@ -90,74 +91,72 @@ tag: [
|
|||||||
("with")
|
("with")
|
||||||
("endwith")] @function
|
("endwith")] @function
|
||||||
|
|
||||||
(filter .
|
(filter
|
||||||
[
|
name: [
|
||||||
("add:")
|
("add")
|
||||||
("addslashes")
|
("addslashes")
|
||||||
("capfirst")
|
("capfirst")
|
||||||
("center:")
|
("center")
|
||||||
("cut:")
|
("cut")
|
||||||
("date")
|
("date")
|
||||||
("date:")
|
("default")
|
||||||
("default:")
|
("default_if_none")
|
||||||
("default_if_none:")
|
("dictsort")
|
||||||
("dictsort:")
|
("dictsortreversed")
|
||||||
("dictsortreversed:")
|
("divisibleby")
|
||||||
("divisibleby:")
|
|
||||||
("escape")
|
("escape")
|
||||||
("escapejs")
|
("escapejs")
|
||||||
("escapeseq")
|
("escapeseq")
|
||||||
("filesizeformat")
|
("filesizeformat")
|
||||||
("first")
|
("first")
|
||||||
("floatformat")
|
("floatformat")
|
||||||
("floatformat:")
|
|
||||||
("force_escape")
|
("force_escape")
|
||||||
("get_digit:")
|
("get_digit")
|
||||||
("iriencode")
|
("iriencode")
|
||||||
("join:")
|
("join")
|
||||||
("json_script")
|
("json_script")
|
||||||
("json_script:")
|
|
||||||
("last")
|
("last")
|
||||||
("length")
|
("length")
|
||||||
("linebreaks")
|
("linebreaks")
|
||||||
("linebreaksbr")
|
("linebreaksbr")
|
||||||
("linenumbers")
|
("linenumbers")
|
||||||
("ljust:")
|
("ljust")
|
||||||
("lower")
|
("lower")
|
||||||
("make_list")
|
("make_list")
|
||||||
("phone2numeric")
|
("phone2numeric")
|
||||||
("pluralize")
|
("pluralize")
|
||||||
("pluralize:")
|
|
||||||
("pprint")
|
("pprint")
|
||||||
("random")
|
("random")
|
||||||
("rjust:")
|
("rjust")
|
||||||
("safe")
|
("safe")
|
||||||
("safeseq")
|
("safeseq")
|
||||||
("slice:")
|
("slice")
|
||||||
("slugify")
|
("slugify")
|
||||||
("stringformat:")
|
("stringformat")
|
||||||
("striptags")
|
("striptags")
|
||||||
("time")
|
("time")
|
||||||
("time:")
|
|
||||||
("timesince")
|
("timesince")
|
||||||
("timesince:")
|
|
||||||
("timeuntil")
|
("timeuntil")
|
||||||
("timeuntil:")
|
|
||||||
("title")
|
("title")
|
||||||
("truncatechars:")
|
("truncatechars")
|
||||||
("truncatechars_html:")
|
("truncatechars_html")
|
||||||
("truncatewords:")
|
("truncatewords")
|
||||||
("truncatewords_html:")
|
("truncatewords_html")
|
||||||
("unordered_list")
|
("unordered_list")
|
||||||
("upper")
|
("upper")
|
||||||
("urlencode")
|
("urlencode")
|
||||||
("urlencode:")
|
|
||||||
("urlize")
|
("urlize")
|
||||||
("urlizetrunc:")
|
("urlizetrunc")
|
||||||
("wordcount")
|
("wordcount")
|
||||||
("wordwrap:")
|
("wordwrap")
|
||||||
("yesno")
|
("yesno")] @function)
|
||||||
("yesno:")] @function)
|
|
||||||
|
; a filter or tag a "load" brought in, whose name the grammar does not know
|
||||||
|
(filter
|
||||||
|
name: (identifier) @function.call)
|
||||||
|
|
||||||
|
(custom_tag
|
||||||
|
tag: (identifier) @function.call)
|
||||||
|
|
||||||
(load
|
(load
|
||||||
(identifier) @function)
|
(identifier) @function)
|
||||||
+451
-535
File diff suppressed because it is too large.
Load diff
+317
-66
@@ -23,6 +23,10 @@
|
|||||||
"type": "csrf_token",
|
"type": "csrf_token",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "custom_tag",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "cycle",
|
"type": "cycle",
|
||||||
"named": true
|
"named": true
|
||||||
@@ -300,6 +304,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "custom_tag",
|
||||||
|
"named": true,
|
||||||
|
"fields": {
|
||||||
|
"tag": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": true,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "identifier",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"variable": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": false,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "identifier",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": {
|
||||||
|
"multiple": true,
|
||||||
|
"required": false,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "filtered_value",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "identifier",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "cycle",
|
"type": "cycle",
|
||||||
"named": true,
|
"named": true,
|
||||||
@@ -381,16 +425,255 @@
|
|||||||
{
|
{
|
||||||
"type": "filter",
|
"type": "filter",
|
||||||
"named": true,
|
"named": true,
|
||||||
"fields": {},
|
"fields": {
|
||||||
"children": {
|
"argument": {
|
||||||
"multiple": false,
|
"multiple": false,
|
||||||
"required": false,
|
"required": false,
|
||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
"type": "value",
|
"type": "value",
|
||||||
"named": true
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": true,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "add",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addslashes",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "capfirst",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "center",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "cut",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "date",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "default",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "default_if_none",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dictsort",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dictsortreversed",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "divisibleby",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "escape",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "escapejs",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "escapeseq",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "filesizeformat",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "first",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "floatformat",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "force_escape",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "get_digit",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "identifier",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "iriencode",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "join",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "json_script",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "last",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "length",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "linebreaks",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "linebreaksbr",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "linenumbers",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ljust",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lower",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "make_list",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "phone2numeric",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pluralize",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pprint",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "random",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rjust",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "safe",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "safeseq",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "slice",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "slugify",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "stringformat",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "striptags",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "time",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timesince",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timeuntil",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "title",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "truncatechars",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "truncatechars_html",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "truncatewords",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "truncatewords_html",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unordered_list",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "upper",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "urlencode",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "urlize",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "urlizetrunc",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wordcount",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wordwrap",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "yesno",
|
||||||
|
"named": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1317,6 +1600,10 @@
|
|||||||
"type": ".",
|
"type": ".",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": ":",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "<",
|
"type": "<",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -1346,7 +1633,7 @@
|
|||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "add:",
|
"type": "add",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1386,7 +1673,7 @@
|
|||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "center:",
|
"type": "center",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1426,7 +1713,7 @@
|
|||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "cut:",
|
"type": "cut",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1437,32 +1724,28 @@
|
|||||||
"type": "date",
|
"type": "date",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "date:",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "debug",
|
"type": "debug",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "default:",
|
"type": "default",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "default_if_none:",
|
"type": "default_if_none",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "dictsort:",
|
"type": "dictsort",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "dictsortreversed:",
|
"type": "dictsortreversed",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "divisibleby:",
|
"type": "divisibleby",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1557,10 +1840,6 @@
|
|||||||
"type": "floatformat",
|
"type": "floatformat",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "floatformat:",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "for",
|
"type": "for",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -1574,7 +1853,7 @@
|
|||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "get_digit:",
|
"type": "get_digit",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1610,17 +1889,13 @@
|
|||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "join:",
|
"type": "join",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "json_script",
|
"type": "json_script",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "json_script:",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "last",
|
"type": "last",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -1642,7 +1917,7 @@
|
|||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "ljust:",
|
"type": "ljust",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1725,10 +2000,6 @@
|
|||||||
"type": "pluralize",
|
"type": "pluralize",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "pluralize:",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "pop_block",
|
"type": "pop_block",
|
||||||
"named": true
|
"named": true
|
||||||
@@ -1774,7 +2045,7 @@
|
|||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "rjust:",
|
"type": "rjust",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1790,7 +2061,7 @@
|
|||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "slice:",
|
"type": "slice",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1806,7 +2077,7 @@
|
|||||||
"named": true
|
"named": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "stringformat:",
|
"type": "stringformat",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1821,44 +2092,32 @@
|
|||||||
"type": "time",
|
"type": "time",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "time:",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "timesince",
|
"type": "timesince",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "timesince:",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "timeuntil",
|
"type": "timeuntil",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "timeuntil:",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "title",
|
"type": "title",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "truncatechars:",
|
"type": "truncatechars",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "truncatechars_html:",
|
"type": "truncatechars_html",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "truncatewords:",
|
"type": "truncatewords",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "truncatewords_html:",
|
"type": "truncatewords_html",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1877,16 +2136,12 @@
|
|||||||
"type": "urlencode",
|
"type": "urlencode",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "urlencode:",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "urlize",
|
"type": "urlize",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "urlizetrunc:",
|
"type": "urlizetrunc",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1914,17 +2169,13 @@
|
|||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "wordwrap:",
|
"type": "wordwrap",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "yesno",
|
"type": "yesno",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "yesno:",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "{#",
|
"type": "{#",
|
||||||
"named": false
|
"named": false
|
||||||
|
|||||||
+28054
-28080
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,231 @@
|
|||||||
|
==================
|
||||||
|
Custom Tag
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% mytag %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(custom_tag
|
||||||
|
tag: (identifier))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Custom Tag With Arguments
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% mytag a "b" 3 %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(custom_tag
|
||||||
|
tag: (identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(literal
|
||||||
|
(string))))
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(literal
|
||||||
|
(number)))))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Custom Tag With Keyword Arguments
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% mytag a b=1 %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(custom_tag
|
||||||
|
tag: (identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))
|
||||||
|
(identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(literal
|
||||||
|
(number)))))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Custom Tag With As
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% mytag a as result %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(custom_tag
|
||||||
|
tag: (identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))
|
||||||
|
variable: (identifier))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Custom Tag With Filtered Argument
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% mytag a|upper %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(custom_tag
|
||||||
|
tag: (identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))
|
||||||
|
(filter_expression
|
||||||
|
(filter))))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Custom Tag Does Not Nest
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% mytag %}hi{% endmytag %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(custom_tag
|
||||||
|
tag: (identifier))
|
||||||
|
(content)
|
||||||
|
(custom_tag
|
||||||
|
tag: (identifier))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Custom Tag Does Not Cover A Builtin Name
|
||||||
|
:error
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% cycle %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
==================
|
||||||
|
Custom Filter
|
||||||
|
==================
|
||||||
|
|
||||||
|
{{ x|intcomma }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(template_variable
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))
|
||||||
|
(filter_expression
|
||||||
|
(filter
|
||||||
|
name: (identifier)))))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Custom Filter With Argument
|
||||||
|
==================
|
||||||
|
|
||||||
|
{{ x|naturaltime:"y" }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(template_variable
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))
|
||||||
|
(filter_expression
|
||||||
|
(filter
|
||||||
|
name: (identifier)
|
||||||
|
argument: (value
|
||||||
|
(literal
|
||||||
|
(string)))))))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Custom Filter In A Chain With A Builtin
|
||||||
|
==================
|
||||||
|
|
||||||
|
{{ x|intcomma|upper }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(template_variable
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))
|
||||||
|
(filter_expression
|
||||||
|
(filter
|
||||||
|
name: (identifier))
|
||||||
|
(filter))))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Custom Filter In A Tag
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% firstof a|intcomma %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(firstof
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))
|
||||||
|
(filter_expression
|
||||||
|
(filter
|
||||||
|
name: (identifier)))))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Builtin Filter Without Its Required Argument
|
||||||
|
:error
|
||||||
|
==================
|
||||||
|
|
||||||
|
{{ x|add }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
==================
|
||||||
|
Builtin Filter With An Argument It Does Not Take
|
||||||
|
:error
|
||||||
|
==================
|
||||||
|
|
||||||
|
{{ x|length:2 }}
|
||||||
|
|
||||||
|
---
|
||||||
Reference in new issue
Block a user