diff --git a/CHANGELOG.md b/CHANGELOG.md
index e124b45..b0e267c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,10 +3,10 @@
- `UndefinedError` exception
- `UndefinedFactory` typedef
- `Environment`:
- - `Environment({undefined})` argument
- - `undefined` field
+ - `Environment({UndefinedFactory undefined})` argument
+ - `UndefinedFactory undefined` field
- `Template`:
- - `Template({undefined})` argument
+ - `Template({UndefinedFactory undefined})` argument
- Filters:
- `null` (`none` alias)
diff --git a/README.md b/README.md
index 8b82866..92ca039 100644
--- a/README.md
+++ b/README.md
@@ -65,6 +65,22 @@ See also examples with [conduit][conduit_example] and
- `stream` method
- Relative template paths
- Async Support
+- Template Inheritance
+ - Super Blocks
+ - `{{ super.super() }}`
+- List of Control Structures
+ - Extends
+ - Execute non-`block` statements and expressions
+ ```jinja
+ {% extends 'base.html' %}
+ {% set title = 'Index' %}
+ {% macro header() %}
+
{{ title }}
+ {% endmacro %} ```
+ {% block body %}
+ {{ header() }}
+ {% endblock %}
+ ```
- Expressions
- Dart Methods and Properties
- `!.`/`?.`
@@ -73,7 +89,6 @@ See also examples with [conduit][conduit_example] and
- ...
- List of Global Functions
- `lipsum`
- - `dict`
- `cycler`
- `joiner`
- Extensions
@@ -165,6 +180,7 @@ See also examples with [conduit][conduit_example] and
- `{{ string.toUpperCase() }}`
- `{{ list.add(item) }}`
- List of Global Functions
+ - ~~`dict`~~
- `print`
- `range`
- `list`
diff --git a/lib/src/compiler.dart b/lib/src/compiler.dart
index 11ca430..08476c4 100644
--- a/lib/src/compiler.dart
+++ b/lib/src/compiler.dart
@@ -3,16 +3,18 @@ import 'package:jinja/src/nodes.dart';
import 'package:jinja/src/visitor.dart';
import 'package:meta/meta.dart';
+// TODO(renderer): Rename to `StringSinkRendererCompiler`
+// and move to `renderer.dart`. Add `ContextNode` for `ContextCallback`s.
@doNotStore
class RuntimeCompiler implements Visitor {
RuntimeCompiler()
: _imports = {},
- _macroses = {},
+ _macros = {},
_inMacro = false;
final Set _imports;
- final Set _macroses;
+ final Set _macros;
bool _inMacro;
@@ -46,8 +48,8 @@ class RuntimeCompiler implements Visitor {
@override
Call visitCall(Call node, void context) {
- // Modifies Template AST from `loop.cycle(first, second, *list)`
- // to `loop['cycle']([first, second], list)`, which matches
+ // Modifies Template AST from `loop.cycle(first, second)`
+ // to `loop['cycle']([first, second])`, which matches
// [LoopContext.cycle] definition.
//
// TODO(compiler): check name arguments
@@ -59,7 +61,7 @@ class RuntimeCompiler implements Visitor {
: [Array(values: calling.arguments)];
return node.copyWith(
- value: visitNode(node.value, context),
+ value: Item(key: Constant(value: 'cycle'), value: value),
calling: Calling(arguments: visitNodes(arguments, context)),
);
}
@@ -94,7 +96,7 @@ class RuntimeCompiler implements Visitor {
);
}
- if (_inMacro && name == 'caller' || _macroses.contains(name)) {
+ if (_inMacro && name == 'caller' || _macros.contains(name)) {
var calling = visitNode(node.calling, context);
var arguments = calling.arguments;
var keywords = calling.keywords;
@@ -344,7 +346,7 @@ class RuntimeCompiler implements Visitor {
@override
FromImport visitFromImport(FromImport node, void context) {
for (var (name, alias) in node.names) {
- _macroses.add(alias ?? name);
+ _macros.add(alias ?? name);
}
return node.copyWith(template: visitNode(node.template, context));
@@ -378,7 +380,7 @@ class RuntimeCompiler implements Visitor {
@override
Macro visitMacro(Macro node, void context) {
_inMacro = true;
- _macroses.add(node.name);
+ _macros.add(node.name);
var positional = [];
var named = <(Expression, Expression)>[];
@@ -420,17 +422,29 @@ class RuntimeCompiler implements Visitor {
}
@override
- Output visitOutput(Output node, void context) {
+ Node visitOutput(Output node, void context) {
+ if (node.nodes.isEmpty) {
+ return Data();
+ }
+
+ if (node.nodes.length == 1) {
+ return visitNode(node.nodes[0], context);
+ }
+
return node.copyWith(nodes: visitNodes(node.nodes, context));
}
@override
TemplateNode visitTemplateNode(TemplateNode node, void context) {
- return node.copyWith(
- blocks: visitNodes(node.blocks, context),
- macros: visitNodes(node.macros, context),
- body: visitNode(node.body, context),
- );
+ var body = visitNode(node.body, context) as Node;
+ var blocks = [if (body is Block) body, ...body.findAll()];
+ var macros = [if (body is Macro) body, ...body.findAll()];
+
+ if (body is Output && body.nodes.first is Extends) {
+ body = body.nodes.first;
+ }
+
+ return node.copyWith(blocks: blocks, macros: macros, body: body);
}
@override
diff --git a/lib/src/environment.dart b/lib/src/environment.dart
index 0b5c921..d34cd65 100644
--- a/lib/src/environment.dart
+++ b/lib/src/environment.dart
@@ -342,10 +342,8 @@ base class Environment {
}
/// Parse the source code and return the AST node.
- ///
- /// This can be useful for debugging or to extract information from templates.
Node parse(String source, {String? path}) {
- return scan(lex(source), path: path);
+ return Parser(this, path: path).parse(source);
}
/// Load a template from a source string without using [loader].
@@ -564,7 +562,7 @@ base class Template {
/// The global variables for this template.
final Map globals;
- /// Template body node.
+ // @nodoc
@internal
final TemplateNode body;
diff --git a/lib/src/loop.dart b/lib/src/loop.dart
index b529809..b504e10 100644
--- a/lib/src/loop.dart
+++ b/lib/src/loop.dart
@@ -1,197 +1,5 @@
-final class LoopContext extends Iterable