Compare commits
53
Commits
25c4be568f
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2181ff3403 | ||
|
|
b154625f1b | ||
|
|
1ac669eee9 | ||
|
|
a3de44f4ca | ||
|
|
96e7b2a9c5 | ||
|
|
99123289f7 | ||
|
|
13c774dedb | ||
|
|
969ba61e88 | ||
|
|
5183f6aa52 | ||
|
|
53e1ee2036 | ||
|
|
0e8ef8e7a5 | ||
|
|
4a79d32f6a | ||
|
|
7979890227 | ||
|
|
ccb80972b6 | ||
|
|
fa0d880e68 | ||
|
|
831acd3516 | ||
|
|
f17fdd2d74 | ||
|
|
5f66027dcf | ||
|
|
70543ffe3b | ||
|
|
9f2fdaf746 | ||
|
|
cb8c9f9c0b | ||
|
|
036928d628 | ||
|
|
b5fbd86fd5 | ||
|
|
1648dc82f3 | ||
|
|
2d7d0db471 | ||
|
|
f2d3a727f6 | ||
|
|
db3caa1672 | ||
|
|
22362ed393 | ||
|
|
9a254328f6 | ||
|
|
1f556f6687 | ||
|
|
b239bf6509 | ||
|
|
9701fab04a | ||
|
|
913ca69e6c | ||
|
|
c0a2e0099c | ||
|
|
606bf7457f | ||
|
|
aceae012fa | ||
|
|
ca01dd5f95 | ||
|
|
06aa8706c8 | ||
|
|
356cc3a885 | ||
|
|
6105fd9002 | ||
|
|
0caa8c104b | ||
|
|
f93938c277 | ||
|
|
516318051f | ||
|
|
d4ef9fc20a | ||
|
|
709a33f52d | ||
|
|
7e71c5e3ae | ||
|
|
7eb95e4e84 | ||
|
|
c0e7b4697d | ||
|
|
eb81b73104 | ||
|
|
10e52ace5d | ||
|
|
00547dba33 | ||
|
|
fc36d7d7b2 | ||
|
|
e357dc17ad |
No files matched your search
@@ -0,0 +1,50 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
|
||||
[*.{json,toml,yml,gyp,xml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{js,ts}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.scm]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{c,cc,h}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.rs]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{py,pyi}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.swift]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.java]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[parser.c]
|
||||
indent_size = 2
|
||||
|
||||
[{alloc,array,parser}.h]
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,46 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
# Generated source files
|
||||
src/*.json linguist-generated
|
||||
src/parser.c linguist-generated
|
||||
src/tree_sitter/* linguist-generated
|
||||
|
||||
# C bindings
|
||||
bindings/c/** linguist-generated
|
||||
CMakeLists.txt linguist-generated
|
||||
Makefile linguist-generated
|
||||
|
||||
# Rust bindings
|
||||
bindings/rust/* linguist-generated
|
||||
Cargo.toml linguist-generated
|
||||
Cargo.lock linguist-generated
|
||||
|
||||
# Node.js bindings
|
||||
bindings/node/* linguist-generated
|
||||
binding.gyp linguist-generated
|
||||
package.json linguist-generated
|
||||
package-lock.json linguist-generated
|
||||
|
||||
# Python bindings
|
||||
bindings/python/** linguist-generated
|
||||
setup.py linguist-generated
|
||||
pyproject.toml linguist-generated
|
||||
|
||||
# Go bindings
|
||||
bindings/go/* linguist-generated
|
||||
go.mod linguist-generated
|
||||
go.sum linguist-generated
|
||||
|
||||
# Swift bindings
|
||||
bindings/swift/** linguist-generated
|
||||
Package.swift linguist-generated
|
||||
Package.resolved linguist-generated
|
||||
|
||||
# Zig bindings
|
||||
bindings/zig/* linguist-generated
|
||||
build.zig linguist-generated
|
||||
build.zig.zon linguist-generated
|
||||
|
||||
# Java bindings
|
||||
pom.xml linguist-generated
|
||||
bindings/java/** linguist-generated
|
||||
@@ -0,0 +1,91 @@
|
||||
name: CI
|
||||
|
||||
# Pull requests run this directly; pushes to main reach it through release.yml,
|
||||
# which calls this workflow before it tags and publishes.
|
||||
on:
|
||||
pull_request:
|
||||
workflow_call:
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
parser:
|
||||
name: Corpus and generated files
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Corpus tests
|
||||
run: npm run parser-test
|
||||
- name: src/ matches grammar.js
|
||||
run: |
|
||||
npm run parser-generate
|
||||
if ! git diff --exit-code --stat src/; then
|
||||
echo "::error::src/ is out of date: run 'npm run parser-generate' and commit the result"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
node:
|
||||
name: Node binding (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
# windows-latest is Windows Server 2025 with Visual Studio 2026, which the
|
||||
# node-gyp bundled with the runner's npm (11.5) reports as "unknown
|
||||
# version undefined" and then gives up on. node-gyp learned VS 18 in
|
||||
# 12.1.0 and npm 12 bundles 13.
|
||||
- if: runner.os == 'Windows'
|
||||
run: npm install -g npm@^12
|
||||
# npm ci builds the addon through the install script (node-gyp-build)
|
||||
- run: npm ci
|
||||
- run: npm run binding-test
|
||||
|
||||
rust:
|
||||
name: Rust binding
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- run: cargo test
|
||||
|
||||
python:
|
||||
name: Python binding
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- run: python -m pip install --upgrade pip
|
||||
- run: python -m pip install . "tree-sitter~=0.24"
|
||||
- run: python -m unittest discover -s bindings/python/tests -v
|
||||
|
||||
go:
|
||||
name: Go binding
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
- run: go test ./bindings/go/
|
||||
|
||||
swift:
|
||||
name: Swift binding
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- run: swift build
|
||||
@@ -0,0 +1,245 @@
|
||||
name: Release
|
||||
|
||||
# Releasing is a version bump: push a commit to main that changes the version in
|
||||
# package.json, and this tags v<version>, creates the GitHub release and stages
|
||||
# the npm publish. Every other push to main only runs the tests.
|
||||
#
|
||||
# Staging is not publishing: "npm stage publish" hands npm the tarball and nobody
|
||||
# can install it until a maintainer runs "npm stage approve <stage-id>" and
|
||||
# answers a 2FA prompt. That is the point — npm is deprecating the tokens that
|
||||
# let a workflow publish on its own, and staging defers the proof of presence
|
||||
# instead of removing it. The job summary carries the command to run.
|
||||
#
|
||||
# Running it by hand instead stages whatever version package.json holds, as long
|
||||
# as npm has neither published nor staged it yet, and skips the tag and the
|
||||
# release if a previous attempt already made them. That is how a half-finished
|
||||
# release is retried.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
version:
|
||||
name: Version
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.check.outputs.version }}
|
||||
changed: ${{ steps.check.outputs.changed }}
|
||||
tag_exists: ${{ steps.check.outputs.tag_exists }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 2
|
||||
fetch-tags: true
|
||||
- id: check
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
name="$(node -p "require('./package.json').name")"
|
||||
version="$(node -p "require('./package.json').version")"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if git rev-parse -q --verify "refs/tags/v$version" >/dev/null; then
|
||||
tag_exists=true
|
||||
else
|
||||
tag_exists=false
|
||||
fi
|
||||
echo "tag_exists=$tag_exists" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
|
||||
# by hand: stage what package.json says, unless npm has it already.
|
||||
# A version staged but not yet approved is invisible to npm view; the
|
||||
# stage job looks for that one itself.
|
||||
if npm view "$name@$version" version >/dev/null 2>&1; then
|
||||
echo "::warning::$name@$version is already on npm; nothing to stage"
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "staging $name@$version by hand (tag exists: $tag_exists)"
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if git cat-file -e HEAD~1:package.json 2>/dev/null; then
|
||||
previous="$(git show HEAD~1:package.json | node -p "JSON.parse(require('node:fs').readFileSync(0, 'utf8')).version")"
|
||||
else
|
||||
previous=""
|
||||
fi
|
||||
if [ "$version" = "$previous" ]; then
|
||||
echo "version is unchanged at $version; not releasing"
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
elif [ "$tag_exists" = "true" ]; then
|
||||
echo "::warning::tag v$version already exists; not releasing"
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "releasing $previous -> $version"
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
ci:
|
||||
name: CI
|
||||
needs: version
|
||||
uses: ./.github/workflows/ci.yml
|
||||
|
||||
prebuild:
|
||||
name: Prebuild (${{ matrix.os }})
|
||||
needs: [version, ci]
|
||||
if: needs.version.outputs.changed == 'true'
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
# one runner per platform and architecture npm should ship a binary for;
|
||||
# the arm Linux runner is free for public repositories only
|
||||
os:
|
||||
[
|
||||
ubuntu-latest,
|
||||
ubuntu-24.04-arm,
|
||||
macos-latest,
|
||||
windows-latest,
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
# the Windows runner's own npm bundles a node-gyp too old to see the
|
||||
# Visual Studio 2026 on that image; see ci.yml
|
||||
- if: runner.os == 'Windows'
|
||||
run: npm install -g npm@^12
|
||||
- run: npm ci
|
||||
- run: npm run prebuildify
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: prebuilds-${{ matrix.os }}
|
||||
path: prebuilds/
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Tag and stage
|
||||
needs: [version, prebuild]
|
||||
if: needs.version.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # the tag and the release
|
||||
id-token: write # the OIDC exchange, and provenance for npm
|
||||
env:
|
||||
VERSION: ${{ needs.version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
registry-url: https://registry.npmjs.org
|
||||
- name: Collect the prebuilds
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: prebuilds-*
|
||||
path: prebuilds/
|
||||
merge-multiple: true
|
||||
- run: ls -R prebuilds/
|
||||
- run: npm ci
|
||||
# tree-sitter build --wasm needs Emscripten; the CLI runs it through
|
||||
# Docker, which the ubuntu runners have
|
||||
- name: Build the Wasm parser
|
||||
run: npm run parser-build
|
||||
# a retry of a half-finished release finds its tag and release already made
|
||||
- name: Tag
|
||||
if: needs.version.outputs.tag_exists != 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git tag -a "v$VERSION" -m "v$VERSION"
|
||||
git push origin "v$VERSION"
|
||||
- name: Release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if gh release view "v$VERSION" >/dev/null 2>&1; then
|
||||
echo "release v$VERSION exists; replacing its Wasm build"
|
||||
gh release upload "v$VERSION" tree-sitter-django.wasm --clobber
|
||||
else
|
||||
gh release create "v$VERSION" --title "v$VERSION" --generate-notes tree-sitter-django.wasm
|
||||
fi
|
||||
# "npm stage" arrived in npm 12, which is newer than the npm any runner
|
||||
# image ships with
|
||||
- name: Install npm 12
|
||||
run: npm install -g npm@^12
|
||||
# staged versions share npm's version index with published ones, so a
|
||||
# second stage of the same version is an error rather than a no-op. A run
|
||||
# that staged and then failed later should find its stage and go on.
|
||||
- name: Look for an existing stage
|
||||
id: staged
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
set -uo pipefail
|
||||
name="$(node -p "require('./package.json').name")"
|
||||
# a short-lived OIDC token cannot run "npm stage list" at all, so a
|
||||
# failure here means "ask npm by staging", not "stop"
|
||||
if ! items="$(npm stage list "$name" --json 2>/dev/null)"; then
|
||||
echo "could not list staged versions; assuming there are none"
|
||||
echo "staged=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
id="$(node -e '
|
||||
const items = JSON.parse(require("node:fs").readFileSync(0, "utf8"));
|
||||
const match = items.find((item) => item.version === process.env.VERSION);
|
||||
if (match) console.log(match.id);
|
||||
' <<< "$items")"
|
||||
if [ -n "$id" ]; then
|
||||
echo "::warning::$name@$VERSION is already staged as $id; not staging it again"
|
||||
echo "staged=true" >> "$GITHUB_OUTPUT"
|
||||
echo "stage_id=$id" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "staged=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
# NPM_TOKEN is the fallback: where the package has a trusted publisher for
|
||||
# this workflow, npm exchanges the OIDC token above for one of its own and
|
||||
# the secret is not read at all.
|
||||
- name: Stage the publish
|
||||
id: stage
|
||||
if: steps.staged.outputs.staged != 'true'
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
npm stage publish --provenance --access public 2>&1 | tee stage.log
|
||||
id="$(sed -n 's/.*staged with id \([0-9a-fA-F-]\{36\}\).*/\1/p' stage.log | tail -n1)"
|
||||
echo "stage_id=$id" >> "$GITHUB_OUTPUT"
|
||||
- name: Say how to approve it
|
||||
env:
|
||||
STAGE_ID: ${{ steps.stage.outputs.stage_id || steps.staged.outputs.stage_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
name="$(node -p "require('./package.json').name")"
|
||||
{
|
||||
echo "### $name@$VERSION is staged, not published"
|
||||
echo
|
||||
echo "npm has the tarball and nobody can install it until a maintainer approves it, which asks for 2FA:"
|
||||
echo
|
||||
echo '```sh'
|
||||
if [ -n "$STAGE_ID" ]; then
|
||||
echo "npm stage view $STAGE_ID # what was staged"
|
||||
echo "npm stage download $STAGE_ID # the tarball itself"
|
||||
echo "npm stage approve $STAGE_ID # publish it"
|
||||
echo "npm stage reject $STAGE_ID # throw it away"
|
||||
else
|
||||
echo "npm stage list $name"
|
||||
echo "npm stage approve <stage-id>"
|
||||
fi
|
||||
echo '```'
|
||||
echo
|
||||
echo "Approving needs npm 12: \`npm install -g npm@^12\`."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
+28
@@ -1,2 +1,30 @@
|
||||
node_modules
|
||||
tree-sitter-django.wasm
|
||||
|
||||
# Node binding
|
||||
build/
|
||||
prebuilds/
|
||||
*.node
|
||||
|
||||
# Rust binding
|
||||
target/
|
||||
Cargo.lock
|
||||
|
||||
# Python binding
|
||||
dist/
|
||||
*.egg-info/
|
||||
__pycache__/
|
||||
*.so
|
||||
*.pyd
|
||||
|
||||
# C binding
|
||||
*.o
|
||||
*.a
|
||||
*.dylib
|
||||
libtree-sitter-django.*
|
||||
|
||||
# Swift binding
|
||||
.build/
|
||||
Package.resolved
|
||||
*.pc
|
||||
*.tgz
|
||||
@@ -0,0 +1,598 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
A tree-sitter grammar for the Django Template Language: `{% %}` tags and `{{ }}` variables.
|
||||
|
||||
## Commands
|
||||
|
||||
```sh
|
||||
npm run parser-generate # regenerate src/parser.c, grammar.json, node-types.json from grammar.js
|
||||
npm run parser-test # run the corpus tests
|
||||
npm run parser-build # build tree-sitter-django.wasm
|
||||
npm run playground # explore the grammar interactively
|
||||
npm install # also builds the Node binding, through node-gyp-build
|
||||
npm run binding-test # the Node binding's own test
|
||||
cargo test # the Rust binding
|
||||
make # the C library, and make clean to remove it
|
||||
|
||||
npx tree-sitter test -i "Cycle With As" # one test, by name regex
|
||||
npx tree-sitter test --file-name cycle.txt # one corpus file
|
||||
npx tree-sitter test -r # force a rebuild of the parser library
|
||||
npx tree-sitter parse file.html # parse one file
|
||||
npx tree-sitter parse --debug normal f.html # lex/shift/reduce trace
|
||||
npx prettier --write grammar.js queries test CLAUDE.md # not `.` — see below
|
||||
```
|
||||
|
||||
`src/parser.c`, `src/grammar.json` and `src/node-types.json` are generated **and committed**, so run
|
||||
`parser-generate` after every change to `grammar.js` or `src/scanner.c`.
|
||||
|
||||
Format with prettier over specific paths rather than `npm run format` (`prettier --write .`): that also
|
||||
rewrites the generated `src/grammar.json` and `src/node-types.json`, which `parser-generate` then writes
|
||||
back, producing churn in both directions.
|
||||
|
||||
`npm run lint` does not work: `eslint.config.mjs` was copied from a Next.js project and enables
|
||||
`@typescript-eslint` and `react-hooks` rules whose plugins aren't installed. Formatting is prettier's job.
|
||||
|
||||
### Bindings
|
||||
|
||||
`bindings/` and the manifests beside it — `binding.gyp`, `Cargo.toml`, `pyproject.toml`, `setup.py`,
|
||||
`go.mod`, `Package.swift`, `Makefile`, `CMakeLists.txt` — are generated by `tree-sitter init` from the
|
||||
`bindings` and `metadata` sections of `tree-sitter.json`, and `.gitattributes` marks them
|
||||
`linguist-generated`. Do not edit them by hand: change `tree-sitter.json` and regenerate.
|
||||
|
||||
`tree-sitter init --update` only rewrites what it considers outdated, and a changed `version` or
|
||||
`description` does not count, so the manifests keep the old values until they are deleted and `init` is run
|
||||
again. It leaves `README.md`, `package.json` and `.gitignore` alone, which is why the Node binding's
|
||||
`package.json` entries — `main`/`types`, the `.` export, `files`, `node-addon-api`, `node-gyp-build`, the
|
||||
`install` script and the optional `tree-sitter` peer — are maintained here rather than generated.
|
||||
|
||||
Every binding was built and run on macOS: `npm install` plus `npm run binding-test`, `cargo test`,
|
||||
`go test ./bindings/go/`, `pip install .` with a parse, `swift build`, and `make`. The Go binding needs a
|
||||
`go.sum`, which `go mod tidy` writes and which is committed with the rest.
|
||||
|
||||
`src/scanner.c` compiles without warnings, and should stay that way: cc and clang warn about `&&` within
|
||||
`||` (`-Wlogical-op-parentheses`), so a comparison chain written with both needs its `&&` groups
|
||||
parenthesized.
|
||||
|
||||
### CI and releasing
|
||||
|
||||
`.github/workflows/ci.yml` runs on pull requests, and the release workflow calls it: the corpus tests, a
|
||||
check that `npm run parser-generate` leaves `src/` unchanged, the Node binding on Linux, macOS and Windows,
|
||||
and the Rust, Python, Go and Swift bindings.
|
||||
|
||||
`.github/workflows/release.yml` runs on every push to `main` but only releases when `package.json`'s version
|
||||
differs from the previous commit's and no `v<version>` tag exists yet. Then it runs CI, `prebuildify` on
|
||||
five runners (Linux x64 and arm64, macOS x64 and arm64, Windows x64), and a last job that tags `v<version>`,
|
||||
creates the GitHub release with `tree-sitter-django.wasm` attached, and runs
|
||||
`npm stage publish --provenance`. **A release is a version-bump commit on `main`**; no other push starts one.
|
||||
|
||||
**The workflow stages the publish; it does not publish.** `npm stage publish` hands npm the tarball and
|
||||
holds it there, unavailable to install, until a maintainer runs `npm stage approve <stage-id>` and answers a
|
||||
2FA prompt — proof of presence deferred rather than dropped, which is npm's answer to deprecating the tokens
|
||||
that let a workflow publish unattended. The job summary prints the `view`/`download`/`approve`/`reject`
|
||||
commands for the stage it made. Approving needs npm 12 (`npm install -g npm@^12`), which is also why the job
|
||||
installs it: no runner image ships an npm that has the command.
|
||||
|
||||
Running the workflow by hand (`workflow_dispatch`) is the retry: it stages whatever version `package.json`
|
||||
holds as long as `npm view` does not find it on the registry already, and it skips the tag and the release
|
||||
when a previous attempt left them behind, replacing only the Wasm asset. So a run that tagged and then
|
||||
failed to stage is finished by running it again, and a run against an already published version stops at the
|
||||
version job. A version that is _staged_ and not yet approved is invisible to `npm view` while still
|
||||
occupying npm's version index, so a second stage of it is an error rather than a no-op; the stage job asks
|
||||
`npm stage list` first and skips straight to the approval summary when it finds one. That listing is
|
||||
best-effort — a short-lived OIDC token may not run `npm stage` subcommands at all — and a failure there just
|
||||
means the stage is attempted.
|
||||
|
||||
Releasing needs three things to line up:
|
||||
|
||||
- credentials to stage with: either a trusted publisher configured for this workflow
|
||||
(`npm trust github --allow-stage-publish`), which needs no secret at all because npm exchanges the
|
||||
workflow's OIDC token for one of its own, or the `NPM_TOKEN` secret as the fallback. Staging works with any
|
||||
token type, so that token no longer needs — and should no longer have — the 2FA bypass an automation token
|
||||
carries. Both the exchange and provenance need `id-token: write`, which the publish job already declares.
|
||||
- **the version in `package.json` and in `tree-sitter.json`'s `metadata`**. The release trigger reads the
|
||||
first; the second is compiled into `src/parser.c` as the language's version, so bumping one alone leaves
|
||||
the parser disagreeing with the manifest.
|
||||
- `npm run parser-generate` after that bump, committed with it, or CI's up-to-date check fails on a two-line
|
||||
diff in `src/parser.c`.
|
||||
|
||||
The arm64 Linux runner (`ubuntu-24.04-arm`) is free for public repositories only. `tree-sitter build --wasm`
|
||||
runs Emscripten through Docker, which the ubuntu runners provide.
|
||||
|
||||
**Windows needs a newer npm than its runner ships.** `windows-latest` is Windows Server 2025 **with Visual
|
||||
Studio 2026**, and the node-gyp bundled with that image's npm (11.5.0) reads VS 18 as
|
||||
`unknown version "undefined"` and then reports "Could not find any Visual Studio installation to use", so
|
||||
`npm ci` fails in the install script. node-gyp learned VS 2026 in 12.1.0 and npm 12 bundles 13, which is why
|
||||
both jobs that compile the addon there — CI's Node binding and the release's `prebuild` — install `npm@^12`
|
||||
first. Pinning `windows-2022` instead would work, at the price of testing against a toolchain the image no
|
||||
longer has. Installs of the published package never meet this: they unpack a prebuild and never run node-gyp.
|
||||
|
||||
**The npm package must carry its prebuilds**, or `npm install` runs `node-gyp-build`, finds nothing and
|
||||
compiles, which fails on an image without `python3`, `make` and a compiler — `node:20-slim`, for instance.
|
||||
0.1.0 was published by hand before the workflow existed and shipped none, which is what that image hit. The
|
||||
`prebuild` jobs are what produce them, and `files` carries `prebuilds/**`.
|
||||
|
||||
`npm run prebuildify` passes `--napi --strip --tag-libc`. `--napi` makes one binary per platform that every
|
||||
Node ABI can load, so the file needs no ABI tag and is named after the package. `--tag-libc` adds `glibc` or
|
||||
`musl`: `node-gyp-build` treats a file with no libc tag as matching anything, so an Alpine install would
|
||||
otherwise load a glibc binary and fail at run time; with the tag it compiles instead. Only glibc is built,
|
||||
so musl installs need a toolchain.
|
||||
|
||||
`package.json` exports the Wasm build as `tree-sitter-django/tree-sitter-django.wasm`. An `exports` map
|
||||
hides everything it does not name, so each published path — the queries, `grammar.js`, `src/scanner.c` —
|
||||
needs its own entry.
|
||||
|
||||
### Stale parser library
|
||||
|
||||
tree-sitter caches the compiled parser in `~/.cache/tree-sitter/lib/django.*`, and it has served a stale
|
||||
build after edits — producing parses that contradict the current grammar (debug traces naming tokens that
|
||||
no longer exist). **If a parse result disagrees with `grammar.js`, suspect this first**: rerun with
|
||||
`npx tree-sitter test -r`, or delete `~/.cache/tree-sitter/lib/django*`.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Whitespace is syntax, not `extras`
|
||||
|
||||
`extras: []`. Django's `Token.split_contents()` splits a tag's contents on whitespace _before_ parsing any
|
||||
argument, so `{% cycle 1as x %}` is the single token `1as` — an error, not `1` followed by `as`. `extras`
|
||||
can never be made mandatory, so the separator is an explicit token and every rule is built from helpers
|
||||
that place it:
|
||||
|
||||
- `SEP` — `sym("_sep")`, the hidden rule `_sep: /[ \t\r\n]+/`, which a grammar extending this one writes as
|
||||
`$._sep`. Newlines included: Django matches tags with `re.DOTALL`, so tags may span lines.
|
||||
- `part(...)` — one tag part, taking the whitespace that separates it from the part before.
|
||||
- `joined(first, ...rest)` — parts separated from _each other_, with no leading separator (`not in`).
|
||||
- `tag(name, ...args)` — `{%`, optional separator, tag name, args, optional separator, `%}`; tags may
|
||||
hug their delimiters (`{%cycle 1%}`).
|
||||
- `asVariable($)` — the `as name` clause. Every tag that takes one names the target `field("variable", …)`;
|
||||
do not invent a second name for it.
|
||||
- `simpleTag($, name, args, kwargs)` — a tag registered with Django's `simple_tag`, which always accepts
|
||||
positional filter expressions, then `name=value` keywords, then `as name`. `args`: `true` (`*args`), a
|
||||
count (at most N), or `false`. `kwargs`: `true` (`**kwargs`), `false`, or `{name: rule}` for a known
|
||||
signature. Put the Python signature in a comment above the call site.
|
||||
- `arrangements(items)` — every ordered subset of `items`, so that a tag's options may be written in any
|
||||
order but never twice. Used by `translate_tag` and `include_tag`; keyword arguments are guarded by the scanner
|
||||
instead. Grows as `Σ C(n,k)·k!` (65 for four names), which is
|
||||
why it is only used for signatures that name their arguments.
|
||||
- `wordAsValueRules(word)` / `wordAsValue($, word, continued)` — a tag's own keyword written where a value
|
||||
goes. Django tells its flag from a variable by comparing the whole bit, so `reversed` is the flag of
|
||||
`{% for %}` while `reversed.x` and `reversed|last` are lookups; `continued` selects that second group.
|
||||
Needed because keyword extraction hands the parser the keyword wherever both it and `identifier` are
|
||||
valid, so the reading in which the word names a variable has to be built back out of it. The rules are
|
||||
hidden and aliased at the use site, one per level (`variable_attribute`, `value`, `filtered_value`),
|
||||
because an alias renames the node its own rule makes and **cannot nest one inside another** — writing
|
||||
it as nested inline `alias(alias(...))` calls silently yields a flat, partly zero-width tree that still
|
||||
generates and still parses. Check the tree, not just the accept.
|
||||
|
||||
Consequences worth knowing before editing a rule:
|
||||
|
||||
- **No `token.immediate` anywhere** — without extras, adjacency is already the default. Use it only to
|
||||
forbid whitespace somewhere a separator is otherwise allowed.
|
||||
- **Every optional trailing part needs somewhere for its separator to go.** Getting this wrong does not
|
||||
fail at generate time; it shows up as `MISSING` nodes in a passing-looking parse.
|
||||
- **`conflicts` entries are the normal fix, not precedence.** With explicit separators the lookahead at a
|
||||
part boundary is `SEP` instead of the meaningful token, so LR(1) loses its discriminator. That is why
|
||||
`predicate`, `binaryOperator` and `[$.library, $.load]` are listed there. Where two readings both stay
|
||||
valid to the end — `is not` as one operator or as `is` and a prefix `not` — the conflict needs a
|
||||
`prec.dynamic` alongside it to settle which wins; a static `prec` resolves the shift/reduce while the
|
||||
table is built and discards the other parse outright, which is how a bare `{% if x is y %}` came to be
|
||||
an error.
|
||||
|
||||
### A tag group is a sequence of blocks
|
||||
|
||||
Every group is split so that each tag that opens a body owns a node of its own:
|
||||
|
||||
```js
|
||||
if_tag: ($) => startTag("if", part($.predicate)),
|
||||
if_block: ($) => seq($.if_tag, optional($.template)),
|
||||
endif_tag: ($) => endTag("endif"),
|
||||
if_group: ($) =>
|
||||
seq(
|
||||
$.if_block,
|
||||
repeat($.elif_block),
|
||||
optional($.else_block),
|
||||
choice($.endif_tag, alias($._missing_tag, $.missing_endif_tag)),
|
||||
),
|
||||
```
|
||||
|
||||
**A block is complete without the closing tag, and that is the point.** While a template is being edited
|
||||
the closer usually is not there yet, and a group rule cannot be reduced without it — so `{% if a %}{% x %}`
|
||||
used to produce a top-level `ERROR` with the body's `template` parented to nothing that says which tag
|
||||
opened it. A block reduces anyway, so it survives error recovery and tooling can walk up from the cursor
|
||||
to find the enclosing tag. That is what makes completion of `{% elif %}`, `{% else %}`, `{% empty %}` and
|
||||
the end tags possible.
|
||||
|
||||
**Every `{% %}` is a node of its own, named `<name>_tag`** after the tag's name as Django spells it: `if_tag`,
|
||||
`elif_tag`, `endif_tag`, `block_tag`, `templatetag_tag`, `custom_tag`. A block is its
|
||||
opening tag and a body; a group is its blocks and its end tag. A tag ends at `%}` and nothing can
|
||||
extend it, so it reduces on whatever token follows — the end of input included — and a half-written
|
||||
template still holds the last `{% for … %}` or `{% endif %}` typed as a whole node, not as loose tokens.
|
||||
Stripping `_tag` gives the tag name; a new tag, or a tag group's new part, follows the same scheme.
|
||||
|
||||
A group's last child is its end block or, where the end tag is missing, a zero-width marker named for it
|
||||
(`missing_endif_tag`; see the scanner section). So an unterminated group still reduces to a group node,
|
||||
and a group's tag written where no open group can hold it is an `unexpected_tag` (see the load section
|
||||
below), so only a body the grammar cannot read produces an `ERROR`.
|
||||
|
||||
Every tag is built from ten inline utility rules — `simpleTagOpen`/`simpleTagClose`,
|
||||
`startTagOpen`/`startTagClose`,
|
||||
`followTagOpen`/`followTagClose`, `repeatTagOpen`/`repeatTagClose`, `endTagOpen`/`endTagClose` —
|
||||
through the helpers `tag` (a tag outside any group, from `simpleTagOpen`/`simpleTagClose`), `startTag`, `followTag` (a middle tag a group
|
||||
holds at most once: `else`, `empty`, `plural`), `repeatTag` (one it may repeat: `elif`) and `endTag`.
|
||||
They are inline rules rather than JS helpers so that a grammar extending this one can use them. **Every
|
||||
tag must begin with one of the opens**, one an extending grammar adds included: the token they read the
|
||||
tag's name with is valid wherever a tag's name is and the scanner always returns it, so a tag written from a bare `"{%"` never matches: its name is read as a `custom_tag` instead,
|
||||
with no error to show for it. Choose `followTag` or `repeatTag` by what the grammar allows, because the
|
||||
scanner trusts it: a tag marked once-only that can in fact repeat is an `unexpected_tag` from its
|
||||
second use on, and one marked repeatable that cannot is never flagged when repeated.
|
||||
|
||||
The names themselves need no hand-maintained list either: in the generated `src/node-types.json`, a group's
|
||||
`children` are its block types and its end tag, and a block's children include its opening tag, so
|
||||
the candidates for a group are its end tag plus the opening tag of each block it can hold. For
|
||||
`if_group` that is `endif_tag` from the group and `if_tag`, `elif_tag`, `else_tag` from its three
|
||||
blocks. Going the other way — from a block the cursor sits in to the
|
||||
groups that can hold it — is the same table read backwards, which is what a block needs when error
|
||||
recovery has left it without its group.
|
||||
|
||||
Do not reach for lookahead to solve that instead: `ts_language_next_state` follows only the shift on `{%`,
|
||||
which lands in the "a tag starts here" state no matter how the tree is shaped.
|
||||
|
||||
`else_block` is shared by `if_group` and `ifchanged_group`. `comment_block` and `verbatim_block` hold raw
|
||||
scanner text that admits no tags. `blocktranslate_group` has one block per spelling, `blocktrans_block` and
|
||||
`blocktranslate_block`, because each must be closed by its own end tag; the counted form nests a
|
||||
`plural_block` inside it, since `{% plural %}` is only valid after `count`. The block has to know which
|
||||
form its opening tag was, so `blocktransRules` builds each as a hidden rule (`_blocktrans_tag`,
|
||||
`_blocktrans_count_tag`) and aliases both to the one visible `blocktrans_tag` where the block uses
|
||||
it. Their bodies are
|
||||
`_translate_body`, not `template`, which is why these four blocks need no `conflicts` entry — the rule
|
||||
below is about a body that can itself start with `{%`.
|
||||
|
||||
**Every block needs a `conflicts` entry.** The generator offers a left associativity instead; taking it is
|
||||
what broke `{% for %}` in `06aa870`, where `prec.left` resolved the shift/reduce on `{%` statically and
|
||||
silently discarded the parse in which the body continues, so every tag inside a loop became an `ERROR`.
|
||||
|
||||
Sharing the blocks also made the table markedly smaller — 7,200 states and a 515KB library became 5,150
|
||||
and 383KB — for the same reason hoisting `blocktranslate`'s pieces into hidden rules did: the automaton
|
||||
stops duplicating the body states in every group's inline context. Giving every `{% %}` its own `_tag`
|
||||
rule shrank it again, from 1,999 states to 1,818, by the same mechanism. The group stack's tokens (see the
|
||||
scanner section) cost 247, to 3,304.
|
||||
|
||||
### Filter pipes differ by context
|
||||
|
||||
Django parses everything between `{{` and `}}` — and the entire remainder of `{% filter %}` — as one
|
||||
`FilterExpression`, whose regex allows `\s*` on both sides of `|`. Every other tag splits its arguments on
|
||||
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.
|
||||
|
||||
### 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.
|
||||
- `_custom_filter`, the last alternative of `filter`, whose name is `$.identifier` rather than one of the
|
||||
builtin literals. Hidden, so the tree is what an inline alternative would give, but named so that a
|
||||
grammar extending this one can filter it out of `filter`'s `members` the way it can `custom_tag` — see
|
||||
the extension section in `README.md`.
|
||||
|
||||
**A name that only exists inside a tag group has to be reserved instead.** `endif`, `else`, `empty` and
|
||||
the rest are tokens only in the state their group opens, so where a tag is named the lexer reads them as an
|
||||
identifier and `custom_tag` takes them: `{% endif %}` on its own parsed as a tag from some library. They are
|
||||
listed in `NAMES_INSIDE_A_TAG_GROUP` and applied through the `tag_name` reserved context, which wraps
|
||||
`custom_tag`'s name **and nothing else** — a reserved context replaces the global one inside whatever it
|
||||
wraps, so wrapping the whole rule would un-reserve `as` and reject `{% mytag endif %}`. Adding a tag group
|
||||
with a new part or end tag means adding its name there too.
|
||||
|
||||
**The same names are what `unexpected_tag` accepts.** Once reserved, a group's tag written where no open
|
||||
group can hold it — `{% endif %}` with nothing open, `{% else %}` in a `{% for %}` — used to be an `ERROR`,
|
||||
and error recovery placed it badly: every tag starts with `{%` and the read token, which the parser has
|
||||
already accepted when the name turns out to be wrong, so recovery rewinds to just after them and folds the
|
||||
stray tag into whichever tag comes next, which then holds the `ERROR`. `unexpected_tag` takes any of
|
||||
`NAMES_INSIDE_A_TAG_GROUP` with loose arguments, so there is nothing to recover; `prec.dynamic(-2)`, or `-4`, keeps it
|
||||
from displacing a tag the group does take, and `queries/errors.scm` reports it as
|
||||
`@error.unexpected_tag`. It added 31 states and no `conflicts` entry.
|
||||
|
||||
**Words a tag does not take are an `unexpected_argument` inside it.** Every close utility accepts one
|
||||
before its scanner token, so `{% else x %}` stays the `else` of its group, `{% endif junk %}` still closes
|
||||
its `if` (it used to leave the whole file an `ERROR`), and `{% static "a.css" "b.css" %}` is a `static_tag`.
|
||||
The node is a run of hidden `_unexpected_word`s, and three details make it work:
|
||||
|
||||
- **Lexical precedence -1.** A loose word is `token(prec(-1, /(?:[^\s%}]|\}[^\s%}])+/))`, which never holds `}}`:
|
||||
lexer states are shared between parse states, so the token is tried inside `{{ }}` too, and after
|
||||
`{{ x| ` it once swallowed the closing braces and left recovery nothing to close the variable with. Tree-sitter weighs lexical
|
||||
precedence before match length, so a token an argument really takes wins wherever one is valid
|
||||
(`b|upper` stays a filtered value) and a loose word only matches where nothing else can.
|
||||
- **Identifiers are words too.** Wherever a keyword is valid — after `{% for a in b`, where `reversed` may
|
||||
follow, or after `as`, `only`, `noop`, `silent` — tree-sitter lexes every word as an `identifier` to
|
||||
check for the keyword, and the loose-word token never gets a turn. So a word may also be an
|
||||
`identifier`, aliased to an anonymous `unexpected_word` so `queries/locals.scm` does not count it as a
|
||||
reference, with whatever touches it (`a=2`, `x.y|upper`, `"z"|lower:"q"`) as an immediate tail.
|
||||
- **-1 of dynamic precedence per word**, not per node, so a reading that flags more words than it must
|
||||
always loses: `{% cycle a b as x silent zqx %}` flags `zqx`, not everything after `a`. `unexpected_tag`
|
||||
costs -2 or -4 so that a misused tag its group does take beats reading it as a stray.
|
||||
|
||||
A tag with a known keyword signature (`simpleTag` given names, so far only `csp_nonce_attr`) needs one more
|
||||
piece: the scanner's `_kwarg_name` accepts any name followed by `=` and commits the parser to a keyword
|
||||
argument before the name is checked, so `{% csp_nonce_attr other="b" %}` would be an `ERROR`. `simpleTag`
|
||||
offers `_unexpected_keyword` — an identifier and its tail, aliased to `unexpected_argument` — beside the
|
||||
names it knows.
|
||||
|
||||
A separator after any argument can now also start stray words, so this needs `conflicts` entries for
|
||||
`unexpected_argument`, `predicate`, `_translate_option`, and `_unexpected_word` with `variable_attribute`,
|
||||
`resetcycle_tag` and `library`. It took the table from 3,363 states to 4,913 and the Wasm build from 251KB to
|
||||
323KB. Two consequences: a repeat the scanner refuses — `{% with a=1 a=2 %}`, `{% blocktrans trimmed trimmed %}`
|
||||
— is reported as an unexpected argument rather than an `ERROR`; and a stray **reserved** word is still an
|
||||
`ERROR`, since `not`, `if`, `in`, `is`, `as`, `for` and `from` can never be an identifier:
|
||||
`{% trans "hi" as x as y %}` errors on the second `as`. A tag missing a required argument, such as
|
||||
`{% with a = 1 %}`, is an `ERROR` too; `unexpected_argument` only takes words after a tag that is otherwise
|
||||
complete.
|
||||
|
||||
Where several tags share a name the group holds only once — `{% if %}{% else %}{% else %}{% else %}` — reading
|
||||
any of them as the stray parses, so the ties are broken by dynamic precedence: `unexpected_tag` is -2 when
|
||||
`_group_held` confirms the innermost group already holds the name and -4 otherwise, so each tag flagged
|
||||
before its time costs one more, and the first still opens the block however many follow.
|
||||
|
||||
**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").
|
||||
|
||||
### First-party libraries
|
||||
|
||||
Django ships `i18n`, `l10n`, `static`, `cache` and `tz` as libraries rather than builtins. Their tags are
|
||||
modelled as ordinary tags (so far: `static`, `l10n`, `tz`, `cache` and `i18n`), and the grammar **never requires the `{% load %}`**, because
|
||||
an engine can preload a library through `OPTIONS: {"builtins": [...]}`, which makes `{% static "a" %}` valid
|
||||
with no load at all.
|
||||
|
||||
Whether a load is present is a question about the order of nodes, not about how the template parses, so it
|
||||
belongs in a tool. `queries/libraries.scm` supplies the halves — `@library.<name>` on each tag, and
|
||||
`@load.library` / `@load.name` on the loads — and the tool does the join, because tree-sitter queries have
|
||||
no ordering predicate to express it. What such a tool needs to know about Django's behaviour:
|
||||
|
||||
- Loading is **positional**: `{% trans "x" %}{% load i18n %}` is an error, because the parser adds to its
|
||||
tag dict as it walks tokens.
|
||||
- Loading is **per file**: a `{% load %}` does not reach a template that `{% extends %}` or `{% include %}`
|
||||
this one.
|
||||
- `{% load trans from i18n %}` makes only `trans` available, not the rest of the library.
|
||||
|
||||
A library's filters go in their own arity tables (`L10N_FILTERS_WITHOUT_ARGUMENT`,
|
||||
`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
|
||||
registers its own tag under one of these names and a different signature therefore gets a parse error. That
|
||||
is accepted: these names are common enough that shadowing them is the mistake.
|
||||
|
||||
**Adding a library tag or filter touches four files**: `grammar.js`, `queries/libraries.scm` (which library
|
||||
it came from), `queries/highlights.scm` (the name, spelled out) and the table in `README.md`, which lists
|
||||
every library name the grammar models. All four are hand-maintained, and **no test catches a name left out
|
||||
of any of them**: `tree-sitter test` loads every `.scm` under `queries/`, so a pattern naming a node that
|
||||
no longer exists aborts the whole run, but a name that was never added passes 304/304 in silence, and the
|
||||
README nothing checks at all. The library-contents differential below, run against
|
||||
`django.templatetags.<name>.register`, is what finds a gap.
|
||||
|
||||
### External scanner (`src/scanner.c`)
|
||||
|
||||
Twenty external tokens, for the constraints a context-free grammar cannot express:
|
||||
|
||||
- `push_block`/`push_partial`/`push_verbatim` and the matching `pop_*` — the names in
|
||||
`{% block a %}…{% endblock a %}`. A push token reads the name into `pending_name`, which the group takes
|
||||
when `_group_open_tag_push` pushes it; a pop token checks the written name, or its absence, against the
|
||||
innermost open group, the only one the grammar lets it close, and `_group_close` then pops it.
|
||||
- `verbatim_content`, `comment_content` — raw text up to the matching close tag.
|
||||
- `_tag_open`, `_bt_option`, `_kwarg_name` — what a tag has already been given, so that a repeated
|
||||
argument is refused: `_bt_option` for `{% blocktranslate %}`'s five options, `_kwarg_name` for `name=value`
|
||||
arguments — those of a tag registered with `simple_tag`, and the `with` bindings of `{% with %}`,
|
||||
`{% include %}` and `{% blocktranslate %}` — whose names are open-ended and so are kept as names rather
|
||||
than as a mask. `_kwarg_name` returns nothing unless the name is followed by `=`, which
|
||||
is also what tells an argument apart from the `as name` clause or the end of the tag. `_tag_open` clears
|
||||
both, and every rule that uses either has one. Both are zero width and hidden, so no tree changes. The state is one byte of
|
||||
flags, not a stack, because the tag cannot nest: its body admits no tags. It lives outside the
|
||||
`stack`/`error` union, is cleared in both branches of `reset_scanner`, and is serialized after the
|
||||
status character, so a GLR stack split copies it like everything else.
|
||||
- `_group_open_tag_read`, `_group_open_tag_push`, `_group_follow`, `_group_repeat`, `_group_close`, `_group_held` —
|
||||
the stack
|
||||
of open groups, which the scanner keeps **without knowing any group by name**, so a grammar extending this
|
||||
one gets it for its own groups. The read token sits right after `{%` in every tag, always returns, and
|
||||
reads the tag's name into `read_word`. The other four sit right before `%}` and check that it follows:
|
||||
push opens a group expecting `"end"` + `read_word` (every Django end tag is spelled that way), follow
|
||||
records a middle tag the innermost group may hold only once, repeat records nothing, and close pops the
|
||||
innermost group once `read_word` is what it expects. `_group_held` belongs to `unexpected_tag` (see the load section) and returns only when
|
||||
the innermost group already holds a tag named `read_word`. Reading `count` (`_bt_option`) marks the group about
|
||||
to be pushed as waiting for `plural`, which its follow clears. An entry is
|
||||
`{expected, name, middles, awaits_plural}`, serialized as `expected:name:middles:flag` and a newline per
|
||||
entry; entries hold whole names, so serializing checks `TREE_SITTER_SERIALIZATION_BUFFER_SIZE` and goes to
|
||||
the error state rather than overrun it. Push and follow before `%}` cost 247 states over the table-driven
|
||||
scanner (3,057 to 3,304), because argument-final states stop being shared with tags where the token is not
|
||||
valid; after `%}`, where each tag's states are its own, saved only 15.
|
||||
- `_missing_tag` — the marker where a group's required tag block is missing, aliased at each use to
|
||||
`missing_endX_tag` or `missing_plural_tag` (`MISSING_TAGS`). It is placed only at the end of input;
|
||||
before the end tag of a group enclosing the innermost one; before an `endblock`/`endpartialdef` naming a
|
||||
block further out; or before a second `plural`, since a blocktranslate body holds no tag that could take it.
|
||||
Any other tag is taken to be the innermost group's, as Django's parser takes it — a middle tag it has not
|
||||
held yet, and a second of one it holds only once, which is an `unexpected_tag` inside it. Marking the
|
||||
group closed before a second `else` was tried and dropped: inside `{% block %}`, where most template code
|
||||
sits, there is always an enclosing group to hand the `else` to, so the inner group's own end tag became a
|
||||
stray instead. A counted blocktranslate is missing its
|
||||
plural before anything but `plural`. The marker closes the group as its end tag would (or records the
|
||||
plural), the group reduces with no `ERROR`, and `queries/errors.scm` reports it. Tags in a raw body are
|
||||
not read: the marker tells the body is raw from `verbatim_content`/`comment_content` being valid, and only
|
||||
the end of input counts there. **It is one token aliased per group, never a token per group**: tree-sitter
|
||||
does not merge parse states whose valid external tokens differ, so a token per group cloned every body
|
||||
state for each kind of group and took the table from 1,818 states to 17,664. A supertype would be the
|
||||
natural way to match every marker, but the generator silently drops one whose members are aliases, so
|
||||
`errors.scm` lists them.
|
||||
|
||||
What a stack that knows no group cannot tell: a middle tag of an enclosing group that the innermost group
|
||||
has never held — `{% if a %}{% for x in y %}{% else %}` — is an `ERROR`, not a marker. Learning each
|
||||
group's middle tags as they appear was tried first and put false markers on valid nesting such as
|
||||
`{% if %}…{% else %}{% if %}…{% else %}`, because the outer group, having held an `else`, claimed the
|
||||
inner one's.
|
||||
|
||||
- `_text_brace` — a single `{` that is text although the content regex cannot tell: before `{%` or `{#`,
|
||||
and at the end of input. Django splits a template with `({%.*?%}|{{.*?}}|{#.*?#})`, so a `{` is text
|
||||
unless one of those starts at it; `{{{` is not a text brace, since `{{` starts a variable at its first
|
||||
brace. `content` is a right-associative run of `_text` and `_text_brace`, so it stays one node. Inside a
|
||||
group's body the missing-tag check reads past `{` first, so it scans the brace itself
|
||||
(`scan_text_brace_rest`) rather than give up the position.
|
||||
- `_missing_variable_close` — the marker where a variable's `}}` is missing, aliased to
|
||||
`missing_variable_close`. It is placed only where the variable cannot go on — at the end of input, or
|
||||
before a `}` that is not `}}`, a `%` or a `{` — so `{{ hello }{% if x %}` closes the variable, reads the
|
||||
stray `}` as text and parses the `if` normally, where error recovery used to take the rest of the file.
|
||||
It is valid only after a variable's value, never where `content` is. It added 18 states.
|
||||
- `matcher_error` — used by no rule; returned only if the scanner reaches an error state.
|
||||
|
||||
`check_space()` defines whitespace for the scanner and **must stay in sync with `_sep`**, because the
|
||||
scanner skips whitespace itself while matching names.
|
||||
|
||||
Prefer the grammar over the scanner. A useful test: does the constraint change how the input _parses_?
|
||||
Block-name matching does — it decides which close tag closes what.
|
||||
|
||||
"This keyword may not repeat" normally does not, and belongs in a linter. `blocktranslate` is the exception,
|
||||
and the reason is worth keeping straight: its five options are a fixed set, so uniqueness _is_ expressible —
|
||||
`arrangements` expresses exactly that — and the scanner is standing in for a grammar that would be correct
|
||||
but takes minutes to build. Where the set is open, as with `{% querystring %}`'s `**kwargs`, no grammar
|
||||
can express it at all, and the scanner remembers the names the tag has been given instead of a mask of
|
||||
known ones. Only `parse_bits` actually refuses a repeat: the `with` bindings of
|
||||
`{% with %}`, `{% include %}` and `{% blocktranslate %}` all go through `token_kwargs`, which builds a dict
|
||||
and lets the later value win. They are guarded here anyway, as a deliberate divergence — writing a name
|
||||
twice has no use and is a mistake worth reporting. `{% blocktranslate %}`'s `count` is not part of that,
|
||||
because Django keeps it in a dict of its own, so `{% blocktranslate with a=1 count a=2 %}` is accepted here
|
||||
as it is there.
|
||||
|
||||
Zero-width guards carry one hazard: `recover_with_missing` can supply one without the scanner running. The
|
||||
`if (valid_symbols[MatcherError]) return false;` near the top of `scan` is what stops a guard being scanned
|
||||
during tree-sitter's mark-everything-valid recovery pass, so new guards go **below** it — and above the
|
||||
whitespace loop, which skips the separator the grammar still has to match. A guard must also never be valid
|
||||
where `content` is: a zero-width token at a content boundary preempts the internal lexer and `content` stops
|
||||
matching. `_missing_tag` is the one exception, and is safe only because it returns a token solely at the end
|
||||
of input or at `{%`, where `content` cannot match. It is scanned above the raw-text scanners, which find
|
||||
nothing at the end of an empty body.
|
||||
|
||||
The group tokens were checked against that hazard with malformed groups — a missing predicate, junk before
|
||||
`%}`, in an end tag, in `{% else %}` and in `{% plural %}`, and a mismatched block name. Recovery never
|
||||
inserted one of them as `MISSING`, and the stack never fell out of step with the parser: later groups still
|
||||
got the right markers. Junk inside an end tag does leave the whole file an `ERROR`.
|
||||
|
||||
### Queries
|
||||
|
||||
`queries/highlights.scm` lists tag and filter names explicitly, so adding either to the grammar means
|
||||
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 filter_name: (identifier))`, `(custom_tag tag_name: (identifier))`).
|
||||
|
||||
`queries/libraries.scm` is not a query editors run themselves; it is data for a linter, and every tag or
|
||||
filter added from a library belongs in it. `queries/locals.scm` relies on the `variable:` field to tell a
|
||||
binding from a reference — another reason to keep `asVariable` uniform. `{% blocktranslate %}`'s `asvar`
|
||||
target carries an `asvar:` field instead, because it is written inside the tag but assigned outside it:
|
||||
the scope on `blocktrans_block` / `blocktranslate_block` confines the `with` and `count` bindings, and would swallow `asvar`
|
||||
too if it were spelled the same way. `queries/exports.scm` names it for a tool to
|
||||
bind in the scope around the block, which a locals query cannot do: it places a definition in the innermost
|
||||
scope containing it and offers no way out again.
|
||||
|
||||
`queries/conditionals.scm` is data for a tool too: `@conditional.group` / `@conditional.branch` /
|
||||
`@conditional.default` say which bodies render on only some passes, so a checker can tell that
|
||||
`{% if a %}{% now "Y" as n %}{% endif %}{{ n }}` may reach `{{ n }}` with nothing bound. A locals query
|
||||
cannot: it matches a reference to the definition whose scope contains it and has no notion of a path not
|
||||
taken. A group is exhaustive iff it has a `@conditional.default`, so each pattern captures the group
|
||||
alongside one of its parts and a tool needs no list of which block belongs to which group. Adding a tag
|
||||
group whose body may be skipped means adding it there — `{% cache %}` is the non-obvious one, since a
|
||||
cache hit skips the body outright and `CacheNode` pushes no context.
|
||||
|
||||
A branch that is also a `@local.scope` confines its bindings whichever way the branch goes, which is why
|
||||
`{% for %}` is inert for that check: `ForNode` renders `nodelist_empty` **inside** the same
|
||||
`context.push()` as the loop body, so `empty_block` is a scope alongside `for_block`.
|
||||
|
||||
`queries/errors.scm` collects what an editor should report: `(ERROR)` as `@error.syntax`, `(MISSING)` as
|
||||
`@error.missing`, every missing-tag marker as `@error.missing_tag`, `unexpected_tag` and
|
||||
`unexpected_argument` as `@error.unexpected_tag` and `@error.unexpected_argument`, and
|
||||
`missing_variable_close` as `@error.missing_variable_close`. A tree holding only markers has
|
||||
no `has_error`, so a tool that checks that flag alone misses them. Adding a tag group means adding its
|
||||
marker there, since no supertype matches them all.
|
||||
|
||||
## Testing
|
||||
|
||||
Corpus tests live in `test/corpus/*.txt`, one file per tag, and `missing.txt` for the missing-tag markers.
|
||||
|
||||
- A test whose header carries `:error` asserts only that the parse fails. Prefer it for syntax-error cases
|
||||
over pinning an error-recovery tree, which is brittle.
|
||||
- An omitted field annotation in an expected tree is treated as "don't care"; `--show-fields` forces fields
|
||||
into diffs.
|
||||
- `test/highlights/` is **not** run by `tree-sitter test` (which looks for `test/highlight/`), and its
|
||||
`example.html` uses a custom `{% greet %}` tag the grammar does not model, so it parses with errors by
|
||||
design.
|
||||
|
||||
### Checking against Django itself
|
||||
|
||||
`Pipfile.lock` pins Django (`Pipfile` itself says `django = "*"`) so the grammar can be diffed against the
|
||||
real implementation instead of against documentation or memory. Both checks are worth repeating whenever tags, filters or literals change:
|
||||
|
||||
- **Builtin parity** — enumerate `Engine.default_builtins`, then compare `register.tags` and
|
||||
`register.filters` against `grammar.js`. For filters also compare argument arity via
|
||||
`inspect.signature`, ignoring the framework-injected `autoescape` parameter, which is never written in a
|
||||
template. Note `querystring` and `csp_nonce_attr` register as `simple_tag`, not `@register.tag`.
|
||||
- **Library contents** — `django.templatetags.<name>.register` gives a library's `tags` and `filters`.
|
||||
All of them are registered with `@register.tag`, so `inspect.signature` only reports `(parser, token)`
|
||||
and a signature has to come from the differential below or from reading the compile function.
|
||||
- **Syntax differential** — run candidate snippets through `django.template.Template()` and through
|
||||
`tree-sitter parse`, and compare accept/reject. This is how the `simple_tag` argument rules, the `_()`
|
||||
literal, and the filter-pipe spacing were pinned down.
|
||||
|
||||
`pipenv run` has resolved to a _different_ project's virtualenv from this directory; confirm with
|
||||
`python -c "import django; print(django.get_version())"` and fall back to the explicit venv path.
|
||||
|
||||
### Deliberate divergences from Django
|
||||
|
||||
`README.md` carries a shorter list of its own, and the two are not the same set on purpose: the README
|
||||
lists only the divergences where Django accepts an input that is almost certainly a mistake, which is the
|
||||
rule it states. Anything added here that fits that rule belongs there too; anything that does not — an
|
||||
unavoidable limitation, or a case where this grammar is the more permissive one — stays here.
|
||||
|
||||
- Django has no number token: `[\w.]+` is one lexeme and `int()`/`float()` decides whether it is a literal,
|
||||
so `1a`, `1e`, `0x1f`, `1.2.3`, `1__0` and `1.` are variable _lookups_ there and errors here. `1.` is the
|
||||
one that needs care: `Variable` calls `float()` and then rejects what it parsed if the last character is
|
||||
a dot (`# "2." is invalid`), so a trailing dot is a number only with an exponent after it — `1.e5` is a
|
||||
float and `1.` is not. The `number` regex is spelled to say that, and `test/corpus/template_variable.txt`
|
||||
pins all three.
|
||||
- Django has no boolean or `None` token either. `True`, `False` and `None` are lookups, which resolve
|
||||
because `BaseContext._reset_dicts` seeds every context with `{"True": True, "False": False, "None": None}`.
|
||||
They are `boolean` and `none` literals here, so that a tool reading `queries/locals.scm` does not count
|
||||
them as references to something the template never defines. Shadowing still parses — `{% with True=1 %}`
|
||||
binds an `identifier`, because the keyword is not valid where a binding name is — but `{{ True.x }}` is
|
||||
an error here and an ordinary two-part lookup in Django, which renders `string_if_invalid`.
|
||||
- A `with` binding written twice (`{% with a=1 a=2 %}`, and the same in `{% include %}` and
|
||||
`{% blocktranslate %}`) is reported here, as an `unexpected_argument`, and not in Django, where
|
||||
`token_kwargs` builds a dict and the later value wins. It has no use, so it is treated as the mistake it almost certainly is.
|
||||
- `{% 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_tag` 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.
|
||||
- Django's `static` tags ignore what follows their argument: `StaticNode.handle_token` looks for `as` two
|
||||
bits from the end, so `{% static "a" junk %}` and `{% static "a" as u v %}` are accepted there and
|
||||
reported here as an `unexpected_argument`, and `PrefixNode.handle_token` raises `IndexError` rather than `TemplateSyntaxError` on
|
||||
`{% get_static_prefix as %}`.
|
||||
- Django's `{% cache %}` takes its fragment name as the raw word in that position and finds the cache to
|
||||
use by testing whether the last word starts with `using=`, and only when something precedes it. So
|
||||
`{% cache 500 sidebar.name %}` names a fragment there, `{% cache 500 using="c" %}` names a fragment
|
||||
called `using="c"` rather than choosing a cache, and `{% cache 500 sidebar using= %}` takes an empty
|
||||
name. All three are errors here.
|
||||
- `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.
|
||||
- An unterminated group parses here and raises `TemplateSyntaxError` in Django: `{% if a %}x`, or a
|
||||
`{% for %}` closed while an `{% if %}` inside it is still open, holds a `missing_endif_tag` marker
|
||||
where the end tag belongs. The marker is the error, reported through `queries/errors.scm`; a check
|
||||
for `ERROR` nodes alone accepts the tree.
|
||||
- A `{{` that does not close is text in Django, whose lexer only takes a variable written `{{ … }}` on one
|
||||
line, so `{{ hello }` renders as written. Here it is a `template_variable` closed by a
|
||||
`missing_variable_close` marker, reported through `queries/errors.scm`, since a half-written variable is
|
||||
what it almost always is. The same lexer makes `{{ hello\n}}` text too, which this grammar accepts as a
|
||||
variable, because its separators allow newlines.
|
||||
Generated
+76
@@ -0,0 +1,76 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
project(tree-sitter-django
|
||||
VERSION "0.1.0"
|
||||
DESCRIPTION "Django grammar for tree-sitter"
|
||||
HOMEPAGE_URL "https://github.com/danhanson/tree-sitter-django"
|
||||
LANGUAGES C)
|
||||
|
||||
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
|
||||
option(TREE_SITTER_REUSE_ALLOCATOR "Reuse the library allocator" OFF)
|
||||
|
||||
set(TREE_SITTER_ABI_VERSION 15 CACHE STRING "Tree-sitter ABI version")
|
||||
if(NOT ${TREE_SITTER_ABI_VERSION} MATCHES "^[0-9]+$")
|
||||
unset(TREE_SITTER_ABI_VERSION CACHE)
|
||||
message(FATAL_ERROR "TREE_SITTER_ABI_VERSION must be an integer")
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI")
|
||||
|
||||
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/node-types.json"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/grammar.js"
|
||||
COMMAND "${TREE_SITTER_CLI}" generate grammar.js --no-parser
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMENT "Generating grammar.json")
|
||||
|
||||
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c"
|
||||
BYPRODUCTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/parser.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/alloc.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/array.h"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
|
||||
COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json
|
||||
--abi=${TREE_SITTER_ABI_VERSION}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMENT "Generating parser.c")
|
||||
|
||||
add_library(tree-sitter-django src/parser.c)
|
||||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.c)
|
||||
target_sources(tree-sitter-django PRIVATE src/scanner.c)
|
||||
endif()
|
||||
target_include_directories(tree-sitter-django
|
||||
PRIVATE src
|
||||
INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/bindings/c>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
|
||||
|
||||
target_compile_definitions(tree-sitter-django PRIVATE
|
||||
$<$<BOOL:${TREE_SITTER_REUSE_ALLOCATOR}>:TREE_SITTER_REUSE_ALLOCATOR>
|
||||
$<$<CONFIG:Debug>:TREE_SITTER_DEBUG>)
|
||||
|
||||
set_target_properties(tree-sitter-django
|
||||
PROPERTIES
|
||||
C_STANDARD 11
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
SOVERSION "${TREE_SITTER_ABI_VERSION}.${PROJECT_VERSION_MAJOR}"
|
||||
DEFINE_SYMBOL "")
|
||||
|
||||
configure_file(bindings/c/tree-sitter-django.pc.in
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-django.pc" @ONLY)
|
||||
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bindings/c/tree_sitter"
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
FILES_MATCHING PATTERN "*.h")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-django.pc"
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
|
||||
install(TARGETS tree-sitter-django
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||
|
||||
file(GLOB QUERIES queries/*.scm)
|
||||
install(FILES ${QUERIES}
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/tree-sitter/queries/django")
|
||||
|
||||
add_custom_target(ts-test "${TREE_SITTER_CLI}" test
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMENT "tree-sitter test")
|
||||
Generated
+34
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "tree-sitter-django"
|
||||
description = "Django grammar for tree-sitter"
|
||||
version = "0.1.0"
|
||||
authors = ["Daniel Hanson"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
keywords = ["incremental", "parsing", "tree-sitter", "django"]
|
||||
categories = ["parser-implementations", "parsing", "text-editors"]
|
||||
repository = "https://github.com/danhanson/tree-sitter-django"
|
||||
edition = "2021"
|
||||
autoexamples = false
|
||||
|
||||
build = "bindings/rust/build.rs"
|
||||
include = [
|
||||
"bindings/rust/*",
|
||||
"grammar.js",
|
||||
"queries/*",
|
||||
"src/*",
|
||||
"tree-sitter.json",
|
||||
"/LICENSE",
|
||||
]
|
||||
|
||||
[lib]
|
||||
path = "bindings/rust/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
tree-sitter-language = "0.1"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
tree-sitter = "0.26.12"
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Daniel Hanson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,116 @@
|
||||
LANGUAGE_NAME := tree-sitter-django
|
||||
HOMEPAGE_URL := https://github.com/danhanson/tree-sitter-django
|
||||
VERSION := 0.1.0
|
||||
DESCRIPTION := Django grammar for tree-sitter
|
||||
|
||||
# repository
|
||||
SRC_DIR := src
|
||||
|
||||
TS ?= tree-sitter
|
||||
|
||||
# install directory layout
|
||||
PREFIX ?= /usr/local
|
||||
DATADIR ?= $(PREFIX)/share
|
||||
INCLUDEDIR ?= $(PREFIX)/include
|
||||
LIBDIR ?= $(PREFIX)/lib
|
||||
BINDIR ?= $(PREFIX)/bin
|
||||
PCLIBDIR ?= $(LIBDIR)/pkgconfig
|
||||
|
||||
# source/object files
|
||||
PARSER := $(SRC_DIR)/parser.c
|
||||
EXTRAS := $(filter-out $(PARSER),$(wildcard $(SRC_DIR)/*.c))
|
||||
OBJS := $(patsubst %.c,%.o,$(PARSER) $(EXTRAS))
|
||||
|
||||
# flags
|
||||
ARFLAGS ?= rcs
|
||||
override CFLAGS += -I$(SRC_DIR) -std=c11 -fPIC
|
||||
|
||||
# ABI versioning
|
||||
SONAME_MAJOR = $(shell sed -n 's/\#define LANGUAGE_VERSION //p' $(PARSER))
|
||||
SONAME_MINOR = $(word 1,$(subst ., ,$(VERSION)))
|
||||
|
||||
# OS-specific bits
|
||||
MACHINE := $(shell $(CC) -dumpmachine)
|
||||
|
||||
ifneq ($(findstring darwin,$(MACHINE)),)
|
||||
SOEXT = dylib
|
||||
SOEXTVER_MAJOR = $(SONAME_MAJOR).$(SOEXT)
|
||||
SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).$(SOEXT)
|
||||
LINKSHARED = -dynamiclib -Wl,-install_name,$(LIBDIR)/lib$(LANGUAGE_NAME).$(SOEXTVER),-rpath,@executable_path/../Frameworks
|
||||
else ifneq ($(findstring mingw32,$(MACHINE)),)
|
||||
SOEXT = dll
|
||||
LINKSHARED += -s -shared -Wl,--out-implib,lib$(LANGUAGE_NAME).dll.a
|
||||
else
|
||||
SOEXT = so
|
||||
SOEXTVER_MAJOR = $(SOEXT).$(SONAME_MAJOR)
|
||||
SOEXTVER = $(SOEXT).$(SONAME_MAJOR).$(SONAME_MINOR)
|
||||
LINKSHARED = -shared -Wl,-soname,lib$(LANGUAGE_NAME).$(SOEXTVER)
|
||||
ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),)
|
||||
PCLIBDIR := $(PREFIX)/libdata/pkgconfig
|
||||
endif
|
||||
endif
|
||||
|
||||
all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc
|
||||
|
||||
lib$(LANGUAGE_NAME).a: $(OBJS)
|
||||
$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
lib$(LANGUAGE_NAME).$(SOEXT): $(OBJS)
|
||||
$(CC) $(LDFLAGS) $(LINKSHARED) $^ $(LDLIBS) -o $@
|
||||
ifneq ($(STRIP),)
|
||||
$(STRIP) $@
|
||||
endif
|
||||
|
||||
ifneq ($(findstring mingw32,$(MACHINE)),)
|
||||
lib$(LANGUAGE_NAME).dll.a: lib$(LANGUAGE_NAME).$(SOEXT)
|
||||
endif
|
||||
|
||||
$(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in
|
||||
sed -e 's|@PROJECT_VERSION@|$(VERSION)|' \
|
||||
-e 's|@CMAKE_INSTALL_LIBDIR@|$(LIBDIR:$(PREFIX)/%=%)|' \
|
||||
-e 's|@CMAKE_INSTALL_INCLUDEDIR@|$(INCLUDEDIR:$(PREFIX)/%=%)|' \
|
||||
-e 's|@PROJECT_DESCRIPTION@|$(DESCRIPTION)|' \
|
||||
-e 's|@PROJECT_HOMEPAGE_URL@|$(HOMEPAGE_URL)|' \
|
||||
-e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@
|
||||
|
||||
$(SRC_DIR)/grammar.json: grammar.js
|
||||
$(TS) generate --no-parser $^
|
||||
|
||||
$(PARSER): $(SRC_DIR)/grammar.json
|
||||
$(TS) generate $^
|
||||
|
||||
install: all
|
||||
install -d '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/django '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)'
|
||||
install -m644 bindings/c/tree_sitter/$(LANGUAGE_NAME).h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h
|
||||
install -m644 $(LANGUAGE_NAME).pc '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
|
||||
install -m644 lib$(LANGUAGE_NAME).a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a
|
||||
install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER)
|
||||
ifneq ($(findstring mingw32,$(MACHINE)),)
|
||||
install -d '$(DESTDIR)$(BINDIR)'
|
||||
install -m755 lib$(LANGUAGE_NAME).dll '$(DESTDIR)$(BINDIR)'/lib$(LANGUAGE_NAME).dll
|
||||
install -m755 lib$(LANGUAGE_NAME).dll.a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).dll.a
|
||||
else
|
||||
install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER)
|
||||
cd '$(DESTDIR)$(LIBDIR)' && ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR)
|
||||
cd '$(DESTDIR)$(LIBDIR)' && ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) lib$(LANGUAGE_NAME).$(SOEXT)
|
||||
endif
|
||||
ifneq ($(wildcard queries/*.scm),)
|
||||
install -m644 queries/*.scm '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/django
|
||||
endif
|
||||
|
||||
uninstall:
|
||||
$(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \
|
||||
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) \
|
||||
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) \
|
||||
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) \
|
||||
'$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h \
|
||||
'$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
|
||||
$(RM) -r '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/django
|
||||
|
||||
clean:
|
||||
$(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) lib$(LANGUAGE_NAME).dll.a
|
||||
|
||||
test:
|
||||
$(TS) test
|
||||
|
||||
.PHONY: all install uninstall clean test
|
||||
Generated
+41
@@ -0,0 +1,41 @@
|
||||
// swift-tools-version:5.3
|
||||
|
||||
import Foundation
|
||||
import PackageDescription
|
||||
|
||||
var sources = ["src/parser.c"]
|
||||
if FileManager.default.fileExists(atPath: "src/scanner.c") {
|
||||
sources.append("src/scanner.c")
|
||||
}
|
||||
|
||||
let package = Package(
|
||||
name: "TreeSitterDjango",
|
||||
products: [
|
||||
.library(name: "TreeSitterDjango", targets: ["TreeSitterDjango"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/tree-sitter/swift-tree-sitter", from: "0.10.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "TreeSitterDjango",
|
||||
dependencies: [],
|
||||
path: ".",
|
||||
sources: sources,
|
||||
resources: [
|
||||
.copy("queries")
|
||||
],
|
||||
publicHeadersPath: "bindings/swift",
|
||||
cSettings: [.headerSearchPath("src")]
|
||||
),
|
||||
.testTarget(
|
||||
name: "TreeSitterDjangoTests",
|
||||
dependencies: [
|
||||
.product(name: "SwiftTreeSitter", package: "swift-tree-sitter"),
|
||||
"TreeSitterDjango",
|
||||
],
|
||||
path: "bindings/swift/TreeSitterDjangoTests"
|
||||
)
|
||||
],
|
||||
cLanguageStandard: .c11
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
django = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.13"
|
||||
Generated
+46
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "1d824dfb7e1efaa95eefc090ec048bd1e901b57997b98d5b8319268f04f72176"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.13"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"asgiref": {
|
||||
"hashes": [
|
||||
"sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340",
|
||||
"sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094"
|
||||
],
|
||||
"markers": "python_version >= '3.10'",
|
||||
"version": "==3.12.1"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:585fb82bf15053c42cf52e67c2f1b54a032dca384759315fd6678ab1870d1d72",
|
||||
"sha256:7ab93536d8e677aa14554c56426290c69480bf2ee68d7513d4a22f57d6bfeb84"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.12'",
|
||||
"version": "==6.1.1"
|
||||
},
|
||||
"sqlparse": {
|
||||
"hashes": [
|
||||
"sha256:113c35c75365ab9cc9c7231d68c6428fb11c085fc8e9eb1ad659b7ddbf6cd2b9",
|
||||
"sha256:b861c0288ce2fa56209a9a6412d2e066ac664b3873b89c26c9d8415e8e32996f"
|
||||
],
|
||||
"markers": "python_version >= '3.10'",
|
||||
"version": "==0.6.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
||||
@@ -1,6 +1,75 @@
|
||||
# tree-sitter-django
|
||||
|
||||
A [tree-sitter](https://tree-sitter.github.io/tree-sitter/) grammar for the Django Template Language (DTL) — the `{% %}` tags and `{{ }}` variables used in Django templates.
|
||||
A [tree-sitter](https://tree-sitter.github.io/tree-sitter/) grammar for the [Django Template Language (DTL)](https://docs.djangoproject.com/en/6.1/ref/templates/language/#the-django-template-language) — the `{% %}` tags, `{{ }}` variables and `{# #}` comments used in Django templates.
|
||||
|
||||
## Coverage
|
||||
|
||||
Every tag and filter Django ships as a builtin is modelled — 28 tags and 57 filters as of Django 6.1 —
|
||||
and so is every tag and filter of the five libraries below. Arguments are part of the grammar rather than
|
||||
something a linter checks later, so a builtin written with the wrong ones is a parse error: `{{ x|length:2 }}`
|
||||
and `{% now %}` do not parse, because `length` takes no argument and `now` requires one.
|
||||
|
||||
The target is Django 6.1, including the builtins Django 6 added (`{% partial %}`, `{% partialdef %}`,
|
||||
`{% querystring %}`, `{% csp_nonce_attr %}`). Rather than being written from the documentation, the grammar
|
||||
is checked against a real Django — `Pipfile.lock` pins the version — by enumerating `Engine.default_builtins`
|
||||
for the tag and filter lists and their arities, and by running candidate snippets through both
|
||||
`django.template.Template()` and this parser to compare what each accepts. Where the two deliberately
|
||||
disagree is listed under [Divergences](#divergences-from-django).
|
||||
|
||||
## Using it
|
||||
|
||||
`tree-sitter-django` is [on npm](https://www.npmjs.com/package/tree-sitter-django), and the other language
|
||||
bindings are in place. What is here today:
|
||||
|
||||
- **`src/parser.c` and `src/scanner.c` are generated and committed**, so any tree-sitter host can compile
|
||||
them directly without running the CLI. The scanner is required: block-name matching
|
||||
(`{% block a %}…{% endblock a %}`), the repeated-argument guards and the missing-tag markers live in it.
|
||||
- **`tree-sitter-django.wasm`** is for a `web-tree-sitter` host or an editor that loads Wasm grammars. It
|
||||
is not checked in: `npm run parser-build` makes it, and a release ships it both in the npm package (the
|
||||
`tree-sitter-django/tree-sitter-django.wasm` export) and as an asset on the GitHub release.
|
||||
- **`queries/`** holds the highlight, locals, tags, injection and error queries an editor loads, plus three files
|
||||
meant for a linter. See [Queries](#queries).
|
||||
- **`tree-sitter.json`** names the grammar `django`, scopes it `source.django`, and points at the query
|
||||
files, which is what a tree-sitter host reads to wire all of the above together.
|
||||
- **`bindings/`** holds a binding per language, generated by `tree-sitter init` from the `bindings` and
|
||||
`metadata` sections of `tree-sitter.json`. See [Bindings](#bindings).
|
||||
|
||||
## Bindings
|
||||
|
||||
| Language | Build from a checkout | Entry point |
|
||||
| -------- | ------------------------------------------------------------- | ------------------------------------------------------ |
|
||||
| Node | `npm install`, which uses a prebuilt binary when there is one | `bindings/node/index.js` (ESM), typed by `index.d.ts` |
|
||||
| Rust | `cargo build` | `bindings/rust/lib.rs`, `Cargo.toml` |
|
||||
| Python | `pip install .` | `bindings/python/tree_sitter_django`, `pyproject.toml` |
|
||||
| Go | `go test ./bindings/go` | `bindings/go/binding.go`, `go.mod` |
|
||||
| Swift | `swift build` | `bindings/swift/TreeSitterDjango`, `Package.swift` |
|
||||
| C / C++ | `make` (static, shared and a pkg-config file) or `cmake` | `bindings/c/tree_sitter/tree-sitter-django.h` |
|
||||
|
||||
```js
|
||||
import Parser from "tree-sitter";
|
||||
import django from "tree-sitter-django";
|
||||
|
||||
const parser = new Parser();
|
||||
parser.setLanguage(django);
|
||||
```
|
||||
|
||||
To use the parser on a website, the wasm needs to be served then loaded.
|
||||
|
||||
```js
|
||||
import { Language, Parser } from "web-tree-sitter";
|
||||
|
||||
await Parser.init();
|
||||
const wasm = "my-resources-path/tree-sitter-django.wasm";
|
||||
const parser = new Parser();
|
||||
parser.setLanguage(await Language.load(wasm));
|
||||
```
|
||||
|
||||
```python
|
||||
import tree_sitter, tree_sitter_django
|
||||
|
||||
language = tree_sitter.Language(tree_sitter_django.language())
|
||||
tree = tree_sitter.Parser(language).parse(b"{% if x %}hi {{ y|upper }}{% endif %}")
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
@@ -11,3 +80,275 @@ npm run parser-test # run the corpus tests in test/corpus
|
||||
npm run parser-build # build the WASM binary
|
||||
npm run playground # interactively explore the grammar
|
||||
```
|
||||
|
||||
`src/parser.c`, `src/grammar.json` and `src/node-types.json` are generated **and committed**, so a change
|
||||
to `grammar.js` or `src/scanner.c` is only half made until `parser-generate` has run and the result is
|
||||
committed alongside it. `tree-sitter test` compiles the parser itself, so a stale `src/` shows up as tests
|
||||
that pass against the wrong grammar.
|
||||
|
||||
Pull requests run the corpus tests and every binding through `.github/workflows/ci.yml`. A release is a
|
||||
version-bump commit on `main`: raise the version in `package.json` and in `tree-sitter.json`'s `metadata`,
|
||||
run `npm run parser-generate`, and push. The workflow tags `v<version>`, attaches
|
||||
`tree-sitter-django.wasm` to the GitHub release, and stages the npm publish with provenance and prebuilt
|
||||
binaries for Linux, macOS and Windows. Staging is not publishing: npm holds the tarball until a maintainer
|
||||
runs `npm stage approve <stage-id>` with 2FA, and the workflow's job summary carries that command. Running
|
||||
the workflow by hand stages the current version if npm does not have it yet, which is how a release that
|
||||
failed part way through is retried.
|
||||
|
||||
## Queries
|
||||
|
||||
Five of the files in `queries/` are query files an editor loads:
|
||||
|
||||
- **`highlights.scm`** — syntax highlighting. Tag and filter names are listed one by one, so a name the
|
||||
grammar knows is captured as `@function` and a name it is only tolerating as `@function.call`.
|
||||
- **`locals.scm`** — scopes, definitions and references, so an editor can resolve a variable to the tag
|
||||
that bound it. A definition is a `variable:` field; the scopes are the blocks whose bindings do not
|
||||
outlive them (`{% with %}`, `{% for %}`, `{% empty %}`, `{% block %}`, `{% partialdef %}`,
|
||||
`{% blocktranslate %}`).
|
||||
- **`tags.scm`** — the symbol index behind `tree-sitter tag`. A `{% partialdef %}` and the names a
|
||||
`{% load %}` brings in are definitions; every tag and filter used is a reference.
|
||||
- **`injections.scm`** — marks template text and `{% verbatim %}` bodies as another language's content, so
|
||||
the HTML around the tags can be parsed by its own grammar. It names no language, leaving the editor to
|
||||
supply one.
|
||||
- **`errors.scm`** — what to report as a problem: `(ERROR)` as `@error.syntax`, tree-sitter's own
|
||||
`(MISSING)` as `@error.missing`, and `@error.missing_tag` for the marker placed where a group's required
|
||||
tag is missing. An unterminated `{% if a %}x` parses as an `if_group` ending in a zero-width
|
||||
`missing_endif_tag` rather than as an `ERROR`, so completion and the scope queries keep working while
|
||||
a template is being written — and the tree reports no error of its own, so this query is how to find
|
||||
it. A marker is placed only where the scanner can tell: at the end of input, before an end tag that
|
||||
belongs to an enclosing group, or before an `{% endblock %}` naming a block further out.
|
||||
A group's tag written where no open group can hold it, such as `{% else %}` inside a `{% for %}`, is an
|
||||
`unexpected_tag`, captured as `@error.unexpected_tag`. Words a tag does not take, such as the `x` in
|
||||
`{% else x %}`, are an `unexpected_argument` inside that tag, captured as `@error.unexpected_argument`. A `{{` whose `}}` is missing closes with a zero-width
|
||||
`missing_variable_close`, captured as `@error.missing_variable_close`, so the rest of the template still parses.
|
||||
|
||||
The other three are data for a tool rather than queries an editor runs. Each captures the two halves of a
|
||||
relation a tree-sitter query cannot express, and the tool does the join:
|
||||
|
||||
- **`libraries.scm`** — which library each tag or filter is registered in, and what each `{% load %}`
|
||||
brings in. The grammar never requires the load, so reporting a missing one is a linter's job; a query
|
||||
has no ordering predicate to compare the positions itself.
|
||||
- **`exports.scm`** — a name a tag binds in the scope _around_ it rather than the scope it opens. Only
|
||||
`{% blocktranslate %}`'s `asvar` does that, and a locals query cannot say so: it places a definition in
|
||||
the innermost scope containing it and offers no way out again.
|
||||
- **`conditionals.scm`** — which bodies render on only some passes, as `@conditional.group` /
|
||||
`@conditional.branch` / `@conditional.default`, so a checker can tell that
|
||||
`{% if a %}{% now "Y" as n %}{% endif %}{{ n }}` may reach `{{ n }}` with nothing bound. A group is
|
||||
exhaustive exactly when one of its branches is a default. A locals query has no notion of a path not
|
||||
taken.
|
||||
|
||||
`tree-sitter test` loads every file in the directory, so a pattern naming a node that no longer exists
|
||||
fails the suite rather than quietly matching nothing.
|
||||
|
||||
## Django libraries
|
||||
|
||||
Five of Django's template libraries are not builtins: their names have to be `{% load %}`ed, or preloaded
|
||||
through the engine's `OPTIONS: {"builtins": [...]}`. Every tag and filter all five register is modelled,
|
||||
with the same argument and arity rules as a builtin:
|
||||
|
||||
| library | tags | filters |
|
||||
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| `i18n` | `trans`, `translate`, `blocktrans`, `blocktranslate`, `language`, `get_available_languages`, `get_current_language`, `get_current_language_bidi`, `get_language_info`, `get_language_info_list` | `language_bidi`, `language_name`, `language_name_local`, `language_name_translated` |
|
||||
| `l10n` | `localize` | `localize`, `unlocalize` |
|
||||
| `static` | `static`, `get_static_prefix`, `get_media_prefix` | — |
|
||||
| `cache` | `cache` | — |
|
||||
| `tz` | `localtime`, `timezone`, `get_current_timezone` | `localtime`, `timezone`, `utc` |
|
||||
|
||||
**The `{% load %}` is never required**, because an engine may have preloaded the library. Whether one is
|
||||
present is a question about the order of nodes rather than about how the template parses, so it is left to
|
||||
a tool: `queries/libraries.scm` marks each name with the library it came from and each `{% load %}` with
|
||||
what it brings in, and the tool joins the two. Such a tool needs to know that loading is positional
|
||||
(`{% trans "x" %}{% load i18n %}` is an error), that it is per file rather than inherited through
|
||||
`{% extends %}` or `{% include %}`, and that `{% load trans from i18n %}` brings in only the names it lists.
|
||||
|
||||
Because these names are modelled, they win over the fallbacks below: a project that registers its own tag
|
||||
under one of them with a different signature gets a parse error. They are common enough that shadowing
|
||||
them is treated as the mistake.
|
||||
|
||||
If a project insists on shadowing one of these tags, it may use a sub-grammar that replaces the rule with
|
||||
their own. See [Adding rules for your own tags and filters](###adding-rules-for-your-own-tags-and-filters).
|
||||
|
||||
## Custom tags and filters
|
||||
|
||||
A library's tags and filters are registered in Python, so the grammar cannot know their names, their
|
||||
arity, or whether a tag opens a body. Two fallback rules accept them:
|
||||
|
||||
- **Tags.** Any tag the grammar does not recognise parses as `custom_tag`, with the shape `simple_tag` and
|
||||
`inclusion_tag` give: positional filter expressions, then `name=value` keyword arguments, then an
|
||||
optional `as name`. So `{% mytag a b|upper "s" k=1 as out %}` parses, with the name on the `tag_name:` field.
|
||||
A keyword argument written twice is refused.
|
||||
- **Filters.** An unknown filter parses as `(filter filter_name: (identifier))` with at most one `:argument`. No arity check for unknown filters is possible.
|
||||
|
||||
Both are captured as `@function.call` rather than `@function`, so an editor can distinguish a name the
|
||||
grammar knows from one it is only tolerating.
|
||||
|
||||
What the fallbacks cannot do:
|
||||
|
||||
- **A block tag is not nested.** `{% mytag %}…{% endmytag %}` is two sibling `custom_tag`s; pairing them
|
||||
would mean guessing that an unknown tag opens a body.
|
||||
- **A tag registered with `@register.tag` may parse anything**, since it receives the raw token, so one
|
||||
whose arguments are not the `simple_tag` shape — `{% mytag <<>> %}` — is an error here and not in Django.
|
||||
- **A misspelled name 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; `queries/libraries.scm` and the `load_tag` rule supply what one needs.
|
||||
- **Names that only exist inside a tag group are reserved**, so `{% endif %}` or `{% empty %}` standing on
|
||||
its own is an `unexpected_tag`, reported by `errors.scm`, rather than a call to a custom tag of that name.
|
||||
|
||||
### Adding rules for your own tags and filters
|
||||
|
||||
For a project whose custom tags deserve real rules rather than the fallbacks, use this grammar as a base
|
||||
grammar. `grammar(base, {...})` merges your rules into it, and a rule that redefines one the base already
|
||||
has receives it as `previous`. A tag is an alternative of `template_tag`; a filter is an
|
||||
alternative of `filter`.
|
||||
|
||||
```js
|
||||
import django from "tree-sitter-django/grammar";
|
||||
|
||||
export default grammar(django, {
|
||||
name: "djangox",
|
||||
|
||||
conflicts: ($, previous) => [...previous, [$.loop_block]],
|
||||
|
||||
rules: {
|
||||
template_tag: ($, previous) => choice(previous, $.map_tag, $.loop_group),
|
||||
|
||||
// {% map items over rows %}
|
||||
map_tag: ($) =>
|
||||
seq(
|
||||
$.simpleTagOpen,
|
||||
field("tag_name", "map"),
|
||||
$._sep,
|
||||
$.filtered_value,
|
||||
$._sep,
|
||||
"over",
|
||||
$._sep,
|
||||
$.filtered_value,
|
||||
$.simpleTagClose,
|
||||
),
|
||||
|
||||
// {% loop x %}…{% endloop %}
|
||||
loop_tag: ($) =>
|
||||
seq(
|
||||
$.startTagOpen,
|
||||
field("tag_name", "loop"),
|
||||
$._sep,
|
||||
$.identifier,
|
||||
$.startTagClose,
|
||||
),
|
||||
loop_block: ($) => seq($.loop_tag, optional($.template)),
|
||||
endloop_tag: ($) =>
|
||||
seq($.endTagOpen, field("tag_name", "endloop"), $.endTagClose),
|
||||
loop_group: ($) =>
|
||||
seq(
|
||||
$.loop_block,
|
||||
choice($.endloop_tag, alias($._missing_tag, $.missing_endloop_tag)),
|
||||
),
|
||||
|
||||
// {{ value|shout:"!" }}
|
||||
filter: ($, previous) =>
|
||||
choice(
|
||||
previous,
|
||||
seq(field("filter_name", "shout"), ":", field("argument", $.value)),
|
||||
),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Seven things to know:
|
||||
|
||||
- **Whitespace is explicit.** `extras` is empty, because Django splits a tag's contents before parsing any
|
||||
argument, so every separator has to be written out. The `part`/`tag`/`simpleTag` helpers the base
|
||||
grammar is built from are private to it, but the separator is the hidden rule `$._sep`, and it and the
|
||||
tag utilities (`simpleTagOpen` and the rest) are rules, so an extending grammar uses them as they are.
|
||||
- **Every tag begins with one of the opens**: `$.simpleTagOpen`, with `$.simpleTagClose` at its end, for a
|
||||
tag outside any group such as `map_tag`, and `startTagOpen`, `followTagOpen`, `repeatTagOpen` or
|
||||
`endTagOpen` for the tags of a group. Its
|
||||
zero-width token hands the tag's name to the scanner and is taken wherever a tag can start, so a tag
|
||||
written from a bare `"{%"` never matches: `{% map items over rows %}` would quietly parse as a
|
||||
`custom_tag` instead.
|
||||
- **A new name wins over the fallback.** `shout` becomes a token of its own, so it commits to your
|
||||
alternative and your arity applies — `{{ x|shout }}` is now an error, while names you have not modelled
|
||||
still fall through to `(filter filter_name: (identifier))`.
|
||||
- **A tag with a body is split the way the base grammar's are**, so every `{% %}` is a node of its own:
|
||||
a `loop_tag` ending in `$.startTagClose`, a `loop_block` of that tag and the body, an
|
||||
`endloop_tag` built from `$.endTagOpen` and `$.endTagClose`, and a `loop_group` of the block and the
|
||||
end tag. A middle tag uses `followTagOpen`/`followTagClose` if the group holds it at most once, and
|
||||
`repeatTagOpen`/`repeatTagClose` if it may repeat; the scanner trusts that choice. The block needs a
|
||||
`conflicts` entry, as every block in the base grammar does. The generator offers an associativity
|
||||
instead; taking it silently discards the parse in which the body continues.
|
||||
- **End tags are not reserved**, a stray `{% endmap %}` parses as a `custom_tag` rather than an error — `reserved` takes no
|
||||
`previous`, so the `tag_name` list cannot be added to a name at a time. Instead you may drop the `custom_tag` rule to avoid parsing end tags as custom tags. While the fallback
|
||||
is there, stray words in one of your end tags (`{% endloop extra %}`) read as a `custom_tag` too, which
|
||||
leaves your group with a missing-tag marker at the end of input instead of an `unexpected_argument`.
|
||||
- **Your groups get missing-tag markers.** The scanner knows no group by name — a group is closed by `end`
|
||||
followed by its opening tag's name — so the `alias($._missing_tag, $.missing_endloop_tag)` choice
|
||||
above is all it takes: an unclosed `{% loop x %}`, or one left open inside an `{% if %}` that
|
||||
`{% endif %}` closes, holds a `missing_endloop_tag`. Add the name to your copy of `errors.scm`.
|
||||
- **The external scanner has to be re-exported under your grammar's name**, or block-name matching and the
|
||||
repeated-argument guards will not link. Give your grammar a `src/scanner.c` that renames the base's five entry points to the
|
||||
ones your generated `parser.c` calls, and includes it:
|
||||
|
||||
```c
|
||||
#define tree_sitter_django_external_scanner_create tree_sitter_djangox_external_scanner_create
|
||||
#define tree_sitter_django_external_scanner_destroy tree_sitter_djangox_external_scanner_destroy
|
||||
#define tree_sitter_django_external_scanner_scan tree_sitter_djangox_external_scanner_scan
|
||||
#define tree_sitter_django_external_scanner_serialize tree_sitter_djangox_external_scanner_serialize
|
||||
#define tree_sitter_django_external_scanner_deserialize tree_sitter_djangox_external_scanner_deserialize
|
||||
|
||||
#include "../node_modules/tree-sitter-django/src/scanner.c"
|
||||
```
|
||||
|
||||
The base's `tree_sitter/parser.h` then resolves to your own generated copy, and the externals keep the
|
||||
order they have here, so the scanner's token enum stays valid. `tree-sitter build` takes no include
|
||||
path, hence the relative `#include`; the package also exports `tree-sitter-django/src/scanner.c`, so a
|
||||
build step can hand the compiler an absolute path from `import.meta.resolve` where a package manager's
|
||||
layout makes the relative one unreliable.
|
||||
|
||||
**Turning the fallbacks off.** A grammar that models every tag and filter its project uses probably does
|
||||
not want `custom_tag` and `_custom_filter` accepting unrecognized names as they allow typos to parse. `previous` is the base rule whose `members` are filterable. We can filter out these rules to make the grammar more strict:
|
||||
|
||||
```js
|
||||
template_tag: ($, previous) =>
|
||||
choice(
|
||||
...previous.members.filter((m) => m.name !== "custom_tag"),
|
||||
$.map_tag,
|
||||
),
|
||||
|
||||
filter: ($, previous) =>
|
||||
choice(
|
||||
...previous.members.filter((m) => m.name !== "_custom_filter"),
|
||||
seq(field("name", "shout"), ":", field("argument", $.value)),
|
||||
),
|
||||
```
|
||||
|
||||
`{% mytag %}` and `{{ x|unknown }}` are then errors, while the builtins, the libraries and your own rules
|
||||
carry on as before.
|
||||
|
||||
## Divergences from Django
|
||||
|
||||
The grammar aims to accept what Django accepts, with one deliberate exception: where Django takes an
|
||||
input without complaint but the input is almost certainly a mistake. It reads the mistake and silently
|
||||
renders nothing or quietly ignores half of what was written. This grammar tries to report such mistakes
|
||||
as errors instead. Every case below is input Django itself accepts:
|
||||
|
||||
- **Number-shaped names.** Django has no number token: a word is a literal if `int()` or `float()` parses
|
||||
it and a variable lookup otherwise, so `0x1f`, `1a`, `1.`, `1.2.3` and `1__0` look up a variable of that
|
||||
name and render nothing. (`1.` really is a lookup there and not a float — `Variable` rejects a trailing
|
||||
dot after parsing it — while `1.e5` is a float, and both are treated that way here.)
|
||||
- **An attribute of `True`, `False` or `None`.** Every Django context is seeded with those three names, so
|
||||
`{{ True.x }}` is an ordinary two-part lookup that can never resolve.
|
||||
- **A keyword argument written twice.** `{% with a=1 a=2 %}`, and the same in `{% include %}` and
|
||||
`{% blocktranslate %}`, where Django builds a dict and lets the last value win.
|
||||
- **Anything after a `{% static %}` path.** `StaticNode.handle_token` looks for `as` two words from the end
|
||||
and ignores the rest, so `{% static "a" junk %}` and `{% static "a" as url junk %}` render as if the
|
||||
extra words were not there.
|
||||
- **A `{% cache %}` argument meant as something other than the fragment name.** Django takes the raw word
|
||||
in that position, so `{% cache 500 using="c" %}` names a fragment called `using="c"` rather than choosing
|
||||
a cache, and `{% cache 500 sidebar.name %}` names one with a dot in it.
|
||||
|
||||
In the other direction the grammar is more permissive only where it cannot know better, which is what the
|
||||
fallbacks above are for.
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
Generated
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "tree_sitter_django_binding",
|
||||
"dependencies": [
|
||||
"<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
|
||||
],
|
||||
"include_dirs": [
|
||||
"src",
|
||||
],
|
||||
"sources": [
|
||||
"bindings/node/binding.cc",
|
||||
"src/parser.c",
|
||||
],
|
||||
"variables": {
|
||||
"has_scanner": "<!(node -p \"fs.existsSync('src/scanner.c')\")"
|
||||
},
|
||||
"conditions": [
|
||||
["has_scanner=='true'", {
|
||||
"sources+": ["src/scanner.c"],
|
||||
}],
|
||||
["OS!='win'", {
|
||||
"cflags_c": [
|
||||
"-std=c11",
|
||||
],
|
||||
}, { # OS == "win"
|
||||
"cflags_c": [
|
||||
"/std:c11",
|
||||
"/utf-8",
|
||||
],
|
||||
}],
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
|
||||
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
|
||||
|
||||
Name: tree-sitter-django
|
||||
Description: @PROJECT_DESCRIPTION@
|
||||
URL: @PROJECT_HOMEPAGE_URL@
|
||||
Version: @PROJECT_VERSION@
|
||||
Libs: -L${libdir} -ltree-sitter-django
|
||||
Cflags: -I${includedir}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#ifndef TREE_SITTER_DJANGO_H_
|
||||
#define TREE_SITTER_DJANGO_H_
|
||||
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
const TSLanguage *tree_sitter_django(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_DJANGO_H_
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
package tree_sitter_django
|
||||
|
||||
// #cgo CFLAGS: -std=c11 -fPIC
|
||||
// #include "../../src/parser.c"
|
||||
// #if __has_include("../../src/scanner.c")
|
||||
// #include "../../src/scanner.c"
|
||||
// #endif
|
||||
import "C"
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Get the tree-sitter Language for this grammar.
|
||||
func Language() unsafe.Pointer {
|
||||
return unsafe.Pointer(C.tree_sitter_django())
|
||||
}
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
package tree_sitter_django_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
tree_sitter "github.com/tree-sitter/go-tree-sitter"
|
||||
tree_sitter_django "github.com/danhanson/tree-sitter-django/bindings/go"
|
||||
)
|
||||
|
||||
func TestCanLoadGrammar(t *testing.T) {
|
||||
language := tree_sitter.NewLanguage(tree_sitter_django.Language())
|
||||
if language == nil {
|
||||
t.Errorf("Error loading Django Parser grammar")
|
||||
}
|
||||
}
|
||||
Generated
+19
@@ -0,0 +1,19 @@
|
||||
#include <napi.h>
|
||||
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
|
||||
extern "C" TSLanguage *tree_sitter_django();
|
||||
|
||||
// "tree-sitter", "language" hashed with BLAKE2
|
||||
const napi_type_tag LANGUAGE_TYPE_TAG = {
|
||||
0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16
|
||||
};
|
||||
|
||||
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
||||
auto language = Napi::External<TSLanguage>::New(env, tree_sitter_django());
|
||||
language.TypeTag(&LANGUAGE_TYPE_TAG);
|
||||
exports["language"] = language;
|
||||
return exports;
|
||||
}
|
||||
|
||||
NODE_API_MODULE(tree_sitter_django_binding, Init)
|
||||
Generated
+11
@@ -0,0 +1,11 @@
|
||||
import assert from "node:assert";
|
||||
import { test } from "node:test";
|
||||
import Parser from "tree-sitter";
|
||||
|
||||
test("can load grammar", () => {
|
||||
const parser = new Parser();
|
||||
assert.doesNotReject(async () => {
|
||||
const { default: language } = await import("./index.js");
|
||||
parser.setLanguage(language);
|
||||
});
|
||||
});
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
type BaseNode = {
|
||||
type: string;
|
||||
named: boolean;
|
||||
};
|
||||
|
||||
type ChildNode = {
|
||||
multiple: boolean;
|
||||
required: boolean;
|
||||
types: BaseNode[];
|
||||
};
|
||||
|
||||
type NodeInfo =
|
||||
| (BaseNode & {
|
||||
subtypes: BaseNode[];
|
||||
})
|
||||
| (BaseNode & {
|
||||
fields: { [name: string]: ChildNode };
|
||||
children: ChildNode[];
|
||||
});
|
||||
|
||||
/**
|
||||
* The tree-sitter language object for this grammar.
|
||||
*
|
||||
* @see {@linkcode https://tree-sitter.github.io/node-tree-sitter/interfaces/Parser.Language.html Parser.Language}
|
||||
*
|
||||
* @example
|
||||
* import Parser from "tree-sitter";
|
||||
* import django from "tree-sitter-django";
|
||||
*
|
||||
* const parser = new Parser();
|
||||
* parser.setLanguage(django);
|
||||
*/
|
||||
declare const binding: {
|
||||
/**
|
||||
* The inner language object.
|
||||
* @private
|
||||
*/
|
||||
language: unknown;
|
||||
|
||||
/**
|
||||
* The content of the `node-types.json` file for this grammar.
|
||||
*
|
||||
* @see {@linkplain https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types Static Node Types}
|
||||
*/
|
||||
nodeTypeInfo: NodeInfo[];
|
||||
|
||||
/** The syntax highlighting query for this grammar. */
|
||||
HIGHLIGHTS_QUERY?: string;
|
||||
|
||||
/** The language injection query for this grammar. */
|
||||
INJECTIONS_QUERY?: string;
|
||||
|
||||
/** The local variable query for this grammar. */
|
||||
LOCALS_QUERY?: string;
|
||||
|
||||
/** The symbol tagging query for this grammar. */
|
||||
TAGS_QUERY?: string;
|
||||
};
|
||||
|
||||
export default binding;
|
||||
Generated
+37
@@ -0,0 +1,37 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = fileURLToPath(new URL("../..", import.meta.url));
|
||||
|
||||
const binding = typeof process.versions.bun === "string"
|
||||
// Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
|
||||
? await import(`${root}/prebuilds/${process.platform}-${process.arch}/tree-sitter-django.node`)
|
||||
: (await import("node-gyp-build")).default(root);
|
||||
|
||||
try {
|
||||
const nodeTypes = await import(`${root}/src/node-types.json`, { with: { type: "json" } });
|
||||
binding.nodeTypeInfo = nodeTypes.default;
|
||||
} catch { }
|
||||
|
||||
const queries = [
|
||||
["HIGHLIGHTS_QUERY", `${root}/queries/highlights.scm`],
|
||||
["INJECTIONS_QUERY", `${root}/queries/injections.scm`],
|
||||
["LOCALS_QUERY", `${root}/queries/locals.scm`],
|
||||
["TAGS_QUERY", `${root}/queries/tags.scm`],
|
||||
];
|
||||
|
||||
for (const [prop, path] of queries) {
|
||||
Object.defineProperty(binding, prop, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
delete binding[prop];
|
||||
try {
|
||||
binding[prop] = readFileSync(path, "utf8");
|
||||
} catch { }
|
||||
return binding[prop];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default binding;
|
||||
Generated
+12
@@ -0,0 +1,12 @@
|
||||
from unittest import TestCase
|
||||
|
||||
from tree_sitter import Language, Parser
|
||||
import tree_sitter_django
|
||||
|
||||
|
||||
class TestLanguage(TestCase):
|
||||
def test_can_load_grammar(self):
|
||||
try:
|
||||
Parser(Language(tree_sitter_django.language()))
|
||||
except Exception:
|
||||
self.fail("Error loading Django Parser grammar")
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
"""Django grammar for tree-sitter"""
|
||||
|
||||
from importlib.resources import files as _files
|
||||
|
||||
from ._binding import language
|
||||
|
||||
|
||||
def _get_query(name, file):
|
||||
try:
|
||||
query = _files(f"{__package__}") / file
|
||||
globals()[name] = query.read_text()
|
||||
except FileNotFoundError:
|
||||
globals()[name] = None
|
||||
return globals()[name]
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "HIGHLIGHTS_QUERY":
|
||||
return _get_query("HIGHLIGHTS_QUERY", "queries/highlights.scm")
|
||||
if name == "INJECTIONS_QUERY":
|
||||
return _get_query("INJECTIONS_QUERY", "queries/injections.scm")
|
||||
if name == "LOCALS_QUERY":
|
||||
return _get_query("LOCALS_QUERY", "queries/locals.scm")
|
||||
if name == "TAGS_QUERY":
|
||||
return _get_query("TAGS_QUERY", "queries/tags.scm")
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"language",
|
||||
"HIGHLIGHTS_QUERY",
|
||||
"INJECTIONS_QUERY",
|
||||
"LOCALS_QUERY",
|
||||
"TAGS_QUERY",
|
||||
]
|
||||
|
||||
|
||||
def __dir__():
|
||||
return sorted(__all__ + [
|
||||
"__all__", "__builtins__", "__cached__", "__doc__", "__file__",
|
||||
"__loader__", "__name__", "__package__", "__path__", "__spec__",
|
||||
])
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
from typing import Final
|
||||
from typing_extensions import CapsuleType
|
||||
|
||||
HIGHLIGHTS_QUERY: Final[str] | None
|
||||
"""The syntax highlighting query for this grammar."""
|
||||
|
||||
INJECTIONS_QUERY: Final[str] | None
|
||||
"""The language injection query for this grammar."""
|
||||
|
||||
LOCALS_QUERY: Final[str] | None
|
||||
"""The local variable query for this grammar."""
|
||||
|
||||
TAGS_QUERY: Final[str] | None
|
||||
"""The symbol tagging query for this grammar."""
|
||||
|
||||
def language() -> CapsuleType:
|
||||
"""The tree-sitter language function for this grammar."""
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#include <Python.h>
|
||||
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
|
||||
TSLanguage *tree_sitter_django(void);
|
||||
|
||||
static PyObject* _binding_language(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) {
|
||||
return PyCapsule_New(tree_sitter_django(), "tree_sitter.Language", NULL);
|
||||
}
|
||||
|
||||
static struct PyModuleDef_Slot slots[] = {
|
||||
#ifdef Py_GIL_DISABLED
|
||||
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
|
||||
#endif
|
||||
{0, NULL}
|
||||
};
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{"language", _binding_language, METH_NOARGS,
|
||||
"Get the tree-sitter language for this grammar."},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
static struct PyModuleDef module = {
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "_binding",
|
||||
.m_doc = NULL,
|
||||
.m_size = 0,
|
||||
.m_methods = methods,
|
||||
.m_slots = slots,
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC PyInit__binding(void) {
|
||||
return PyModuleDef_Init(&module);
|
||||
}
|
||||
Whitespace-only changes.
Generated
+56
@@ -0,0 +1,56 @@
|
||||
fn main() {
|
||||
let src_dir = std::path::Path::new("src");
|
||||
|
||||
let mut c_config = cc::Build::new();
|
||||
c_config.std("c11").include(src_dir);
|
||||
|
||||
#[cfg(target_env = "msvc")]
|
||||
c_config.flag("-utf-8");
|
||||
|
||||
if std::env::var("TARGET").unwrap() == "wasm32-unknown-unknown" {
|
||||
let Ok(wasm_headers) = std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS") else {
|
||||
panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS must be set by the language crate");
|
||||
};
|
||||
let Ok(wasm_src) =
|
||||
std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_SRC").map(std::path::PathBuf::from)
|
||||
else {
|
||||
panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_SRC must be set by the language crate");
|
||||
};
|
||||
|
||||
c_config.include(&wasm_headers);
|
||||
c_config.files([
|
||||
wasm_src.join("stdio.c"),
|
||||
wasm_src.join("stdlib.c"),
|
||||
wasm_src.join("string.c"),
|
||||
]);
|
||||
}
|
||||
|
||||
let parser_path = src_dir.join("parser.c");
|
||||
c_config.file(&parser_path);
|
||||
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
|
||||
|
||||
let scanner_path = src_dir.join("scanner.c");
|
||||
if scanner_path.exists() {
|
||||
c_config.file(&scanner_path);
|
||||
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
|
||||
}
|
||||
|
||||
c_config.compile("tree-sitter-django");
|
||||
|
||||
println!("cargo:rustc-check-cfg=cfg(with_highlights_query)");
|
||||
if !"queries/highlights.scm".is_empty() && std::path::Path::new("queries/highlights.scm").exists() {
|
||||
println!("cargo:rustc-cfg=with_highlights_query");
|
||||
}
|
||||
println!("cargo:rustc-check-cfg=cfg(with_injections_query)");
|
||||
if !"queries/injections.scm".is_empty() && std::path::Path::new("queries/injections.scm").exists() {
|
||||
println!("cargo:rustc-cfg=with_injections_query");
|
||||
}
|
||||
println!("cargo:rustc-check-cfg=cfg(with_locals_query)");
|
||||
if !"queries/locals.scm".is_empty() && std::path::Path::new("queries/locals.scm").exists() {
|
||||
println!("cargo:rustc-cfg=with_locals_query");
|
||||
}
|
||||
println!("cargo:rustc-check-cfg=cfg(with_tags_query)");
|
||||
if !"queries/tags.scm".is_empty() && std::path::Path::new("queries/tags.scm").exists() {
|
||||
println!("cargo:rustc-cfg=with_tags_query");
|
||||
}
|
||||
}
|
||||
Generated
+60
@@ -0,0 +1,60 @@
|
||||
//! This crate provides Django Parser language support for the [tree-sitter] parsing library.
|
||||
//!
|
||||
//! Typically, you will use the [`LANGUAGE`] constant to add this language to a
|
||||
//! tree-sitter [`Parser`], and then use the parser to parse some code:
|
||||
//!
|
||||
//! ```
|
||||
//! let code = r#"
|
||||
//! "#;
|
||||
//! let mut parser = tree_sitter::Parser::new();
|
||||
//! let language = tree_sitter_django::LANGUAGE;
|
||||
//! parser
|
||||
//! .set_language(&language.into())
|
||||
//! .expect("Error loading Django Parser parser");
|
||||
//! let tree = parser.parse(code, None).unwrap();
|
||||
//! assert!(!tree.root_node().has_error());
|
||||
//! ```
|
||||
//!
|
||||
//! [`Parser`]: https://docs.rs/tree-sitter/0.26.12/tree_sitter/struct.Parser.html
|
||||
//! [tree-sitter]: https://tree-sitter.github.io/
|
||||
|
||||
use tree_sitter_language::LanguageFn;
|
||||
|
||||
extern "C" {
|
||||
fn tree_sitter_django() -> *const ();
|
||||
}
|
||||
|
||||
/// The tree-sitter [`LanguageFn`] for this grammar.
|
||||
pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_django) };
|
||||
|
||||
/// The content of the [`node-types.json`] file for this grammar.
|
||||
///
|
||||
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types
|
||||
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
|
||||
|
||||
#[cfg(with_highlights_query)]
|
||||
/// The syntax highlighting query for this grammar.
|
||||
pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
|
||||
|
||||
#[cfg(with_injections_query)]
|
||||
/// The language injection query for this grammar.
|
||||
pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm");
|
||||
|
||||
#[cfg(with_locals_query)]
|
||||
/// The local variable query for this grammar.
|
||||
pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm");
|
||||
|
||||
#[cfg(with_tags_query)]
|
||||
/// The symbol tagging query for this grammar.
|
||||
pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm");
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_can_load_grammar() {
|
||||
let mut parser = tree_sitter::Parser::new();
|
||||
parser
|
||||
.set_language(&super::LANGUAGE.into())
|
||||
.expect("Error loading Django Parser parser");
|
||||
}
|
||||
}
|
||||
Generated
+16
@@ -0,0 +1,16 @@
|
||||
#ifndef TREE_SITTER_DJANGO_H_
|
||||
#define TREE_SITTER_DJANGO_H_
|
||||
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
const TSLanguage *tree_sitter_django(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_DJANGO_H_
|
||||
@@ -0,0 +1,12 @@
|
||||
import XCTest
|
||||
import SwiftTreeSitter
|
||||
import TreeSitterDjango
|
||||
|
||||
final class TreeSitterDjangoTests: XCTestCase {
|
||||
func testCanLoadGrammar() throws {
|
||||
let parser = Parser()
|
||||
let language = Language(language: tree_sitter_django())
|
||||
XCTAssertNoThrow(try parser.setLanguage(language),
|
||||
"Error loading Django Parser grammar")
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,6 @@ import eslintConfigPrettier from "eslint-config-prettier";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
eslintConfigPrettier, // Disable ESLint rules that conflict with Prettier
|
||||
{
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"react-hooks/exhaustive-deps": "error",
|
||||
},
|
||||
},
|
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
"out/**",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module github.com/danhanson/tree-sitter-django
|
||||
|
||||
go 1.22
|
||||
|
||||
require github.com/tree-sitter/go-tree-sitter v0.24.0
|
||||
|
||||
require github.com/mattn/go-pointer v0.0.1 // indirect
|
||||
@@ -0,0 +1,36 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
|
||||
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tree-sitter/go-tree-sitter v0.24.0 h1:kRZb6aBNfcI/u0Qh8XEt3zjNVnmxTisDBN+kXK0xRYQ=
|
||||
github.com/tree-sitter/go-tree-sitter v0.24.0/go.mod h1:x681iFVoLMEwOSIHA1chaLkXlroXEN7WY+VHGFaoDbk=
|
||||
github.com/tree-sitter/tree-sitter-c v0.21.5-0.20240818205408-927da1f210eb h1:A8425heRM8mylnv4H58FPUiH+aYivyitre0PzxrfmWs=
|
||||
github.com/tree-sitter/tree-sitter-c v0.21.5-0.20240818205408-927da1f210eb/go.mod h1:dOF6gtQiF9UwNh995T5OphYmtIypkjsp3ap7r9AN/iA=
|
||||
github.com/tree-sitter/tree-sitter-cpp v0.22.4-0.20240818224355-b1a4e2b25148 h1:AfFPZwtwGN01BW1jDdqBVqscTwetvMpydqYZz57RSlc=
|
||||
github.com/tree-sitter/tree-sitter-cpp v0.22.4-0.20240818224355-b1a4e2b25148/go.mod h1:Bh6U3viD57rFXRYIQ+kmiYtr+1Bx0AceypDLJJSyi9s=
|
||||
github.com/tree-sitter/tree-sitter-embedded-template v0.21.1-0.20240819044651-ffbf64942c33 h1:TwqSV3qLp3tKSqirGLRHnjFk9Tc2oy57LIl+FQ4GjI4=
|
||||
github.com/tree-sitter/tree-sitter-embedded-template v0.21.1-0.20240819044651-ffbf64942c33/go.mod h1:CvCKCt3v04Ufos1zZnNCelBDeCGRpPucaN8QczoUsN4=
|
||||
github.com/tree-sitter/tree-sitter-go v0.21.3-0.20240818010209-8c0f0e7a6012 h1:Xvxck3tE5FW7F7bTS97iNM2ADMyCMJztVqn5HYKdJGo=
|
||||
github.com/tree-sitter/tree-sitter-go v0.21.3-0.20240818010209-8c0f0e7a6012/go.mod h1:T40D0O1cPvUU/+AmiXVXy1cncYQT6wem4Z0g4SfAYvY=
|
||||
github.com/tree-sitter/tree-sitter-html v0.20.5-0.20240818004741-d11201a263d0 h1:c46K6uh5Dz00zJeU9BfjXdb8I+E4RkUdfnWJpQADXFo=
|
||||
github.com/tree-sitter/tree-sitter-html v0.20.5-0.20240818004741-d11201a263d0/go.mod h1:hcNt/kOJHcIcuMvouE7LJcYdeFUFbVpBJ6d4wmOA+tU=
|
||||
github.com/tree-sitter/tree-sitter-java v0.21.1-0.20240824015150-576d8097e495 h1:jrt4qbJVEFs4H93/ITxygHc6u0TGqAkkate7TQ4wFSA=
|
||||
github.com/tree-sitter/tree-sitter-java v0.21.1-0.20240824015150-576d8097e495/go.mod h1:oyaR7fLnRV0hT9z6qwE9GkaeTom/hTDwK3H2idcOJFc=
|
||||
github.com/tree-sitter/tree-sitter-javascript v0.21.5-0.20240818005344-15887341e5b5 h1:om4X9AVg3asL8gxNJDcz4e/Wp+VpQj1PY3uJXKr6EOg=
|
||||
github.com/tree-sitter/tree-sitter-javascript v0.21.5-0.20240818005344-15887341e5b5/go.mod h1:nNqgPoV/h9uYWk6kYEFdEAhNVOacpfpRW5SFmdaP4tU=
|
||||
github.com/tree-sitter/tree-sitter-json v0.21.1-0.20240818005659-bdd69eb8c8a5 h1:pfV3G3k7NCKqKk8THBmyuh2zA33lgYHS3GVrzRR8ry4=
|
||||
github.com/tree-sitter/tree-sitter-json v0.21.1-0.20240818005659-bdd69eb8c8a5/go.mod h1:GbMKRjLfk0H+PI7nLi1Sx5lHf5wCpLz9al8tQYSxpEk=
|
||||
github.com/tree-sitter/tree-sitter-php v0.22.9-0.20240819002312-a552625b56c1 h1:ZXZMDwE+IhUtGug4Brv6NjJWUU3rfkZBKpemf6RY8/g=
|
||||
github.com/tree-sitter/tree-sitter-php v0.22.9-0.20240819002312-a552625b56c1/go.mod h1:UKCLuYnJ312Mei+3cyTmGOHzn0YAnaPRECgJmHtzrqs=
|
||||
github.com/tree-sitter/tree-sitter-python v0.21.1-0.20240818005537-55a9b8a4fbfb h1:EXEM82lFM7JjJb6qiKZXkpIDaCcbV2obNn82ghwj9lw=
|
||||
github.com/tree-sitter/tree-sitter-python v0.21.1-0.20240818005537-55a9b8a4fbfb/go.mod h1:lXCF1nGG5Dr4J3BTS0ObN4xJCCICiSu/b+Xe/VqMV7g=
|
||||
github.com/tree-sitter/tree-sitter-ruby v0.21.1-0.20240818211811-7dbc1e2d0e2d h1:fcYCvoXdcP1uRQYXqJHRy6Hec+uKScQdKVtMwK9JeCI=
|
||||
github.com/tree-sitter/tree-sitter-ruby v0.21.1-0.20240818211811-7dbc1e2d0e2d/go.mod h1:T1nShQ4v5AJtozZ8YyAS4uzUtDAJj/iv4YfwXSbUHzg=
|
||||
github.com/tree-sitter/tree-sitter-rust v0.21.3-0.20240818005432-2b43eafe6447 h1:o9alBu1J/WjrcTKEthYtXmdkDc5OVXD+PqlvnEZ0Lzc=
|
||||
github.com/tree-sitter/tree-sitter-rust v0.21.3-0.20240818005432-2b43eafe6447/go.mod h1:1Oh95COkkTn6Ezp0vcMbvfhRP5gLeqqljR0BYnBzWvc=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
+1182
-246
File diff suppressed because it is too large.
Load diff
Generated
+340
@@ -7,16 +7,29 @@
|
||||
"": {
|
||||
"name": "tree-sitter-django",
|
||||
"version": "0.1.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.3.1",
|
||||
"node-gyp-build": "^4.8.4",
|
||||
"web-tree-sitter": "^0.26.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^9",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prebuildify": "^6.0.1",
|
||||
"prettier": "^3.9.6",
|
||||
"tree-sitter": "^0.25.1",
|
||||
"tree-sitter-cli": "^0.26.12",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.25.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree-sitter": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
@@ -313,6 +326,39 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
@@ -324,6 +370,31 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -351,6 +422,13 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -418,6 +496,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
@@ -673,6 +761,13 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@@ -709,6 +804,27 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
@@ -746,6 +862,13 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -880,6 +1003,23 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -894,6 +1034,62 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.96.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz",
|
||||
"integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "8.9.2",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.2.tgz",
|
||||
"integrity": "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.4",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
|
||||
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz",
|
||||
"integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -977,6 +1173,24 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuildify": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz",
|
||||
"integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"node-abi": "^3.3.0",
|
||||
"npm-run-path": "^3.1.0",
|
||||
"pump": "^3.0.0",
|
||||
"tar-fs": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuildify": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@@ -1003,6 +1217,17 @@
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
|
||||
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@@ -1013,6 +1238,21 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
@@ -1023,6 +1263,40 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -1046,6 +1320,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||
@@ -1072,6 +1356,48 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz",
|
||||
"integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter": {
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.25.1.tgz",
|
||||
"integrity": "sha512-mrcEdkYtHfrK1A6fs3O6FxkBo0Qig5XUXqHhxUOQu0bmPo00QF4XaSx4edpazdHwxnSCjlGKGgIqWdaN4dvTLA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.5.0",
|
||||
"node-gyp-build": "^4.8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter-cli": {
|
||||
"version": "0.26.12",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.26.12.tgz",
|
||||
@@ -1123,6 +1449,13 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/web-tree-sitter": {
|
||||
"version": "0.26.12",
|
||||
"resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.12.tgz",
|
||||
@@ -1155,6 +1488,13 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
Generated
+52
-3
@@ -1,9 +1,19 @@
|
||||
{
|
||||
"name": "tree-sitter-django",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"version": "0.1.2",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./bindings/node/index.d.ts",
|
||||
"default": "./bindings/node/index.js"
|
||||
},
|
||||
"./grammar": "./grammar.js",
|
||||
"./tree-sitter-django.wasm": "./tree-sitter-django.wasm",
|
||||
"./src/scanner.c": "./src/scanner.c",
|
||||
"./queries/*": "./queries/*",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix",
|
||||
@@ -14,16 +24,55 @@
|
||||
"parser-build": "tree-sitter build --wasm",
|
||||
"highlight": "tree-sitter highlight",
|
||||
"tag": "tree-sitter tag",
|
||||
"playground": "tree-sitter playground"
|
||||
"playground": "tree-sitter playground",
|
||||
"install": "node-gyp-build",
|
||||
"prebuildify": "prebuildify --napi --strip --tag-libc",
|
||||
"binding-test": "node --test bindings/node/*_test.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.3.1",
|
||||
"node-gyp-build": "^4.8.4",
|
||||
"web-tree-sitter": "^0.26.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^9",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prebuildify": "^6.0.1",
|
||||
"prettier": "^3.9.6",
|
||||
"tree-sitter": "^0.25.1",
|
||||
"tree-sitter-cli": "^0.26.12",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"description": "Django grammar for tree-sitter",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/danhanson/tree-sitter-django.git"
|
||||
},
|
||||
"author": "Daniel Hanson (https://github.com/danhanson)",
|
||||
"keywords": [
|
||||
"incremental",
|
||||
"parsing",
|
||||
"tree-sitter",
|
||||
"django"
|
||||
],
|
||||
"main": "bindings/node",
|
||||
"types": "bindings/node",
|
||||
"files": [
|
||||
"grammar.js",
|
||||
"tree-sitter.json",
|
||||
"binding.gyp",
|
||||
"prebuilds/**",
|
||||
"bindings/node/*",
|
||||
"queries/*",
|
||||
"src/**",
|
||||
"*.wasm"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.25.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree-sitter": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+29
@@ -0,0 +1,29 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=62.4.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "tree-sitter-django"
|
||||
description = "Django grammar for tree-sitter"
|
||||
version = "0.1.0"
|
||||
keywords = ["incremental", "parsing", "tree-sitter", "django"]
|
||||
classifiers = [
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Software Development :: Compilers",
|
||||
"Topic :: Text Processing :: Linguistic",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
authors = [{ name = "Daniel Hanson" }]
|
||||
requires-python = ">=3.10"
|
||||
license.text = "MIT"
|
||||
readme = "README.md"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/danhanson/tree-sitter-django"
|
||||
|
||||
[project.optional-dependencies]
|
||||
core = ["tree-sitter~=0.24"]
|
||||
|
||||
[tool.cibuildwheel]
|
||||
build = "cp310-*"
|
||||
build-frontend = "build"
|
||||
@@ -0,0 +1,76 @@
|
||||
; Which parts of a template render conditionally, for a tool checking that a
|
||||
; variable is defined on every path that reaches a use of it.
|
||||
;
|
||||
; Django binds an "as" target straight into the context, and a tag that pushes
|
||||
; no context leaves that binding visible after its group closes. So
|
||||
;
|
||||
; {% if a %}{% now "Y" as n %}{% endif %}{{ n }}
|
||||
;
|
||||
; renders the year when a is truthy and string_if_invalid ("" by default) when
|
||||
; it is not — a silent empty string, not an error. queries/locals.scm cannot
|
||||
; see that: a locals query matches a reference to the definition whose scope
|
||||
; contains it and has no notion of a path that may not be taken.
|
||||
;
|
||||
; The halves a tool joins, by grouping matches on the @conditional.group node:
|
||||
;
|
||||
; @conditional.group the whole construct
|
||||
; @conditional.branch one alternative; at most one of a group's branches
|
||||
; renders on any given pass
|
||||
; @conditional.default the branch that renders when no other one does
|
||||
;
|
||||
; A group is exhaustive iff one of its matches carries a @conditional.default.
|
||||
; If it is not, some pass through it binds nothing, so every definition inside
|
||||
; is conditional. If it is, a definition escapes only when every branch makes
|
||||
; it. Each capture names both halves of the pair, so no ancestor walk is needed
|
||||
; and no block has to be attributed to a group by name.
|
||||
;
|
||||
; Branches nest, so the same test applies again to a group inside a branch.
|
||||
;
|
||||
; A branch that is also a @local.scope in queries/locals.scm confines its
|
||||
; bindings whether or not it renders; the two queries compose and the scope
|
||||
; decides first.
|
||||
|
||||
; if/elif are alternatives tested in order; else runs when none matched. None
|
||||
; of the three pushes a context, so this is the case the check exists for.
|
||||
(if_group
|
||||
[
|
||||
(if_block)
|
||||
(elif_block)
|
||||
] @conditional.branch) @conditional.group
|
||||
|
||||
(if_group
|
||||
(else_block) @conditional.default) @conditional.group
|
||||
|
||||
; ifchanged renders its body only when the watched value differs from the
|
||||
; previous pass, and its else otherwise. It pushes no context either.
|
||||
(ifchanged_group
|
||||
(ifchanged_block) @conditional.branch) @conditional.group
|
||||
|
||||
(ifchanged_group
|
||||
(else_block) @conditional.default) @conditional.group
|
||||
|
||||
; The loop body runs once per item and empty runs when there are none, so the
|
||||
; two partition every render. Both are inside ForNode's context.push() — the
|
||||
; empty branch included, which is easy to miss — so locals.scm scopes both and
|
||||
; nothing escapes either way. Named here because the construct does branch, and
|
||||
; because a tool reading only this file should not have to infer that it does.
|
||||
(for_group
|
||||
(for_block) @conditional.branch) @conditional.group
|
||||
|
||||
(for_group
|
||||
(empty_block) @conditional.default) @conditional.group
|
||||
|
||||
; A cache body is skipped entirely on a cache hit, and CacheNode pushes no
|
||||
; context, so a binding made inside it reaches the rest of the template on the
|
||||
; first render and is gone on the next. One branch and no default: never
|
||||
; exhaustive, which is the right answer.
|
||||
(cache_group
|
||||
(cache_block) @conditional.branch) @conditional.group
|
||||
|
||||
; Not here, deliberately: block and partialdef bodies may also fail to render —
|
||||
; a child template overrides the block, and a partial renders only where
|
||||
; {% partial %} names it — but both blocks are @local.scope in locals.scm, so
|
||||
; nothing they bind escapes to be checked. Bodies that always render once
|
||||
; (with, filter, autoescape, spaceless, language, localize, localtime,
|
||||
; timezone) are not conditional at all, and comment, verbatim and
|
||||
; blocktranslate bodies hold no tags to bind anything.
|
||||
@@ -0,0 +1,47 @@
|
||||
; Everything in a template that an editor should report as a problem.
|
||||
;
|
||||
; A group whose required tag is missing parses without an ERROR: the scanner
|
||||
; supplies a zero-width marker where the tag belongs, so the group, its blocks
|
||||
; and the scopes they open stay intact for completion and the other queries.
|
||||
; Such a tree has no error of its own (ts_node_has_error is false), so a tool
|
||||
; has to run this query to find them.
|
||||
;
|
||||
; @error.syntax input the grammar could not parse
|
||||
; @error.missing a token that tree-sitter's error recovery inserted
|
||||
; @error.missing_tag expected tag block is missing
|
||||
;
|
||||
; The markers are listed one by one, as MISSING_TAGS in grammar.js: the
|
||||
; generator drops a supertype whose members are aliases, so there is no single
|
||||
; node type to match them all. A new tag group, or a new kind of marker, is
|
||||
; added here.
|
||||
|
||||
(ERROR) @error.syntax
|
||||
|
||||
(MISSING) @error.missing
|
||||
|
||||
[
|
||||
(missing_endautoescape_tag)
|
||||
(missing_endblock_tag)
|
||||
(missing_endblocktrans_tag)
|
||||
(missing_endblocktranslate_tag)
|
||||
(missing_endcache_tag)
|
||||
(missing_endcomment_tag)
|
||||
(missing_endfilter_tag)
|
||||
(missing_endfor_tag)
|
||||
(missing_endif_tag)
|
||||
(missing_endifchanged_tag)
|
||||
(missing_endlanguage_tag)
|
||||
(missing_endlocalize_tag)
|
||||
(missing_endlocaltime_tag)
|
||||
(missing_endpartialdef_tag)
|
||||
(missing_endspaceless_tag)
|
||||
(missing_endtimezone_tag)
|
||||
(missing_endverbatim_tag)
|
||||
(missing_endwith_tag)
|
||||
(missing_plural_tag)] @error.missing_tag
|
||||
|
||||
(unexpected_tag) @error.unexpected_tag
|
||||
|
||||
(unexpected_argument) @error.unexpected_argument
|
||||
|
||||
(missing_variable_close) @error.missing_variable_close
|
||||
@@ -0,0 +1,30 @@
|
||||
; Names a tag binds in the scope *around* it rather than in the scope it opens.
|
||||
;
|
||||
; queries/locals.scm cannot express this: a locals query places a definition in
|
||||
; the innermost @local.scope whose span contains it, and there is no way to let
|
||||
; one out again. A tool reads this file alongside locals.scm and binds
|
||||
; @export.name in the nearest scope enclosing @export.from, not inside it.
|
||||
;
|
||||
; Only blocktranslate does this. Its asvar target is written inside the opening
|
||||
; tag — which is inside the scope, because the "with" and "count" bindings in
|
||||
; that same tag do belong to the block — but it holds the rendered text and is
|
||||
; read after the block:
|
||||
;
|
||||
; {% blocktranslate with bar=foo asvar var %}…{% endblocktranslate %}
|
||||
; {{ var }}
|
||||
;
|
||||
; It carries an "asvar" field rather than "variable" so locals.scm leaves it
|
||||
; alone. Every other "as name" clause is written in a tag that opens no scope,
|
||||
; so locals.scm already places those correctly and none of them belong here.
|
||||
;
|
||||
; asvar and the "with" bindings may be written in either order, so no single
|
||||
; scope span could have separated them; that is why this is a separate file
|
||||
; rather than a narrower scope in locals.scm.
|
||||
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
asvar: (identifier) @export.name)) @export.from
|
||||
|
||||
(blocktranslate_block
|
||||
(blocktranslate_tag
|
||||
asvar: (identifier) @export.name)) @export.from
|
||||
+75
-38
@@ -4,13 +4,13 @@
|
||||
variable: (identifier) @variable.parameter
|
||||
|
||||
|
||||
(block_group
|
||||
(block_tag
|
||||
(push_block) @type)
|
||||
|
||||
(partialdef_group
|
||||
(partialdef_tag
|
||||
(push_partial) @type)
|
||||
|
||||
(verbatim_group
|
||||
(verbatim_tag
|
||||
(push_verbatim) @type)
|
||||
|
||||
(comment_content) @comment
|
||||
@@ -25,6 +25,7 @@ variable: (identifier) @variable.parameter
|
||||
("in")
|
||||
("=")
|
||||
("|")
|
||||
(":")
|
||||
(".")] @operator
|
||||
|
||||
[
|
||||
@@ -40,17 +41,27 @@ variable: (identifier) @variable.parameter
|
||||
|
||||
(number) @number
|
||||
(string) @string
|
||||
(boolean) @constant.builtin
|
||||
(none) @constant.builtin
|
||||
|
||||
(library
|
||||
(identifier) @module)
|
||||
|
||||
tag: [
|
||||
tag_name: [
|
||||
("autoescape")
|
||||
("endautoescape")
|
||||
("block")
|
||||
("endblock")
|
||||
("blocktrans")
|
||||
("endblocktrans")
|
||||
("blocktranslate")
|
||||
("endblocktranslate")
|
||||
("plural")
|
||||
("cache")
|
||||
("endcache")
|
||||
("comment")
|
||||
("endcomment")
|
||||
("csp_nonce_attr")
|
||||
("csrf_token")
|
||||
("cycle")
|
||||
("debug")
|
||||
@@ -64,6 +75,14 @@ tag: [
|
||||
("for")
|
||||
("empty")
|
||||
("endfor")
|
||||
("get_available_languages")
|
||||
("get_current_language")
|
||||
("get_current_language_bidi")
|
||||
("get_current_timezone")
|
||||
("get_language_info")
|
||||
("get_language_info_list")
|
||||
("get_media_prefix")
|
||||
("get_static_prefix")
|
||||
("if")
|
||||
("elif")
|
||||
("else")
|
||||
@@ -71,8 +90,14 @@ tag: [
|
||||
("ifchanged")
|
||||
("endifchanged")
|
||||
("include")
|
||||
("language")
|
||||
("endlanguage")
|
||||
("lorem")
|
||||
("load")
|
||||
("localize")
|
||||
("endlocalize")
|
||||
("localtime")
|
||||
("endlocaltime")
|
||||
("now")
|
||||
("partial")
|
||||
("partialdef")
|
||||
@@ -82,81 +107,93 @@ tag: [
|
||||
("resetcycle")
|
||||
("spaceless")
|
||||
("endspaceless")
|
||||
("static")
|
||||
("templatetag")
|
||||
("timezone")
|
||||
("endtimezone")
|
||||
("trans")
|
||||
("translate")
|
||||
("url")
|
||||
("verbatim")
|
||||
("endverbatim")
|
||||
("with")
|
||||
("endwith")] @function
|
||||
|
||||
(filter .
|
||||
[
|
||||
("add:")
|
||||
(filter
|
||||
filter_name: [
|
||||
("add")
|
||||
("addslashes")
|
||||
("capfirst")
|
||||
("center:")
|
||||
("cut:")
|
||||
("center")
|
||||
("cut")
|
||||
("date")
|
||||
("date:")
|
||||
("default:")
|
||||
("default_if_none:")
|
||||
("dictsort:")
|
||||
("dictsortreversed:")
|
||||
("divisibleby:")
|
||||
("default")
|
||||
("default_if_none")
|
||||
("dictsort")
|
||||
("dictsortreversed")
|
||||
("divisibleby")
|
||||
("escape")
|
||||
("escapejs")
|
||||
("escapeseq")
|
||||
("filesizeformat")
|
||||
("first")
|
||||
("floatformat")
|
||||
("floatformat:")
|
||||
("force_escape")
|
||||
("get_digit:")
|
||||
("get_digit")
|
||||
("iriencode")
|
||||
("join:")
|
||||
("join")
|
||||
("json_script")
|
||||
("json_script:")
|
||||
("language_bidi")
|
||||
("language_name")
|
||||
("language_name_local")
|
||||
("language_name_translated")
|
||||
("last")
|
||||
("length")
|
||||
("length_is:")
|
||||
("linebreaks")
|
||||
("linebreaksbr")
|
||||
("linenumbers")
|
||||
("ljust:")
|
||||
("ljust")
|
||||
("localize")
|
||||
("localtime")
|
||||
("lower")
|
||||
("make_list")
|
||||
("phone2numeric")
|
||||
("pluralize")
|
||||
("pluralize:")
|
||||
("pprint")
|
||||
("random")
|
||||
("rjust:")
|
||||
("rjust")
|
||||
("safe")
|
||||
("safeseq")
|
||||
("slice:")
|
||||
("slice")
|
||||
("slugify")
|
||||
("stringformat:")
|
||||
("stringformat")
|
||||
("striptags")
|
||||
("time")
|
||||
("time:")
|
||||
("timesince")
|
||||
("timesince:")
|
||||
("timeuntil")
|
||||
("timeuntil:")
|
||||
("timezone")
|
||||
("title")
|
||||
("truncatechars:")
|
||||
("truncatechars_html:")
|
||||
("truncatewords:")
|
||||
("truncatewords_html:")
|
||||
("truncatechars")
|
||||
("truncatechars_html")
|
||||
("truncatewords")
|
||||
("truncatewords_html")
|
||||
("unlocalize")
|
||||
("unordered_list")
|
||||
("upper")
|
||||
("urlencode")
|
||||
("urlencode:")
|
||||
("urlize")
|
||||
("urlizetrunc:")
|
||||
("urlizetrunc")
|
||||
("utc")
|
||||
("wordcount")
|
||||
("wordwrap:")
|
||||
("yesno")
|
||||
("yesno:")] @function)
|
||||
("wordwrap")
|
||||
("yesno")] @function)
|
||||
|
||||
(load
|
||||
; a filter or tag a "load" brought in, whose name the grammar does not know
|
||||
(filter
|
||||
filter_name: (identifier) @function.call)
|
||||
|
||||
(custom_tag
|
||||
tag_name: (identifier) @function.call)
|
||||
|
||||
(load_tag
|
||||
(identifier) @function)
|
||||
@@ -0,0 +1,95 @@
|
||||
; Which library a tag or filter is registered in.
|
||||
;
|
||||
; The grammar accepts these whether or not a "{% load %}" precedes them: an
|
||||
; engine may preload a library through its "builtins" option, and Django
|
||||
; resolves a name against the libraries loaded *so far*, which is a question
|
||||
; about the order of nodes rather than about how the template parses. A tool
|
||||
; that wants to report a missing load can run this query and compare positions
|
||||
; itself, and should skip the check for a library the engine preloads.
|
||||
;
|
||||
; Loading is per file and positional: "{% load %}" does not reach a template
|
||||
; that extends or includes this one, and a tag written before the load is an
|
||||
; error even though the load is in the same file.
|
||||
|
||||
; Each pattern captures the name a library registered, rather than the node it
|
||||
; heads, so that the capture is both the name to look up and the range to
|
||||
; report. A paired tag names the opening tag only.
|
||||
|
||||
(static_tag
|
||||
tag_name: ("static") @library.static)
|
||||
|
||||
(get_static_prefix_tag
|
||||
tag_name: ("get_static_prefix") @library.static)
|
||||
|
||||
(get_media_prefix_tag
|
||||
tag_name: ("get_media_prefix") @library.static)
|
||||
|
||||
(blocktrans_tag
|
||||
tag_name: ("blocktrans") @library.i18n)
|
||||
|
||||
(blocktranslate_tag
|
||||
tag_name: ("blocktranslate") @library.i18n)
|
||||
|
||||
(translate_tag
|
||||
tag_name: [
|
||||
("trans")
|
||||
("translate")] @library.i18n)
|
||||
|
||||
(language_tag
|
||||
tag_name: ("language") @library.i18n)
|
||||
|
||||
(get_available_languages_tag
|
||||
tag_name: ("get_available_languages") @library.i18n)
|
||||
|
||||
(get_current_language_tag
|
||||
tag_name: ("get_current_language") @library.i18n)
|
||||
|
||||
(get_current_language_bidi_tag
|
||||
tag_name: ("get_current_language_bidi") @library.i18n)
|
||||
|
||||
(get_language_info_tag
|
||||
tag_name: ("get_language_info") @library.i18n)
|
||||
|
||||
(get_language_info_list_tag
|
||||
tag_name: ("get_language_info_list") @library.i18n)
|
||||
|
||||
(filter
|
||||
filter_name: [
|
||||
("language_bidi")
|
||||
("language_name")
|
||||
("language_name_local")
|
||||
("language_name_translated")] @library.i18n)
|
||||
|
||||
(cache_tag
|
||||
tag_name: ("cache") @library.cache)
|
||||
|
||||
(localize_tag
|
||||
tag_name: ("localize") @library.l10n)
|
||||
|
||||
(filter
|
||||
filter_name: [
|
||||
("localize")
|
||||
("unlocalize")] @library.l10n)
|
||||
|
||||
(localtime_tag
|
||||
tag_name: ("localtime") @library.tz)
|
||||
|
||||
(timezone_tag
|
||||
tag_name: ("timezone") @library.tz)
|
||||
|
||||
(get_current_timezone_tag
|
||||
tag_name: ("get_current_timezone") @library.tz)
|
||||
|
||||
(filter
|
||||
filter_name: [
|
||||
("localtime")
|
||||
("timezone")
|
||||
("utc")] @library.tz)
|
||||
|
||||
; "{% load static %}" makes every name in the library available; "{% load
|
||||
; static from staticfiles %}" makes only the names listed available.
|
||||
(load_tag
|
||||
(library) @load.library)
|
||||
|
||||
(load_tag
|
||||
(identifier) @load.name)
|
||||
+22
-2
@@ -3,5 +3,25 @@
|
||||
|
||||
variable: (identifier) @local.definition
|
||||
|
||||
(with_group) @local.scope
|
||||
(for_scope) @local.scope
|
||||
; The tags whose Node pushes a context, so that what they bind is not visible
|
||||
; after them. Verified by rendering: "{% block b %}{% now "Y" as n %}{% endblock %}{{ n }}"
|
||||
; is empty, while the same around an "{% if %}" is not, because if does not push.
|
||||
(with_block) @local.scope
|
||||
(for_block) @local.scope
|
||||
(block_block) @local.scope
|
||||
(partialdef_block) @local.scope
|
||||
|
||||
; ForNode renders nodelist_empty inside the same context.push() as the loop
|
||||
; body, so "{% empty %}" is part of the loop's scope even though it is a
|
||||
; separate block: "{% for x in ys %}a{% empty %}{% now "Y" as n %}{% endfor %}{{ n }}"
|
||||
; is empty. queries/conditionals.scm names both blocks as branches of the
|
||||
; same group and relies on this.
|
||||
(empty_block) @local.scope
|
||||
|
||||
; blocktranslate pushes a context for its "with" and "count" bindings. Its
|
||||
; asvar target is not one of them: it is written in the same tag but assigned
|
||||
; outside it, and carries an "asvar" field rather than "variable" so that this
|
||||
; scope does not swallow it. A tool that wants it must define it in the scope
|
||||
; around the block, which a locals query has no way to say.
|
||||
(blocktrans_block) @local.scope
|
||||
(blocktranslate_block) @local.scope
|
||||
+14
-9
@@ -1,15 +1,20 @@
|
||||
(load
|
||||
(load_tag
|
||||
(identifier) @name) @definition.function
|
||||
|
||||
(partialdef_group (push_partial) @name) @definition.method
|
||||
(partial (identifier) @name) @reference.call
|
||||
(partialdef_tag (push_partial) @name) @definition.method
|
||||
(partial_tag (identifier) @name) @reference.call
|
||||
|
||||
(template_block_groups
|
||||
[
|
||||
; a tag that opens no body names itself; a group is named by the first tag of
|
||||
; its first block, so neither its end tag nor the tags opening its later blocks
|
||||
; are references of their own
|
||||
(template_tag
|
||||
tag_name: _ @name) @reference.call
|
||||
|
||||
(template_tag
|
||||
.
|
||||
(_
|
||||
tag: _ @name) @reference.call
|
||||
(for_group
|
||||
(for_scope
|
||||
tag: "for" @reference.call))])
|
||||
.
|
||||
(_
|
||||
tag_name: _ @name))) @reference.call
|
||||
|
||||
(filter) @reference.call
|
||||
@@ -0,0 +1,78 @@
|
||||
from os import path
|
||||
from sysconfig import get_config_var
|
||||
|
||||
from setuptools import Extension, find_packages, setup
|
||||
from setuptools.command.build import build
|
||||
from setuptools.command.build_ext import build_ext
|
||||
from setuptools.command.egg_info import egg_info
|
||||
from wheel.bdist_wheel import bdist_wheel
|
||||
|
||||
|
||||
class Build(build):
|
||||
def run(self):
|
||||
if path.isdir("queries"):
|
||||
dest = path.join(self.build_lib, "tree_sitter_django", "queries")
|
||||
self.copy_tree("queries", dest)
|
||||
super().run()
|
||||
|
||||
|
||||
class BuildExt(build_ext):
|
||||
def build_extension(self, ext: Extension):
|
||||
if self.compiler.compiler_type != "msvc":
|
||||
ext.extra_compile_args = ["-std=c11", "-fvisibility=hidden"]
|
||||
else:
|
||||
ext.extra_compile_args = ["/std:c11", "/utf-8"]
|
||||
if path.exists("src/scanner.c"):
|
||||
ext.sources.append("src/scanner.c")
|
||||
if ext.py_limited_api:
|
||||
ext.define_macros.append(("Py_LIMITED_API", "0x030A0000"))
|
||||
super().build_extension(ext)
|
||||
|
||||
|
||||
class BdistWheel(bdist_wheel):
|
||||
def get_tag(self):
|
||||
python, abi, platform = super().get_tag()
|
||||
if python.startswith("cp") and not get_config_var("Py_GIL_DISABLED"):
|
||||
python, abi = "cp310", "abi3"
|
||||
return python, abi, platform
|
||||
|
||||
|
||||
class EggInfo(egg_info):
|
||||
def find_sources(self):
|
||||
super().find_sources()
|
||||
self.filelist.recursive_include("queries", "*.scm")
|
||||
self.filelist.include("src/tree_sitter/*.h")
|
||||
self.filelist.include("src/*.c")
|
||||
|
||||
|
||||
setup(
|
||||
packages=find_packages("bindings/python"),
|
||||
package_dir={"": "bindings/python"},
|
||||
package_data={
|
||||
"tree_sitter_django": ["*.pyi", "py.typed"],
|
||||
"tree_sitter_django.queries": ["*.scm"],
|
||||
},
|
||||
ext_package="tree_sitter_django",
|
||||
ext_modules=[
|
||||
Extension(
|
||||
name="_binding",
|
||||
sources=[
|
||||
"bindings/python/tree_sitter_django/binding.c",
|
||||
"src/parser.c",
|
||||
],
|
||||
define_macros=[
|
||||
("PY_SSIZE_T_CLEAN", None),
|
||||
("TREE_SITTER_HIDE_SYMBOLS", None),
|
||||
],
|
||||
include_dirs=["src"],
|
||||
py_limited_api=not get_config_var("Py_GIL_DISABLED"),
|
||||
)
|
||||
],
|
||||
cmdclass={
|
||||
"build": Build,
|
||||
"build_ext": BuildExt,
|
||||
"bdist_wheel": BdistWheel,
|
||||
"egg_info": EggInfo,
|
||||
},
|
||||
zip_safe=False
|
||||
)
|
||||
Generated
+6330
-1102
File diff suppressed because it is too large.
Load diff
Generated
+3128
-231
File diff suppressed because it is too large.
Load diff
Generated
+62118
-15640
File diff suppressed because it is too large.
Load diff
+791
-327
File diff suppressed because it is too large.
Load diff
@@ -17,6 +17,9 @@ Autoescape
|
||||
(template
|
||||
(content)
|
||||
(autoescape_group
|
||||
(autoescape_block
|
||||
(autoescape_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endautoescape_tag))
|
||||
(content))
|
||||
+48
-41
@@ -17,10 +17,13 @@ Block
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(push_block)
|
||||
(block_block
|
||||
(block_tag
|
||||
name: (push_block))
|
||||
(template
|
||||
(content))
|
||||
(pop_block))
|
||||
(content)))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -42,10 +45,13 @@ Block Matching End Tag
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(push_block)
|
||||
(block_block
|
||||
(block_tag
|
||||
name: (push_block))
|
||||
(template
|
||||
(content))
|
||||
(pop_block))
|
||||
(content)))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -69,33 +75,22 @@ Nested Blocks
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(push_block)
|
||||
(block_block
|
||||
(block_tag
|
||||
name: (push_block))
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(push_block)
|
||||
(block_block
|
||||
(block_tag
|
||||
name: (push_block))
|
||||
(template
|
||||
(content))
|
||||
(pop_block))
|
||||
(content))
|
||||
(pop_block))
|
||||
(content))
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(push_block)
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(push_block)
|
||||
(template
|
||||
(content))
|
||||
(pop_block))
|
||||
(content))
|
||||
(pop_block))
|
||||
(content)))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content)))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -119,16 +114,22 @@ Nested Blocks End Tag
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(push_block)
|
||||
(block_block
|
||||
(block_tag
|
||||
name: (push_block))
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(push_block)
|
||||
(block_block
|
||||
(block_tag
|
||||
name: (push_block))
|
||||
(template
|
||||
(content))
|
||||
(pop_block))
|
||||
(content))
|
||||
(pop_block))
|
||||
(content)))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content)))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -152,14 +153,20 @@ Nested Blocks Mixed Tags
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(push_block)
|
||||
(block_block
|
||||
(block_tag
|
||||
name: (push_block))
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(push_block)
|
||||
(block_block
|
||||
(block_tag
|
||||
name: (push_block))
|
||||
(template
|
||||
(content))
|
||||
(pop_block))
|
||||
(content))
|
||||
(pop_block))
|
||||
(content)))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content)))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content))
|
||||
@@ -0,0 +1,698 @@
|
||||
==================
|
||||
Blocktrans
|
||||
==================
|
||||
|
||||
{% blocktrans %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag)
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktranslate
|
||||
==================
|
||||
|
||||
{% blocktranslate %}hi{% endblocktranslate %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktranslate_block
|
||||
(blocktranslate_tag)
|
||||
(content))
|
||||
(endblocktranslate_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans Empty
|
||||
==================
|
||||
|
||||
{% blocktrans %}{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With A Placeholder
|
||||
==================
|
||||
|
||||
{% blocktrans %}hi {{ name }}{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag)
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans Trimmed
|
||||
==================
|
||||
|
||||
{% blocktrans trimmed %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag)
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With
|
||||
==================
|
||||
|
||||
{% blocktrans with a=b %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Several Bindings
|
||||
==================
|
||||
|
||||
{% blocktrans with a=b c=d %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With A Filtered Binding
|
||||
==================
|
||||
|
||||
{% blocktrans with a=b|upper %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Context
|
||||
==================
|
||||
|
||||
{% blocktrans context "menu" %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Asvar
|
||||
==================
|
||||
|
||||
{% blocktrans asvar out %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
asvar: (identifier))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Count And Plural
|
||||
==================
|
||||
|
||||
{% blocktrans count n=x %}one{% plural %}many{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content)
|
||||
(plural_block
|
||||
(plural_tag)
|
||||
(content)))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With An Empty Singular
|
||||
==================
|
||||
|
||||
{% blocktrans count n=x %}{% plural %}many{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(plural_block
|
||||
(plural_tag)
|
||||
(content)))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktranslate With An Empty Plural
|
||||
==================
|
||||
|
||||
{% blocktranslate count n=x %}one{% plural %}{% endblocktranslate %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktranslate_block
|
||||
(blocktranslate_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content)
|
||||
(plural_block
|
||||
(plural_tag)))
|
||||
(endblocktranslate_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Every Option
|
||||
==================
|
||||
|
||||
{% blocktrans trimmed with a=b count n=x context "menu" asvar out %}s{% plural %}p{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
asvar: (identifier))
|
||||
(content)
|
||||
(plural_block
|
||||
(plural_tag)
|
||||
(content)))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Its Options Reordered
|
||||
==================
|
||||
|
||||
{% blocktrans asvar out trimmed %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
asvar: (identifier))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans Closed By The Other Spelling
|
||||
:error
|
||||
==================
|
||||
|
||||
{% blocktrans %}hi{% endblocktranslate %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Blocktranslate Closed By The Other Spelling
|
||||
:error
|
||||
==================
|
||||
|
||||
{% blocktranslate %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With Count And No Plural
|
||||
==================
|
||||
|
||||
{% blocktrans count n=x %}one{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content)
|
||||
(missing_plural_tag))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Plural And No Count
|
||||
:error
|
||||
==================
|
||||
|
||||
{% blocktrans %}one{% plural %}many{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans Counting Two Bindings
|
||||
==================
|
||||
|
||||
{% blocktrans count n=x c=d %}one{% plural %}many{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(unexpected_argument))
|
||||
(content)
|
||||
(plural_block
|
||||
(plural_tag)
|
||||
(content)))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Nothing To Bind
|
||||
:error
|
||||
==================
|
||||
|
||||
{% blocktrans with %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With An Unknown Option
|
||||
==================
|
||||
|
||||
{% blocktrans bogus %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Asvar And No Name
|
||||
:error
|
||||
==================
|
||||
|
||||
{% blocktrans asvar %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans Holding A Tag
|
||||
:error
|
||||
==================
|
||||
|
||||
{% blocktrans %}a{% if x %}b{% endif %}{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans Holding A Comment
|
||||
:error
|
||||
==================
|
||||
|
||||
{% blocktrans %}a{# c #}b{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans Holding Another Blocktrans
|
||||
:error
|
||||
==================
|
||||
|
||||
{% blocktrans %}a{% blocktrans %}b{% endblocktrans %}{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With Two Plurals
|
||||
==================
|
||||
|
||||
{% blocktrans count n=x %}one{% plural %}many{% plural %}x{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content)
|
||||
(plural_block
|
||||
(plural_tag)
|
||||
(content)))
|
||||
(missing_endblocktrans_tag))
|
||||
(unexpected_tag)
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With An Argument On Plural
|
||||
==================
|
||||
|
||||
{% blocktrans count n=x %}one{% plural extra %}many{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content)
|
||||
(plural_block
|
||||
(plural_tag
|
||||
(unexpected_argument))
|
||||
(content)))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Trimmed Twice
|
||||
==================
|
||||
|
||||
{% blocktrans trimmed trimmed %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With With Twice
|
||||
==================
|
||||
|
||||
{% blocktrans with a=b with c=d %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Context Twice
|
||||
==================
|
||||
|
||||
{% blocktrans context "a" context "b" %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Asvar Twice
|
||||
==================
|
||||
|
||||
{% blocktrans asvar x asvar y %}hi{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(identifier)
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With Count Twice
|
||||
==================
|
||||
|
||||
{% blocktrans count n=x count m=y %}a{% plural %}b{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(unexpected_argument))
|
||||
(content)
|
||||
(plural_block
|
||||
(plural_tag)
|
||||
(content)))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktranslate With Trimmed Twice
|
||||
==================
|
||||
|
||||
{% blocktranslate trimmed trimmed %}hi{% endblocktranslate %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktranslate_block
|
||||
(blocktranslate_tag
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
(endblocktranslate_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans Repeating An Option Of The Tag Before It
|
||||
==================
|
||||
|
||||
{% blocktrans trimmed %}a{% endblocktrans %}{% blocktrans trimmed %}b{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag)
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag)
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Blocktrans With A Binding Twice
|
||||
==================
|
||||
|
||||
{% blocktrans with a=1 a=2 %}x{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
(endblocktrans_tag))
|
||||
(content))
|
||||
@@ -0,0 +1,278 @@
|
||||
==================
|
||||
Cache
|
||||
==================
|
||||
|
||||
{% cache 500 sidebar %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cache_group
|
||||
(cache_block
|
||||
(cache_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(identifier))
|
||||
(template
|
||||
(content)))
|
||||
(endcache_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Cache With A Variable Timeout
|
||||
==================
|
||||
|
||||
{% cache t sidebar %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cache_group
|
||||
(cache_block
|
||||
(cache_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(identifier))
|
||||
(template
|
||||
(content)))
|
||||
(endcache_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Cache With A Filtered Timeout
|
||||
==================
|
||||
|
||||
{% cache 500|add:1 sidebar %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cache_group
|
||||
(cache_block
|
||||
(cache_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))
|
||||
(filter_expression
|
||||
(filter
|
||||
argument: (value
|
||||
(literal
|
||||
(number))))))
|
||||
(identifier))
|
||||
(template
|
||||
(content)))
|
||||
(endcache_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Cache With A Quoted Fragment Name
|
||||
==================
|
||||
|
||||
{% cache 500 "sidebar" %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cache_group
|
||||
(cache_block
|
||||
(cache_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(string))
|
||||
(template
|
||||
(content)))
|
||||
(endcache_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Cache Varying On Arguments
|
||||
==================
|
||||
|
||||
{% cache 500 sidebar a b %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cache_group
|
||||
(cache_block
|
||||
(cache_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(endcache_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Cache Varying On A Filtered Argument
|
||||
==================
|
||||
|
||||
{% cache 500 sidebar a|upper %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cache_group
|
||||
(cache_block
|
||||
(cache_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(template
|
||||
(content)))
|
||||
(endcache_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Cache Using A Named Cache
|
||||
==================
|
||||
|
||||
{% cache 500 sidebar using="c" %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cache_group
|
||||
(cache_block
|
||||
(cache_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(template
|
||||
(content)))
|
||||
(endcache_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Cache Using An Unquoted Cache Name
|
||||
==================
|
||||
|
||||
{% cache 500 sidebar using=c %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cache_group
|
||||
(cache_block
|
||||
(cache_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(endcache_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Cache Without A Fragment Name
|
||||
:error
|
||||
==================
|
||||
|
||||
{% cache 500 %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Cache With Anything After Using
|
||||
==================
|
||||
|
||||
{% cache 500 sidebar using="c" extra %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cache_group
|
||||
(cache_block
|
||||
(cache_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endcache_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Cache With An Empty Using
|
||||
:error
|
||||
==================
|
||||
|
||||
{% cache 500 sidebar using= %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Cache With An Attribute Fragment Name
|
||||
:error
|
||||
==================
|
||||
|
||||
{% cache 500 sidebar.name %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Cache With Using As Its Fragment Name
|
||||
:error
|
||||
==================
|
||||
|
||||
{% cache 500 using="c" %}x{% endcache %}
|
||||
|
||||
---
|
||||
|
||||
+14
-2
@@ -11,7 +11,10 @@ This is ignored
|
||||
(template
|
||||
(content)
|
||||
(comment_group
|
||||
(comment_block
|
||||
(comment_tag)
|
||||
(comment_content))
|
||||
(endcomment_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -24,7 +27,10 @@ Comment Empty
|
||||
|
||||
(template
|
||||
(content)
|
||||
(comment_group)
|
||||
(comment_group
|
||||
(comment_block
|
||||
(comment_tag))
|
||||
(endcomment_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -40,8 +46,11 @@ This is ignored
|
||||
(template
|
||||
(content)
|
||||
(comment_group
|
||||
(string)
|
||||
(comment_block
|
||||
(comment_tag
|
||||
(string))
|
||||
(comment_content))
|
||||
(endcomment_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -57,5 +66,8 @@ Contains {% if x %} tag-like text
|
||||
(template
|
||||
(content)
|
||||
(comment_group
|
||||
(comment_block
|
||||
(comment_tag)
|
||||
(comment_content))
|
||||
(endcomment_tag))
|
||||
(content))
|
||||
@@ -0,0 +1,115 @@
|
||||
==================
|
||||
Csp Nonce Attr
|
||||
==================
|
||||
|
||||
{% csp_nonce_attr %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(csp_nonce_attr_tag)
|
||||
(content))
|
||||
|
||||
==================
|
||||
Csp Nonce Attr With Argument And As
|
||||
==================
|
||||
|
||||
{% csp_nonce_attr "script" as nonce %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(csp_nonce_attr_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Csp Nonce Attr With Media Keyword
|
||||
==================
|
||||
|
||||
{% csp_nonce_attr media="script" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(csp_nonce_attr_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Csp Nonce Attr Takes One Argument
|
||||
==================
|
||||
|
||||
{% csp_nonce_attr "a" "b" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(csp_nonce_attr_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Csp Nonce Attr Rejects Unknown Keyword
|
||||
==================
|
||||
|
||||
{% csp_nonce_attr other="b" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(csp_nonce_attr_tag
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Csp Nonce Attr Rejects Repeated Keyword
|
||||
==================
|
||||
|
||||
{% csp_nonce_attr media=1 media=2 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(csp_nonce_attr_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Csp Nonce Attr With Media Twice
|
||||
==================
|
||||
|
||||
{% csp_nonce_attr media="a" media="b" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(csp_nonce_attr_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
@@ -10,5 +10,5 @@ Csrf Token
|
||||
|
||||
(template
|
||||
(content)
|
||||
(csrf_token)
|
||||
(csrf_token_tag)
|
||||
(content))
|
||||
@@ -0,0 +1,364 @@
|
||||
==================
|
||||
Custom Tag
|
||||
==================
|
||||
|
||||
{% mytag %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(custom_tag
|
||||
tag_name: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Custom Tag With Arguments
|
||||
==================
|
||||
|
||||
{% mytag a "b" 3 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(custom_tag
|
||||
tag_name: (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_name: (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_name: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Custom Tag With Filtered Argument
|
||||
==================
|
||||
|
||||
{% mytag a|upper %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(custom_tag
|
||||
tag_name: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Custom Tag Does Not Nest
|
||||
==================
|
||||
|
||||
{% mytag %}hi{% endmytag %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(custom_tag
|
||||
tag_name: (identifier))
|
||||
(content)
|
||||
(custom_tag
|
||||
tag_name: (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
|
||||
filter_name: (identifier)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Custom Filter With Argument
|
||||
==================
|
||||
|
||||
{{ x|naturaltime:"y" }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter
|
||||
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
|
||||
filter_name: (identifier))
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Custom Filter In A Tag
|
||||
==================
|
||||
|
||||
{% firstof a|intcomma %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(firstof_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter
|
||||
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 }}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Custom Tag Does Not Cover An End Tag
|
||||
==================
|
||||
|
||||
{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content))
|
||||
|
||||
==================
|
||||
Custom Tag Does Not Cover A Part Of A Tag Group
|
||||
==================
|
||||
|
||||
{% else %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content))
|
||||
|
||||
==================
|
||||
A Name Reserved For A Tag Is Still A Variable
|
||||
==================
|
||||
|
||||
{{ endif }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
A Name Reserved For A Tag Is Still An Argument
|
||||
==================
|
||||
|
||||
{% mytag endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(custom_tag
|
||||
tag_name: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Custom Tag With A Keyword Argument Twice
|
||||
==================
|
||||
|
||||
{% mytag a=1 a=2 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(custom_tag
|
||||
(identifier)
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Custom Tag With Keyword Arguments And As
|
||||
==================
|
||||
|
||||
{% mytag a=1 b=2 as out %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(custom_tag
|
||||
tag_name: (identifier)
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Custom Tag Repeating A Keyword Of The Tag Before It
|
||||
==================
|
||||
|
||||
{% mytag page=1 %}{% mytag page=2 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(custom_tag
|
||||
tag_name: (identifier)
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(custom_tag
|
||||
tag_name: (identifier)
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(content))
|
||||
+28
-4
@@ -11,7 +11,7 @@ Cycle Basic
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cycle
|
||||
(cycle_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
@@ -35,7 +35,7 @@ Cycle With As
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cycle
|
||||
(cycle_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
@@ -60,7 +60,7 @@ Cycle With As And Silent
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cycle
|
||||
(cycle_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
@@ -85,7 +85,7 @@ Cycle Referencing Existing Variable
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cycle
|
||||
(cycle_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
@@ -124,3 +124,27 @@ Cycle Number Run On To Number
|
||||
---
|
||||
|
||||
(ERROR)
|
||||
|
||||
==================
|
||||
Cycle Spanning Lines
|
||||
==================
|
||||
|
||||
{% cycle 1
|
||||
2 as x %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(cycle_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
@@ -10,5 +10,5 @@ Debug
|
||||
|
||||
(template
|
||||
(content)
|
||||
(debug)
|
||||
(debug_tag)
|
||||
(content))
|
||||
@@ -9,7 +9,7 @@ Extends With String
|
||||
|
||||
(template
|
||||
(content)
|
||||
(extends
|
||||
(extends_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
@@ -27,7 +27,7 @@ Extends With Variable
|
||||
|
||||
(template
|
||||
(content)
|
||||
(extends
|
||||
(extends_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
|
||||
+34
-2
@@ -13,10 +13,13 @@ hello
|
||||
(template
|
||||
(content)
|
||||
(filter_group
|
||||
(filter_block
|
||||
(filter_tag
|
||||
(filter_expression
|
||||
(filter))
|
||||
(filter)))
|
||||
(template
|
||||
(content)))
|
||||
(endfilter_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -34,9 +37,38 @@ HELLO WORLD
|
||||
(template
|
||||
(content)
|
||||
(filter_group
|
||||
(filter_block
|
||||
(filter_tag
|
||||
(filter_expression
|
||||
(filter)
|
||||
(filter))
|
||||
(filter)))
|
||||
(template
|
||||
(content)))
|
||||
(endfilter_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Filter With Spaced Chain
|
||||
==================
|
||||
|
||||
<p>
|
||||
{% filter lower
|
||||
| title %}
|
||||
HELLO WORLD
|
||||
{% endfilter %}
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(filter_group
|
||||
(filter_block
|
||||
(filter_tag
|
||||
(filter_expression
|
||||
(filter)
|
||||
(filter)))
|
||||
(template
|
||||
(content)))
|
||||
(endfilter_tag))
|
||||
(content))
|
||||
@@ -8,7 +8,7 @@ Firstof Basic
|
||||
|
||||
(template
|
||||
(content)
|
||||
(firstof
|
||||
(firstof_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
@@ -33,7 +33,7 @@ Firstof With As
|
||||
|
||||
(template
|
||||
(content)
|
||||
(firstof
|
||||
(firstof_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
@@ -42,5 +42,5 @@ Firstof With As
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(identifier))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
+748
-8
@@ -13,12 +13,13 @@ For Basic
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_scope
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
@@ -26,7 +27,8 @@ For Basic
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content))))
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -44,13 +46,14 @@ For With Multiple Variables
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_scope
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
@@ -58,7 +61,8 @@ For With Multiple Variables
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content))))
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -78,12 +82,13 @@ For With Empty
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_scope
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
@@ -92,6 +97,741 @@ For With Empty
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content)))
|
||||
(empty_block
|
||||
(empty_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For Does Not Take An Else
|
||||
==================
|
||||
|
||||
{% for a in b %}x{% else %}y{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With Empty Twice
|
||||
==================
|
||||
|
||||
{% for a in b %}x{% empty %}y{% empty %}z{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(empty_block
|
||||
(empty_tag)
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With An Empty Clause And No Body
|
||||
==================
|
||||
|
||||
{% for a in b %}{% empty %}{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(empty_block
|
||||
(empty_tag))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With A Nested If In Its Empty Clause
|
||||
==================
|
||||
|
||||
{% for a in b %}x{% empty %}{% if q %}y{% endif %}{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(empty_block
|
||||
(empty_tag)
|
||||
(template
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With A Tag In Its Body
|
||||
==================
|
||||
|
||||
{% for x in y %}
|
||||
{% if a %}b{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With A Nested For
|
||||
==================
|
||||
|
||||
{% for x in y %}
|
||||
{% for z in x %}a{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With A Nested With
|
||||
==================
|
||||
|
||||
{% for a in b %}{% with c=1 %}y{% endwith %}{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(with_group
|
||||
(with_block
|
||||
(with_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(template
|
||||
(content)))
|
||||
(endwith_tag))))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With A Cycle In Its Body
|
||||
==================
|
||||
|
||||
{% for a in b %}{% cycle 1 2 %}{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(cycle_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With Multiple Variables And A Nested If
|
||||
==================
|
||||
|
||||
{% for a, b in c %}{% if x %}y{% endif %}{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With Tags In Both Branches
|
||||
==================
|
||||
|
||||
{% for x in y %}
|
||||
{% if a %}b{% endif %}
|
||||
{% empty %}
|
||||
{% with c=1 %}d{% endwith %}
|
||||
{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content)))
|
||||
(empty_block
|
||||
(empty_tag)
|
||||
(template
|
||||
(content)
|
||||
(with_group
|
||||
(with_block
|
||||
(with_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(template
|
||||
(content)))
|
||||
(endwith_tag))
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With A Named Block In Its Body
|
||||
==================
|
||||
|
||||
{% for x in y %}
|
||||
{% block b %}c{% endblock b %}
|
||||
{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(block_block
|
||||
(block_tag
|
||||
name: (push_block))
|
||||
(template
|
||||
(content)))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For Nested In A Tag
|
||||
==================
|
||||
|
||||
{% if a %}
|
||||
{% for x in y %}{% if b %}c{% endif %}{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))))
|
||||
(endfor_tag))
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With An Empty Body
|
||||
==================
|
||||
|
||||
{% for x in y %}{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For Reversed
|
||||
==================
|
||||
|
||||
{% for x in y reversed %}
|
||||
a
|
||||
{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With Multiple Variables Reversed
|
||||
==================
|
||||
|
||||
{% for k,v in y reversed %}
|
||||
a
|
||||
{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With A Filtered Sequence Reversed
|
||||
==================
|
||||
|
||||
{% for x in y|slice:":2" reversed %}
|
||||
a
|
||||
{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter
|
||||
argument: (value
|
||||
(literal
|
||||
(string)))))))
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For Reversed With Empty
|
||||
==================
|
||||
|
||||
{% for x in y reversed %}
|
||||
a
|
||||
{% empty %}
|
||||
b
|
||||
{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(empty_block
|
||||
(empty_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For Reversed With A Tag In Its Body
|
||||
==================
|
||||
|
||||
{% for x in y reversed %}
|
||||
{% if a %}b{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With Reversed Twice
|
||||
==================
|
||||
|
||||
{% for x in y reversed reversed %}a{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With Reversed Before In
|
||||
:error
|
||||
==================
|
||||
|
||||
{% for x reversed in y %}a{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
For Over A Variable Named Reversed
|
||||
==================
|
||||
|
||||
{% for x in reversed reversed %}a{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For Over An Attribute Of A Variable Named Reversed
|
||||
==================
|
||||
|
||||
{% for x in reversed.items %}a{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)
|
||||
(attribute)))))
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For Over A Filtered Variable Named Reversed
|
||||
==================
|
||||
|
||||
{% for x in reversed|last %}a{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
For With Reversed As Its Whole Sequence
|
||||
:error
|
||||
==================
|
||||
|
||||
{% for x in reversed %}a{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
For With Reversed Three Times
|
||||
==================
|
||||
|
||||
{% for x in reversed reversed reversed %}a{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
@@ -0,0 +1,579 @@
|
||||
==================
|
||||
Trans
|
||||
==================
|
||||
|
||||
{% trans "hi" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Translate
|
||||
==================
|
||||
|
||||
{% translate "hi" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Trans With A Variable
|
||||
==================
|
||||
|
||||
{% trans greeting %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Trans With A Filtered Message
|
||||
==================
|
||||
|
||||
{% trans "hi"|upper %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Trans Noop
|
||||
==================
|
||||
|
||||
{% trans "hi" noop %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Trans With As
|
||||
==================
|
||||
|
||||
{% trans "hi" as greeting %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Trans With Context
|
||||
==================
|
||||
|
||||
{% trans "hi" context "menu" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Trans With Every Option
|
||||
==================
|
||||
|
||||
{% trans "hi" context "menu" noop as greeting %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Trans With Its Options Reordered
|
||||
==================
|
||||
|
||||
{% trans "hi" as greeting noop context "menu" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Trans Without A Message
|
||||
:error
|
||||
==================
|
||||
|
||||
{% trans %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Trans With An Option Twice
|
||||
==================
|
||||
|
||||
{% trans "hi" noop noop %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Trans With As Twice
|
||||
:error
|
||||
==================
|
||||
|
||||
{% trans "hi" as x as y %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Trans With An Unknown Option
|
||||
==================
|
||||
|
||||
{% trans "hi" bogus %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Trans With Context And No Value
|
||||
:error
|
||||
==================
|
||||
|
||||
{% trans "hi" context %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Get Available Languages
|
||||
==================
|
||||
|
||||
{% get_available_languages as languages %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_available_languages_tag
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Available Languages Without As
|
||||
:error
|
||||
==================
|
||||
|
||||
{% get_available_languages %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Get Current Language
|
||||
==================
|
||||
|
||||
{% get_current_language as language %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_current_language_tag
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Current Language Bidi
|
||||
==================
|
||||
|
||||
{% get_current_language_bidi as bidi %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_current_language_bidi_tag
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Language Info
|
||||
==================
|
||||
|
||||
{% get_language_info for "de" as info %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_language_info_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Language Info For A Variable
|
||||
==================
|
||||
|
||||
{% get_language_info for code as info %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_language_info_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Language Info Without As
|
||||
:error
|
||||
==================
|
||||
|
||||
{% get_language_info for "de" %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Get Language Info Without For
|
||||
:error
|
||||
==================
|
||||
|
||||
{% get_language_info "de" as info %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Get Language Info List
|
||||
==================
|
||||
|
||||
{% get_language_info_list for codes as info %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_language_info_list_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Language
|
||||
==================
|
||||
|
||||
{% language "de" %}x{% endlanguage %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(language_group
|
||||
(language_block
|
||||
(language_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(template
|
||||
(content)))
|
||||
(endlanguage_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Language With A Variable
|
||||
==================
|
||||
|
||||
{% language code %}x{% endlanguage %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(language_group
|
||||
(language_block
|
||||
(language_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(endlanguage_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Language Without An Argument
|
||||
:error
|
||||
==================
|
||||
|
||||
{% language %}x{% endlanguage %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Language With Two Arguments
|
||||
==================
|
||||
|
||||
{% language "a" "b" %}x{% endlanguage %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(language_group
|
||||
(language_block
|
||||
(language_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endlanguage_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Language Name Filter
|
||||
==================
|
||||
|
||||
{{ "de"|language_name }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Language Bidi Filter
|
||||
==================
|
||||
|
||||
{{ "de"|language_bidi }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Language Name Local Filter
|
||||
==================
|
||||
|
||||
{{ "de"|language_name_local }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Language Name Translated Filter
|
||||
==================
|
||||
|
||||
{{ "de"|language_name_translated }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Language Name Filter With An Argument
|
||||
:error
|
||||
==================
|
||||
|
||||
{{ "de"|language_name:1 }}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Translate With A Context Named After Noop
|
||||
:error
|
||||
==================
|
||||
|
||||
{% trans "hi" context noop %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Translate With A Context From An Attribute Of Noop
|
||||
==================
|
||||
|
||||
{% trans "hi" context noop.x %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)
|
||||
(attribute)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Translate With A Filtered Context Named After Noop
|
||||
==================
|
||||
|
||||
{% trans "hi" context noop|upper %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(translate_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
+366
-19
@@ -11,14 +11,17 @@ Hi
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)
|
||||
(attribute)))))
|
||||
(attribute))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -34,13 +37,16 @@ working
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -60,6 +66,8 @@ other
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
@@ -69,23 +77,175 @@ other
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(template
|
||||
(content))
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(binaryOperator)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(template
|
||||
(content))
|
||||
(number))))))
|
||||
(template
|
||||
(content)))
|
||||
(elif_block
|
||||
(elif_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(binaryOperator)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
If With Elif
|
||||
==================
|
||||
|
||||
{% if x %}a{% elif y %}b{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(elif_block
|
||||
(elif_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
If With Else
|
||||
==================
|
||||
|
||||
{% if x %}a{% else %}b{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
If With An Elif Missing Its Condition
|
||||
==================
|
||||
|
||||
{% if x %}a{% elif %}b{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
If With An Elif After An Else
|
||||
==================
|
||||
|
||||
{% if x %}a{% else %}b{% elif y %}c{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(elif_block
|
||||
(elif_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
If With Two Elses
|
||||
==================
|
||||
|
||||
{% if x %}a{% else %}b{% else %}c{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -101,6 +261,8 @@ yes
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
@@ -137,9 +299,10 @@ yes
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(number))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -151,4 +314,188 @@ If Operator Adjacent To Value
|
||||
|
||||
---
|
||||
|
||||
(ERROR)
|
||||
==================
|
||||
If Is Not None
|
||||
==================
|
||||
|
||||
{% if status is not None %}yes{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(binaryOperator)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(none))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
If Is
|
||||
==================
|
||||
|
||||
{% if x is y %}a{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(binaryOperator)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
If Is Not
|
||||
==================
|
||||
|
||||
{% if x is not y %}a{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(binaryOperator)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
If Is Not Not
|
||||
==================
|
||||
|
||||
{% if x is not not y %}a{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(binaryOperator)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
If With A Repeated Not
|
||||
==================
|
||||
|
||||
{% if not not x %}a{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
If Not Before A Name Beginning With In
|
||||
==================
|
||||
|
||||
{% if not in_stock %}a{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
If Is Before A Name Beginning With Not
|
||||
==================
|
||||
|
||||
{% if x is nothing %}a{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(binaryOperator)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
+189
-1
@@ -11,8 +11,11 @@ content
|
||||
(template
|
||||
(content)
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endifchanged_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -28,6 +31,8 @@ content
|
||||
(template
|
||||
(content)
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
@@ -35,7 +40,190 @@ content
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(endifchanged_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Ifchanged With Else
|
||||
==================
|
||||
|
||||
{% ifchanged %}
|
||||
content
|
||||
{% else %}
|
||||
unchanged
|
||||
{% endifchanged %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag)
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endifchanged_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Ifchanged With Values And Else
|
||||
==================
|
||||
|
||||
{% ifchanged x y|upper %}
|
||||
content
|
||||
{% else %}
|
||||
unchanged
|
||||
{% endifchanged %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endifchanged_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Ifchanged With An Empty Else
|
||||
==================
|
||||
|
||||
{% ifchanged %}
|
||||
content
|
||||
{% else %}{% endifchanged %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag)
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag))
|
||||
(endifchanged_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Ifchanged Nested In The Else Of Another
|
||||
==================
|
||||
|
||||
{% ifchanged %}
|
||||
a
|
||||
{% else %}
|
||||
{% ifchanged %}
|
||||
b
|
||||
{% else %}
|
||||
c
|
||||
{% endifchanged %}
|
||||
{% endifchanged %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag)
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag)
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endifchanged_tag))
|
||||
(content)))
|
||||
(endifchanged_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Ifchanged With Two Elses
|
||||
==================
|
||||
|
||||
{% ifchanged %}
|
||||
a
|
||||
{% else %}
|
||||
b
|
||||
{% else %}
|
||||
c
|
||||
{% endifchanged %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag)
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endifchanged_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Ifchanged With An Argument On Else
|
||||
==================
|
||||
|
||||
{% ifchanged %}
|
||||
a
|
||||
{% else x %}
|
||||
b
|
||||
{% endifchanged %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag)
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endifchanged_tag))
|
||||
(content))
|
||||
+261
-2
@@ -8,7 +8,7 @@ Include With String
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
@@ -25,9 +25,268 @@ Include With Variable
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Include With A Filtered Name
|
||||
==================
|
||||
|
||||
{% include "t.html"|upper %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Include Only
|
||||
==================
|
||||
|
||||
{% include "t.html" only %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Include With Context
|
||||
==================
|
||||
|
||||
{% include "t.html" with a=1 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Include With Several Bindings
|
||||
==================
|
||||
|
||||
{% include "t.html" with a=1 b=2 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Include With Context And Only
|
||||
==================
|
||||
|
||||
{% include "t.html" with a=1 only %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Include With Only Before With
|
||||
==================
|
||||
|
||||
{% include "t.html" only with a=1 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Include With A Binding Twice
|
||||
==================
|
||||
|
||||
{% include "t.html" with a=1 a=2 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Include Without A Name
|
||||
:error
|
||||
==================
|
||||
|
||||
{% include %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Include With Nothing To Bind
|
||||
:error
|
||||
==================
|
||||
|
||||
{% include "t.html" with %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Include With With Twice
|
||||
==================
|
||||
|
||||
{% include "t.html" with a=1 with b=2 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Include With Only Twice
|
||||
==================
|
||||
|
||||
{% include "t.html" only only %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Include With An Unknown Option
|
||||
==================
|
||||
|
||||
{% include "t.html" bogus %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Include With A Legacy Binding
|
||||
:error
|
||||
==================
|
||||
|
||||
{% include "t.html" with a as b %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Include With A Second Name
|
||||
==================
|
||||
|
||||
{% include "t.html" "u.html" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(include_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
@@ -0,0 +1,207 @@
|
||||
==================
|
||||
Localize
|
||||
==================
|
||||
|
||||
{% localize %}x{% endlocalize %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localize_group
|
||||
(localize_block
|
||||
(localize_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endlocalize_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localize On
|
||||
==================
|
||||
|
||||
{% localize on %}x{% endlocalize %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localize_group
|
||||
(localize_block
|
||||
(localize_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endlocalize_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localize Off
|
||||
==================
|
||||
|
||||
{% localize off %}x{% endlocalize %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localize_group
|
||||
(localize_block
|
||||
(localize_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endlocalize_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localize Hugging Its Delimiters
|
||||
==================
|
||||
|
||||
{%localize%}x{%endlocalize%}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localize_group
|
||||
(localize_block
|
||||
(localize_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endlocalize_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localize Nested
|
||||
==================
|
||||
|
||||
{% localize on %}{% localize off %}y{% endlocalize %}{% endlocalize %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localize_group
|
||||
(localize_block
|
||||
(localize_tag)
|
||||
(template
|
||||
(localize_group
|
||||
(localize_block
|
||||
(localize_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endlocalize_tag))))
|
||||
(endlocalize_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localize With Another Argument
|
||||
==================
|
||||
|
||||
{% localize maybe %}x{% endlocalize %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localize_group
|
||||
(localize_block
|
||||
(localize_tag
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endlocalize_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localize With Two Arguments
|
||||
==================
|
||||
|
||||
{% localize on off %}x{% endlocalize %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localize_group
|
||||
(localize_block
|
||||
(localize_tag
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endlocalize_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localize Unclosed
|
||||
==================
|
||||
|
||||
{% localize %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localize_group
|
||||
(localize_block
|
||||
(localize_tag)
|
||||
(template
|
||||
(content)))
|
||||
(missing_endlocalize_tag)))
|
||||
|
||||
==================
|
||||
Localize Filter
|
||||
==================
|
||||
|
||||
{{ x|localize }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Unlocalize Filter
|
||||
==================
|
||||
|
||||
{{ x|unlocalize }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localize Filter With An Argument
|
||||
:error
|
||||
==================
|
||||
|
||||
{{ x|localize:1 }}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Unlocalize Filter With An Argument
|
||||
:error
|
||||
==================
|
||||
|
||||
{{ x|unlocalize:1 }}
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
==================
|
||||
Load Single Library
|
||||
==================
|
||||
|
||||
{% load static %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(load_tag
|
||||
(library
|
||||
(identifier)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Load Multiple Libraries
|
||||
==================
|
||||
|
||||
{% load i18n static %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(load_tag
|
||||
(library
|
||||
(identifier))
|
||||
(library
|
||||
(identifier)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Load Dotted Library
|
||||
==================
|
||||
|
||||
{% load django.contrib %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(load_tag
|
||||
(library
|
||||
(identifier)
|
||||
(identifier)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Load Names From Library
|
||||
==================
|
||||
|
||||
{% load humanize naturaltime from django %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(load_tag
|
||||
(identifier)
|
||||
(identifier)
|
||||
(library
|
||||
(identifier)))
|
||||
(content))
|
||||
@@ -8,7 +8,7 @@ Lorem Words
|
||||
|
||||
(template
|
||||
(content)
|
||||
(lorem
|
||||
(lorem_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
@@ -25,7 +25,7 @@ Lorem Paragraphs Random
|
||||
|
||||
(template
|
||||
(content)
|
||||
(lorem
|
||||
(lorem_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
|
||||
@@ -0,0 +1,992 @@
|
||||
==================
|
||||
Missing Endif At End Of Input
|
||||
==================
|
||||
|
||||
{% if a %}x
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(missing_endif_tag)))
|
||||
|
||||
==================
|
||||
Missing Endfor After Empty
|
||||
==================
|
||||
|
||||
{% for x in y %}a{% empty %}b
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(empty_block
|
||||
(empty_tag)
|
||||
(template
|
||||
(content)))
|
||||
(missing_endfor_tag)))
|
||||
|
||||
==================
|
||||
Missing Endblock At End Of Input
|
||||
==================
|
||||
|
||||
{% block content %}x
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(block_block
|
||||
(block_tag
|
||||
(push_block))
|
||||
(template
|
||||
(content)))
|
||||
(missing_endblock_tag)))
|
||||
|
||||
==================
|
||||
Missing Endpartialdef At End Of Input
|
||||
==================
|
||||
|
||||
{% partialdef p %}x
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(partialdef_group
|
||||
(partialdef_block
|
||||
(partialdef_tag
|
||||
(push_partial))
|
||||
(template
|
||||
(content)))
|
||||
(missing_endpartialdef_tag)))
|
||||
|
||||
==================
|
||||
Missing Endverbatim With An Empty Body
|
||||
==================
|
||||
|
||||
{% verbatim %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(verbatim_group
|
||||
(verbatim_block
|
||||
(verbatim_tag
|
||||
(push_verbatim))
|
||||
(verbatim_content))
|
||||
(missing_endverbatim_tag)))
|
||||
|
||||
==================
|
||||
Missing Endverbatim After A Tag In Its Text
|
||||
==================
|
||||
|
||||
{% verbatim %}{% endif %}x
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(verbatim_group
|
||||
(verbatim_block
|
||||
(verbatim_tag
|
||||
(push_verbatim))
|
||||
(verbatim_content))
|
||||
(missing_endverbatim_tag)))
|
||||
|
||||
==================
|
||||
Missing Endcomment At End Of Input
|
||||
==================
|
||||
|
||||
{% comment %}x
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(comment_group
|
||||
(comment_block
|
||||
(comment_tag)
|
||||
(comment_content))
|
||||
(missing_endcomment_tag)))
|
||||
|
||||
==================
|
||||
Missing Endif Before An Outer End Tag
|
||||
==================
|
||||
|
||||
{% for x in y %}{% if a %}{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))))
|
||||
(missing_endif_tag))))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Unseen Outer Else Is Not A Missing Tag
|
||||
==================
|
||||
|
||||
{% if a %}{% for x in y %}{% else %}b{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(missing_endfor_tag))))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Missing Endwith Before An Outer Endblock
|
||||
==================
|
||||
|
||||
{% block a %}{% with b=1 %}{% endblock a %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(block_block
|
||||
(block_tag
|
||||
(push_block))
|
||||
(template
|
||||
(with_group
|
||||
(with_block
|
||||
(with_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))))
|
||||
(missing_endwith_tag))))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Missing Two End Tags At End Of Input
|
||||
==================
|
||||
|
||||
{% for x in y %}{% if a %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(missing_endif_tag))))
|
||||
(missing_endfor_tag)))
|
||||
|
||||
==================
|
||||
Missing Endblock Before An Outer Name
|
||||
==================
|
||||
|
||||
{% block a %}{% block b %}{% endblock a %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(block_block
|
||||
(block_tag
|
||||
(push_block))
|
||||
(template
|
||||
(block_group
|
||||
(block_block
|
||||
(block_tag
|
||||
(push_block)))
|
||||
(missing_endblock_tag))))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Missing Endpartialdef Before An Outer Name
|
||||
==================
|
||||
|
||||
{% partialdef a %}{% partialdef b %}{% endpartialdef a %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(partialdef_group
|
||||
(partialdef_block
|
||||
(partialdef_tag
|
||||
(push_partial))
|
||||
(template
|
||||
(partialdef_group
|
||||
(partialdef_block
|
||||
(partialdef_tag
|
||||
(push_partial)))
|
||||
(missing_endpartialdef_tag))))
|
||||
(endpartialdef_tag
|
||||
(pop_partial)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Missing Plural At End Of Input
|
||||
==================
|
||||
|
||||
{% blocktrans count n=x %}one
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content)
|
||||
(missing_plural_tag))
|
||||
(missing_endblocktrans_tag)))
|
||||
|
||||
==================
|
||||
Missing Plural Before Endblocktranslate
|
||||
==================
|
||||
|
||||
{% blocktranslate count n=x %}one{% endblocktranslate %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktranslate_block
|
||||
(blocktranslate_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content)
|
||||
(missing_plural_tag))
|
||||
(endblocktranslate_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Missing Endblocktrans After Plural
|
||||
==================
|
||||
|
||||
{% blocktrans count n=x %}one{% plural %}many
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(blocktranslate_group
|
||||
(blocktrans_block
|
||||
(blocktrans_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content)
|
||||
(plural_block
|
||||
(plural_tag)
|
||||
(content)))
|
||||
(missing_endblocktrans_tag)))
|
||||
|
||||
==================
|
||||
Missing Endfor Before A Tag Without Spaces
|
||||
==================
|
||||
|
||||
{% if a %}{% for x in y %}{%endif%}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(missing_endfor_tag))))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Missing Endfor Before A Tag Across Lines
|
||||
==================
|
||||
|
||||
{% if a %}{% for x in y %}{%
|
||||
endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(missing_endfor_tag))))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Missing Endif After A Custom End Tag
|
||||
==================
|
||||
|
||||
{% if a %}{% endmytag %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(custom_tag
|
||||
(identifier))
|
||||
(content)))
|
||||
(missing_endif_tag)))
|
||||
|
||||
==================
|
||||
Second Else After A Nested Else Is Unexpected
|
||||
==================
|
||||
|
||||
{% if a %}{% if b %}x{% else %}y{% else %}z{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content)))
|
||||
(missing_endif_tag)))
|
||||
|
||||
==================
|
||||
Second Empty After A Nested Empty Is Unexpected
|
||||
==================
|
||||
|
||||
{% for x in y %}{% for z in w %}a{% empty %}b{% empty %}c{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(empty_block
|
||||
(empty_tag)
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content)))
|
||||
(missing_endfor_tag)))
|
||||
|
||||
==================
|
||||
Else Of An If Nested In An Else
|
||||
==================
|
||||
|
||||
{% if a %}x{% else %}{% if b %}y{% else %}z{% endif %}{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Elif Of An If Nested In An Elif
|
||||
==================
|
||||
|
||||
{% if a %}x{% elif b %}{% if c %}y{% elif d %}z{% endif %}{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(elif_block
|
||||
(elif_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(elif_block
|
||||
(elif_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Empty Of A For Nested In An Empty
|
||||
==================
|
||||
|
||||
{% for x in y %}a{% empty %}{% for z in w %}b{% empty %}c{% endfor %}{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(empty_block
|
||||
(empty_tag)
|
||||
(template
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(empty_block
|
||||
(empty_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endfor_tag))))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Else Of An Ifchanged Nested In An Else Of An If
|
||||
==================
|
||||
|
||||
{% if a %}x{% else %}{% ifchanged %}y{% else %}z{% endifchanged %}{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag)
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endifchanged_tag))))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Else Of An If Nested In An Else Of An Ifchanged
|
||||
==================
|
||||
|
||||
{% ifchanged %}a{% else %}{% if b %}y{% else %}z{% endif %}{% endifchanged %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag)
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))))
|
||||
(endifchanged_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Third Else Is Unexpected Too
|
||||
==================
|
||||
|
||||
{% if a %}w{% else %}x{% else %}y{% else %}z{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Second And Third Empty Are Unexpected
|
||||
==================
|
||||
|
||||
{% for x in y %}a{% empty %}b{% empty %}c{% empty %}d{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(empty_block
|
||||
(empty_tag)
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Second And Third Else Of An Ifchanged Are Unexpected
|
||||
==================
|
||||
|
||||
{% ifchanged %}a{% else %}b{% else %}c{% else %}d{% endifchanged %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(ifchanged_group
|
||||
(ifchanged_block
|
||||
(ifchanged_tag)
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endifchanged_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Second Else Inside A Block Is Unexpected
|
||||
==================
|
||||
|
||||
{% block a %}{% if b %}x{% else %}y{% else %}z{% endif %}{% endblock %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(block_block
|
||||
(block_tag
|
||||
(push_block))
|
||||
(template
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endif_tag))))
|
||||
(endblock_tag
|
||||
(pop_block)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Second Else Of An If In A For Is Unexpected
|
||||
==================
|
||||
|
||||
{% for x in y %}{% if a %}b{% else %}c{% else %}d{% endif %}{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(for_group
|
||||
(for_block
|
||||
(for_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endif_tag))))
|
||||
(endfor_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Stray End Tag After A Missing One
|
||||
==================
|
||||
|
||||
{% if a %}{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(missing_endif_tag)))
|
||||
|
||||
==================
|
||||
Endblock Naming No Open Block
|
||||
==================
|
||||
|
||||
{% block a %}{% block b %}{% endblock c %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(block_group
|
||||
(block_block
|
||||
(block_tag
|
||||
(push_block))
|
||||
(template
|
||||
(block_group
|
||||
(block_block
|
||||
(block_tag
|
||||
(push_block))
|
||||
(template
|
||||
(unexpected_tag
|
||||
(unexpected_argument))
|
||||
(content)))
|
||||
(missing_endblock_tag))))
|
||||
(missing_endblock_tag)))
|
||||
|
||||
==================
|
||||
Second Else Is Not A Missing Tag
|
||||
==================
|
||||
|
||||
{% if a %}{% else %}{% else %}{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))))
|
||||
(else_block
|
||||
(else_tag)
|
||||
(template
|
||||
(unexpected_tag)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Plural Without Count Is Not A Missing Tag
|
||||
:error
|
||||
==================
|
||||
|
||||
{% blocktrans %}one{% plural %}many{% endblocktrans %}
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
==================
|
||||
Now Basic
|
||||
==================
|
||||
|
||||
{% now "Y-m-d" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(now_tag
|
||||
(string))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Now With As
|
||||
==================
|
||||
|
||||
{% now "Y" as today %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(now_tag
|
||||
(string)
|
||||
variable: (identifier))
|
||||
(content))
|
||||
@@ -8,6 +8,6 @@ Partial Basic
|
||||
|
||||
(template
|
||||
(content)
|
||||
(partial
|
||||
(partial_tag
|
||||
(identifier))
|
||||
(content))
|
||||
@@ -13,10 +13,13 @@ Partialdef Basic
|
||||
(template
|
||||
(content)
|
||||
(partialdef_group
|
||||
(push_partial)
|
||||
(partialdef_block
|
||||
(partialdef_tag
|
||||
(push_partial))
|
||||
(template
|
||||
(content))
|
||||
(pop_partial))
|
||||
(content)))
|
||||
(endpartialdef_tag
|
||||
(pop_partial)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -34,8 +37,11 @@ Partialdef Inline
|
||||
(template
|
||||
(content)
|
||||
(partialdef_group
|
||||
(push_partial)
|
||||
(partialdef_block
|
||||
(partialdef_tag
|
||||
(push_partial))
|
||||
(template
|
||||
(content))
|
||||
(pop_partial))
|
||||
(content)))
|
||||
(endpartialdef_tag
|
||||
(pop_partial)))
|
||||
(content))
|
||||
@@ -8,7 +8,7 @@ Querystring Empty
|
||||
|
||||
(template
|
||||
(content)
|
||||
(query_string)
|
||||
(querystring_tag)
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -21,7 +21,7 @@ Querystring With Key Value
|
||||
|
||||
(template
|
||||
(content)
|
||||
(query_string
|
||||
(querystring_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
@@ -39,11 +39,104 @@ Querystring With Identifier And Key Value
|
||||
|
||||
(template
|
||||
(content)
|
||||
(query_string
|
||||
(identifier)
|
||||
(querystring_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Querystring With Expression Argument And As
|
||||
==================
|
||||
|
||||
{% querystring request.GET page=2 as qs %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(querystring_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)
|
||||
(attribute))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Querystring With Two Keys
|
||||
==================
|
||||
|
||||
{% querystring page=2 size=3 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(querystring_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Querystring With A Key Twice
|
||||
==================
|
||||
|
||||
{% querystring page=2 page=3 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(querystring_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Querystring With A Key Repeated Later
|
||||
==================
|
||||
|
||||
{% querystring a=1 b=2 a=3 %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(querystring_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
@@ -8,7 +8,7 @@ Regroup Basic
|
||||
|
||||
(template
|
||||
(content)
|
||||
(regroup
|
||||
(regroup_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
|
||||
@@ -8,7 +8,7 @@ Resetcycle Bare
|
||||
|
||||
(template
|
||||
(content)
|
||||
(reset_cycle)
|
||||
(resetcycle_tag)
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -21,6 +21,6 @@ Resetcycle With Name
|
||||
|
||||
(template
|
||||
(content)
|
||||
(reset_cycle
|
||||
(resetcycle_tag
|
||||
(identifier))
|
||||
(content))
|
||||
@@ -13,6 +13,9 @@ Spaceless Basic
|
||||
(template
|
||||
(content)
|
||||
(spaceless_group
|
||||
(spaceless_block
|
||||
(spaceless_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endspaceless_tag))
|
||||
(content))
|
||||
@@ -0,0 +1,202 @@
|
||||
==================
|
||||
Static
|
||||
==================
|
||||
|
||||
{% static "a.css" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(static_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Static With A Variable Path
|
||||
==================
|
||||
|
||||
{% static path %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(static_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Static With A Filtered Path
|
||||
==================
|
||||
|
||||
{% static "a.css"|upper %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(static_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Static With As
|
||||
==================
|
||||
|
||||
{% static "a.css" as url %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(static_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Static Without A Path
|
||||
:error
|
||||
==================
|
||||
|
||||
{% static %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Static With A Second Path
|
||||
==================
|
||||
|
||||
{% static "a.css" "b.css" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(static_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Static With Anything After As
|
||||
==================
|
||||
|
||||
{% static "a.css" as url junk %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(static_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(identifier)
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Static Prefix
|
||||
==================
|
||||
|
||||
{% get_static_prefix %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_static_prefix_tag)
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Static Prefix With As
|
||||
==================
|
||||
|
||||
{% get_static_prefix as prefix %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_static_prefix_tag
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Media Prefix With As
|
||||
==================
|
||||
|
||||
{% get_media_prefix as prefix %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_media_prefix_tag
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Static Prefix Without As
|
||||
==================
|
||||
|
||||
{% get_static_prefix prefix %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_static_prefix_tag
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Static Prefix Without A Variable
|
||||
:error
|
||||
==================
|
||||
|
||||
{% get_static_prefix as %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Load Names The Library, Not The Tag
|
||||
==================
|
||||
|
||||
{% load static %}{% static "a.css" %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(load_tag
|
||||
(library
|
||||
(identifier)))
|
||||
(static_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(content))
|
||||
@@ -174,7 +174,7 @@ Template Variable With Filter Argument
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter
|
||||
(value
|
||||
argument: (value
|
||||
(literal
|
||||
(string)))))))
|
||||
(content))
|
||||
@@ -196,7 +196,7 @@ Template Variable Number With Filter Argument
|
||||
(number)))
|
||||
(filter_expression
|
||||
(filter
|
||||
(value
|
||||
argument: (value
|
||||
(literal
|
||||
(number)))))))
|
||||
(content))
|
||||
@@ -210,7 +210,6 @@ Template Variable Literal Adjacent To Name
|
||||
|
||||
---
|
||||
|
||||
(ERROR)
|
||||
|
||||
==================
|
||||
Template Variable With Escaped String
|
||||
@@ -272,7 +271,6 @@ Template Variable Attribute Beginning With Underscore
|
||||
|
||||
---
|
||||
|
||||
(ERROR)
|
||||
|
||||
==================
|
||||
Template Variable Translated String
|
||||
@@ -321,5 +319,369 @@ Template Variable Translated String With Spaces
|
||||
|
||||
---
|
||||
|
||||
(ERROR)
|
||||
|
||||
==================
|
||||
Template Variable Spanning Lines
|
||||
==================
|
||||
|
||||
{{ value
|
||||
|floatformat:2 }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter
|
||||
argument: (value
|
||||
(literal
|
||||
(number)))))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Template Variable With Spaced Filter
|
||||
==================
|
||||
|
||||
{{ value | upper }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Template Variable True
|
||||
==================
|
||||
|
||||
{{ True }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(boolean)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Template Variable False
|
||||
==================
|
||||
|
||||
{{ False }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(boolean)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Template Variable None
|
||||
==================
|
||||
|
||||
{{ None }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(none)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Template Variable Boolean As Filter Argument
|
||||
==================
|
||||
|
||||
{{ value|default:True }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter
|
||||
argument: (value
|
||||
(literal
|
||||
(boolean)))))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Template Variable Name Beginning With A Literal
|
||||
==================
|
||||
|
||||
{{ Truest }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Template Variable Attribute Of A Literal
|
||||
:error
|
||||
==================
|
||||
|
||||
{{ None.x }}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Template Variable Trailing Dot With An Exponent
|
||||
==================
|
||||
|
||||
{{ 1.e5 }}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Template Variable Trailing Dot
|
||||
:error
|
||||
==================
|
||||
|
||||
{{ 1. }}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Template Variable Signed Trailing Dot
|
||||
:error
|
||||
==================
|
||||
|
||||
{{ -1. }}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Braces That Open Nothing Are Text
|
||||
==================
|
||||
|
||||
{% if x %}Hello {} world{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
A Brace Before A Tag Is Text
|
||||
==================
|
||||
|
||||
Hello {{% if x %}a{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
A Brace Before A Comment Is Text
|
||||
==================
|
||||
|
||||
a{{# c #}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_comment)
|
||||
(content))
|
||||
|
||||
==================
|
||||
A Brace At The End Is Text
|
||||
==================
|
||||
|
||||
a{
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content))
|
||||
|
||||
==================
|
||||
A Brace Before The End Tag Of A Group Is Text
|
||||
==================
|
||||
|
||||
{% if x %}Hello {{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Three Braces Open A Variable
|
||||
:error
|
||||
==================
|
||||
|
||||
a{{{ x }}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
A Filter Pipe With No Filter Stays In Its Variable
|
||||
==================
|
||||
|
||||
{{ oops| }}
|
||||
{% if x == 1 %}
|
||||
hello
|
||||
{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(ERROR))
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(binaryOperator)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
A Variable Missing Its Close Ends Where It Cannot Continue
|
||||
==================
|
||||
|
||||
{{ hello }{% if display %}body {% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(missing_variable_close))
|
||||
(content)
|
||||
(if_group
|
||||
(if_block
|
||||
(if_tag
|
||||
(predicate
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(template
|
||||
(content)))
|
||||
(endif_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
A Variable Missing Its Close At The End
|
||||
==================
|
||||
|
||||
{{ hello
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))
|
||||
(missing_variable_close)))
|
||||
@@ -8,7 +8,7 @@ Templatetag Openblock
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_tag_block)
|
||||
(templatetag_tag)
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -21,5 +21,5 @@ Templatetag Closevariable
|
||||
|
||||
(template
|
||||
(content)
|
||||
(template_tag_block)
|
||||
(templatetag_tag)
|
||||
(content))
|
||||
@@ -0,0 +1,311 @@
|
||||
==================
|
||||
Localtime
|
||||
==================
|
||||
|
||||
{% localtime %}x{% endlocaltime %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localtime_group
|
||||
(localtime_block
|
||||
(localtime_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endlocaltime_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localtime On
|
||||
==================
|
||||
|
||||
{% localtime on %}x{% endlocaltime %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localtime_group
|
||||
(localtime_block
|
||||
(localtime_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endlocaltime_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localtime Off
|
||||
==================
|
||||
|
||||
{% localtime off %}x{% endlocaltime %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localtime_group
|
||||
(localtime_block
|
||||
(localtime_tag)
|
||||
(template
|
||||
(content)))
|
||||
(endlocaltime_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localtime With Another Argument
|
||||
==================
|
||||
|
||||
{% localtime maybe %}x{% endlocaltime %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localtime_group
|
||||
(localtime_block
|
||||
(localtime_tag
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endlocaltime_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Localtime With Two Arguments
|
||||
==================
|
||||
|
||||
{% localtime on off %}x{% endlocaltime %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(localtime_group
|
||||
(localtime_block
|
||||
(localtime_tag
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endlocaltime_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Timezone
|
||||
==================
|
||||
|
||||
{% timezone "Europe/Paris" %}x{% endtimezone %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(timezone_group
|
||||
(timezone_block
|
||||
(timezone_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string)))))
|
||||
(template
|
||||
(content)))
|
||||
(endtimezone_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Timezone With A Variable
|
||||
==================
|
||||
|
||||
{% timezone tz %}x{% endtimezone %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(timezone_group
|
||||
(timezone_block
|
||||
(timezone_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(template
|
||||
(content)))
|
||||
(endtimezone_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Timezone With A Filtered Argument
|
||||
==================
|
||||
|
||||
{% timezone tz|upper %}x{% endtimezone %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(timezone_group
|
||||
(timezone_block
|
||||
(timezone_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)))
|
||||
(filter_expression
|
||||
(filter))))
|
||||
(template
|
||||
(content)))
|
||||
(endtimezone_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Timezone Without An Argument
|
||||
:error
|
||||
==================
|
||||
|
||||
{% timezone %}x{% endtimezone %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Timezone With Two Arguments
|
||||
==================
|
||||
|
||||
{% timezone "a" "b" %}x{% endtimezone %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(timezone_group
|
||||
(timezone_block
|
||||
(timezone_tag
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(string))))
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endtimezone_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Current Timezone
|
||||
==================
|
||||
|
||||
{% get_current_timezone as tz %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_current_timezone_tag
|
||||
variable: (identifier))
|
||||
(content))
|
||||
|
||||
==================
|
||||
Get Current Timezone Without As
|
||||
:error
|
||||
==================
|
||||
|
||||
{% get_current_timezone %}
|
||||
|
||||
---
|
||||
|
||||
|
||||
==================
|
||||
Get Current Timezone With Anything After As
|
||||
==================
|
||||
|
||||
{% get_current_timezone as tz junk %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(get_current_timezone_tag
|
||||
(identifier)
|
||||
(unexpected_argument))
|
||||
(content))
|
||||
|
||||
==================
|
||||
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 }}
|
||||
|
||||
---
|
||||
|
||||
+4
-4
@@ -8,7 +8,7 @@ Url Basic
|
||||
|
||||
(template
|
||||
(content)
|
||||
(url_block
|
||||
(url_tag
|
||||
(string))
|
||||
(content))
|
||||
|
||||
@@ -22,7 +22,7 @@ Url With Positional Args
|
||||
|
||||
(template
|
||||
(content)
|
||||
(url_block
|
||||
(url_tag
|
||||
(string)
|
||||
(filtered_value
|
||||
(value
|
||||
@@ -44,7 +44,7 @@ Url With Keyword Args
|
||||
|
||||
(template
|
||||
(content)
|
||||
(url_block
|
||||
(url_tag
|
||||
(string)
|
||||
(identifier)
|
||||
(filtered_value
|
||||
@@ -63,7 +63,7 @@ Url With As
|
||||
|
||||
(template
|
||||
(content)
|
||||
(url_block
|
||||
(url_tag
|
||||
(string)
|
||||
variable: (identifier))
|
||||
(content))
|
||||
+29
-14
@@ -9,9 +9,12 @@ Verbatim Basic
|
||||
(template
|
||||
(content)
|
||||
(verbatim_group
|
||||
(push_verbatim)
|
||||
(verbatim_content)
|
||||
(pop_verbatim))
|
||||
(verbatim_block
|
||||
(verbatim_tag
|
||||
(push_verbatim))
|
||||
(verbatim_content))
|
||||
(endverbatim_tag
|
||||
(pop_verbatim)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -25,8 +28,11 @@ Verbatim Empty
|
||||
(template
|
||||
(content)
|
||||
(verbatim_group
|
||||
(push_verbatim)
|
||||
(pop_verbatim))
|
||||
(verbatim_block
|
||||
(verbatim_tag
|
||||
(push_verbatim)))
|
||||
(endverbatim_tag
|
||||
(pop_verbatim)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -43,9 +49,12 @@ Verbatim Ignores Tags
|
||||
(template
|
||||
(content)
|
||||
(verbatim_group
|
||||
(push_verbatim)
|
||||
(verbatim_content)
|
||||
(pop_verbatim))
|
||||
(verbatim_block
|
||||
(verbatim_tag
|
||||
(push_verbatim))
|
||||
(verbatim_content))
|
||||
(endverbatim_tag
|
||||
(pop_verbatim)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -59,9 +68,12 @@ Verbatim With Name
|
||||
(template
|
||||
(content)
|
||||
(verbatim_group
|
||||
(push_verbatim)
|
||||
(verbatim_content)
|
||||
(pop_verbatim))
|
||||
(verbatim_block
|
||||
(verbatim_tag
|
||||
(push_verbatim))
|
||||
(verbatim_content))
|
||||
(endverbatim_tag
|
||||
(pop_verbatim)))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -77,7 +89,10 @@ Avoid template rendering via the {% verbatim %}{% endverbatim %} block.
|
||||
(template
|
||||
(content)
|
||||
(verbatim_group
|
||||
(push_verbatim)
|
||||
(verbatim_content)
|
||||
(pop_verbatim))
|
||||
(verbatim_block
|
||||
(verbatim_tag
|
||||
(push_verbatim))
|
||||
(verbatim_content))
|
||||
(endverbatim_tag
|
||||
(pop_verbatim)))
|
||||
(content))
|
||||
+143
-2
@@ -11,12 +11,14 @@ With Basic
|
||||
(template
|
||||
(content)
|
||||
(with_group
|
||||
(with_block
|
||||
(with_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier)
|
||||
(attribute))))
|
||||
(attribute)))))
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
@@ -25,6 +27,7 @@ With Basic
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content)))
|
||||
(endwith_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
@@ -40,6 +43,8 @@ With Multiple Variables
|
||||
(template
|
||||
(content)
|
||||
(with_group
|
||||
(with_block
|
||||
(with_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
@@ -49,7 +54,7 @@ With Multiple Variables
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(number)))))
|
||||
(template
|
||||
(content)
|
||||
(template_variable
|
||||
@@ -58,4 +63,140 @@ With Multiple Variables
|
||||
(variable_attribute
|
||||
(identifier)))))
|
||||
(content)))
|
||||
(endwith_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
With Does Not Take An Else
|
||||
==================
|
||||
|
||||
{% with x=1 %}a{% else %}b{% endwith %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(with_group
|
||||
(with_block
|
||||
(with_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(template
|
||||
(content)
|
||||
(unexpected_tag)
|
||||
(content)))
|
||||
(endwith_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
With A Binding Twice
|
||||
==================
|
||||
|
||||
{% with a=1 a=2 %}x{% endwith %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(with_group
|
||||
(with_block
|
||||
(with_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endwith_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
With A Binding Repeated Later
|
||||
==================
|
||||
|
||||
{% with a=1 b=2 a=3 %}x{% endwith %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(with_group
|
||||
(with_block
|
||||
(with_tag
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number))))
|
||||
(unexpected_argument))
|
||||
(template
|
||||
(content)))
|
||||
(endwith_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
With Repeating A Binding Of The Tag Before It
|
||||
==================
|
||||
|
||||
{% with a=1 %}x{% endwith %}{% with a=2 %}y{% endwith %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(with_group
|
||||
(with_block
|
||||
(with_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(template
|
||||
(content)))
|
||||
(endwith_tag))
|
||||
(with_group
|
||||
(with_block
|
||||
(with_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(template
|
||||
(content)))
|
||||
(endwith_tag))
|
||||
(content))
|
||||
|
||||
==================
|
||||
With A Binding Named After A Literal
|
||||
==================
|
||||
|
||||
{% with True=1 %}a{% endwith %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(with_group
|
||||
(with_block
|
||||
(with_tag
|
||||
variable: (identifier)
|
||||
(filtered_value
|
||||
(value
|
||||
(literal
|
||||
(number)))))
|
||||
(template
|
||||
(content)))
|
||||
(endwith_tag))
|
||||
(content))
|
||||
@@ -1,6 +1,5 @@
|
||||
{% load greet from custom.tags %}
|
||||
{% partialdef url %}
|
||||
<li>{% greet %} {{ user }}</li>
|
||||
{% load greet from custom.tags %} {% partialdef url %}
|
||||
<li>{% greet %} {{ user }}</li>
|
||||
{% endpartialdef %}
|
||||
<body>
|
||||
{# hello this is an example file #}
|
||||
@@ -24,9 +23,5 @@
|
||||
<h1>EMPTY</h1>
|
||||
{% endblock %}
|
||||
|
||||
<footer>
|
||||
{% for user in page.footer %}
|
||||
{% partial url %}
|
||||
{% endfor %}
|
||||
</footer>
|
||||
<footer>{% for user in page.footer %} {% partial url %} {% endfor %}</footer>
|
||||
</body>
|
||||
+20
-9
@@ -6,21 +6,22 @@
|
||||
"camelcase": "django",
|
||||
"title": "Django Parser",
|
||||
"scope": "source.django",
|
||||
"file-types": ["html", "txt", "md"],
|
||||
"injection-regex": "^django$",
|
||||
"class-name": "TreeSitterDjango",
|
||||
"file-types": [
|
||||
"html",
|
||||
"txt",
|
||||
"md"
|
||||
],
|
||||
"highlights": "queries/highlights.scm",
|
||||
"injections": "queries/injections.scm",
|
||||
"locals": "queries/locals.scm",
|
||||
"injections": "queries/injections.scm"
|
||||
"injection-regex": "^django$",
|
||||
"class-name": "TreeSitterDjango"
|
||||
}
|
||||
],
|
||||
"bindings": {
|
||||
"node": true
|
||||
},
|
||||
"metadata": {
|
||||
"version": "0.0.1",
|
||||
"description": "DJango grammar for tree-sitter",
|
||||
"version": "0.1.2",
|
||||
"license": "MIT",
|
||||
"description": "Django grammar for tree-sitter",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniel Hanson",
|
||||
@@ -30,5 +31,15 @@
|
||||
"links": {
|
||||
"repository": "https://github.com/danhanson/tree-sitter-django"
|
||||
}
|
||||
},
|
||||
"bindings": {
|
||||
"c": true,
|
||||
"go": true,
|
||||
"java": false,
|
||||
"node": true,
|
||||
"python": true,
|
||||
"rust": true,
|
||||
"swift": true,
|
||||
"zig": false
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user