Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ gradlew.bat

.project
.classpath
.settings
.settings
4 changes: 4 additions & 0 deletions third_party/packages/mustache_template/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.0.6

* Adds an example app.

## 2.0.5

* Updates metadata for move to https://github.com/flutter/core-packages.
Expand Down
127 changes: 59 additions & 68 deletions third_party/packages/mustache_template/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<?code-excerpt path-base="example/lib"?>

# Mustache templates

A Dart library to parse and render [mustache templates](https://mustache.github.io/).
Expand All @@ -7,34 +9,32 @@ See the [mustache manual](https://mustache.github.io/mustache.5.html) for detail
This library passes all [mustache specification](https://github.com/mustache/spec/tree/master/specs) tests.

## Example usage
```dart
import 'package:mustache_template/mustache_template.dart';

main() {
var source = '''
{{# names }}
<?code-excerpt "readme_excerpts.dart (example_usage)"?>
```dart
var source = '''
{{# names }}
<div>{{ lastname }}, {{ firstname }}</div>
{{/ names }}
{{^ names }}
<div>No names.</div>
{{/ names }}
{{! I am a comment. }}
''';

var template = Template(source, name: 'template-filename.html');

var output = template.renderString({'names': [
{'firstname': 'Greg', 'lastname': 'Lowe'},
{'firstname': 'Bob', 'lastname': 'Johnson'}
]});

print(output);
}
{{/ names }}
{{^ names }}
<div>No names.</div>
{{/ names }}
{{! I am a comment. }}
''';

var template = Template(source, name: 'template-filename.html');

String output = template.renderString(<String, dynamic>{
'names': <Map<String, String>>[
<String, String>{'firstname': 'Greg', 'lastname': 'Lowe'},
<String, String>{'firstname': 'Bob', 'lastname': 'Johnson'}
]
});
```

A template is parsed when it is created, after parsing it can be rendered any number of times with different values. A TemplateException is thrown if there is a problem parsing or rendering the template.

The Template contstructor allows passing a name, this name will be used in error messages. When working with a number of templates, it is important to pass a name so that the error messages specify which template caused the error.
The Template constructor allows passing a name, this name will be used in error messages. When working with a number of templates, it is important to pass a name so that the error messages specify which template caused the error.

By default all output from `{{variable}}` tags is html escaped, this behaviour can be changed by passing htmlEscapeValues : false to the Template constructor. You can also use a `{{{triple mustache}}}` tag, or a unescaped variable tag `{{&unescaped}}`, the output from these tags is not escaped.

Expand All @@ -53,65 +53,56 @@ By default all output from `{{variable}}` tags is html escaped, this behaviour c

## Nested paths

<?code-excerpt "readme_excerpts.dart (nested_paths)"?>
```dart
var t = Template('{{ author.name }}');
var output = template.renderString({'author': {'name': 'Greg Lowe'}});
var template = Template('{{ author.name }}');
String output = template.renderString(<String, dynamic>{
'author': <String, String>{'name': 'Greg Lowe'}
});
```

## Partials - example usage

<?code-excerpt "readme_excerpts.dart (partials)"?>
```dart
var partial = Template('{{ foo }}', name: 'partial');

var partial = Template('{{ foo }}', name: 'partial');

var resolver = (String name) {
if (name == 'partial-name') { // Name of partial tag.
return partial;
}
};

var t = Template('{{> partial-name }}', partialResolver: resolver);
Template? resolver(String name) {
if (name == 'partial-name') {
// Name of partial tag.
return partial;
}
return null;
}

var output = t.renderString({'foo': 'bar'}); // bar
var t = Template('{{> partial-name }}', partialResolver: resolver);

String output = t.renderString(<String, dynamic>{'foo': 'bar'});
```

## Lambdas - example usage

<?code-excerpt "readme_excerpts.dart (lambdas)"?>
```dart
var t = Template('{{# foo }}');
var lambda = (_) => 'bar';
t.renderString({'foo': lambda}); // bar
```

```dart
var t = Template('{{# foo }}hidden{{/ foo }}');
var lambda = (_) => 'shown';
t.renderString('foo': lambda); // shown
```

```dart
var t = Template('{{# foo }}oi{{/ foo }}');
var lambda = (LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
t.renderString({'foo': lambda}); // <b>OI</b>
```

```dart
var t = Template('{{# foo }}{{bar}}{{/ foo }}');
var lambda = (LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
t.renderString({'foo': lambda, 'bar': 'pub'}); // <b>PUB</b>
```

```dart
var t = Template('{{# foo }}{{bar}}{{/ foo }}');
var lambda = (LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
t.renderString({'foo': lambda, 'bar': 'pub'}); // <b>PUB</b>
// Simple lambda
var t1 = Template('{{# foo }}inner{{/ foo }}');
Object lambda1(Object? _) => 'bar';

// Lambda returning text for a hidden section
var t2 = Template('{{# foo }}hidden{{/ foo }}');
Object lambda2(Object? _) => 'shown';

// Lambda Context
var t3 = Template('{{# foo }}oi{{/ foo }}');
Object lambda3(LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';

// Lambda Context with variables
var t4 = Template('{{# foo }}{{bar}}{{/ foo }}');
Object lambda4(LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';

// Lambda Context re-parsing source
var t5 = Template('{{# foo }}{{bar}}{{/ foo }}');
Object lambda5(LambdaContext ctx) => ctx.renderSource('${ctx.source} {{cmd}}');
```

In the following example `LambdaContext.renderSource(source)` re-parses the source string in the current context, this is the default behaviour in many mustache implementations. Since re-parsing the content is slow, and often not required, this library makes this step optional.

```dart
var t = Template('{{# foo }}{{bar}}{{/ foo }}');
var lambda = (LambdaContext ctx) => ctx.renderSource(ctx.source + ' {{cmd}}');
t.renderString({'foo': lambda, 'bar': 'pub', 'cmd': 'build'}); // pub build
```
In the last lambda example `LambdaContext.renderSource(source)` re-parses the source string in the current context, this is the default behaviour in many mustache implementations. Since re-parsing the content is slow, and often not required, this library makes this step optional.
102 changes: 102 additions & 0 deletions third_party/packages/mustache_template/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2026 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: avoid_print

import 'package:mustache_template/mustache_template.dart';

/// The main entrypoint for the example app.
void main() {
exampleUsage();
nestedPaths();
partialsExample();
lambdasExample();
}

/// Demonstrates basic usage of mustache templates.
void exampleUsage() {
const source = '''
{{# names }}
<div>{{ lastname }}, {{ firstname }}</div>
{{/ names }}
{{^ names }}
<div>No names.</div>
{{/ names }}
{{! I am a comment. }}
''';

final template = Template(source, name: 'template-filename.html');

final String output = template.renderString(<String, dynamic>{
'names': <Map<String, String>>[
<String, String>{'firstname': 'Greg', 'lastname': 'Lowe'},
<String, String>{'firstname': 'Bob', 'lastname': 'Johnson'},
],
});

print(output);
}

/// Demonstrates how to access nested map properties.
void nestedPaths() {
final template = Template('The author is {{ author.name }}');
final String output = template.renderString(<String, dynamic>{
'author': <String, String>{'name': 'Greg Lowe'},
});
print(output);
}

/// Demonstrates the usage of partials with a custom resolver.
void partialsExample() {
final partial = Template('{{ foo }}', name: 'partial');

Template? resolver(String name) {
if (name == 'partial-name') {
// Name of partial tag.
return partial;
}
return null;
}

final t = Template('{{> partial-name }}', partialResolver: resolver);

final String output = t.renderString(<String, dynamic>{'foo': 'bar'});
print(output); // bar
}

/// Demonstrates various usages of lambdas, including hidden sections and lambda contexts.
void lambdasExample() {
// Simple lambda
final t1 = Template('{{# foo }}inner{{/ foo }}');
Object lambda1(Object? _) => 'bar';
print(t1.renderString(<String, dynamic>{'foo': lambda1})); // bar

// Lambda returning text for a hidden section
final t2 = Template('{{# foo }}hidden{{/ foo }}');
Object lambda2(Object? _) => 'shown';
print(t2.renderString(<String, dynamic>{'foo': lambda2})); // shown

// Lambda Context
final t3 = Template('{{# foo }}oi{{/ foo }}');
Object lambda3(LambdaContext ctx) =>
'<b>${ctx.renderString().toUpperCase()}</b>';
print(t3.renderString(<String, dynamic>{'foo': lambda3})); // <b>OI</b>

// Lambda Context with variables
final t4 = Template('{{# foo }}{{bar}}{{/ foo }}');
Object lambda4(LambdaContext ctx) =>
'<b>${ctx.renderString().toUpperCase()}</b>';
print(t4.renderString(
<String, dynamic>{'foo': lambda4, 'bar': 'pub'})); // <b>PUB</b>

// Lambda Context re-parsing source
final t5 = Template('{{# foo }}{{bar}}{{/ foo }}');
Object lambda5(LambdaContext ctx) =>
ctx.renderSource('${ctx.source} {{cmd}}');
print(t5.renderString(<String, dynamic>{
'foo': lambda5,
'bar': 'pub',
'cmd': 'build',
})); // pub build
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright 2026 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file exists solely to host compiled excerpts for README.md, and is not
// intended for use as an actual example application.

// ignore_for_file: avoid_print
// ignore_for_file: omit_local_variable_types
// ignore_for_file: strict_raw_type
// ignore_for_file: prefer_final_locals

import 'package:mustache_template/mustache_template.dart';

/// Example for basic usage of a mustache template.
void exampleUsageSnippet() {
// #docregion example_usage
var source = '''
{{# names }}
<div>{{ lastname }}, {{ firstname }}</div>
{{/ names }}
{{^ names }}
<div>No names.</div>
{{/ names }}
{{! I am a comment. }}
''';

var template = Template(source, name: 'template-filename.html');

String output = template.renderString(<String, dynamic>{
'names': <Map<String, String>>[
<String, String>{'firstname': 'Greg', 'lastname': 'Lowe'},
<String, String>{'firstname': 'Bob', 'lastname': 'Johnson'}
]
});
// #enddocregion example_usage

print(output);
}

/// Example for rendering nested paths in a template.
void nestedPathsSnippet() {
// #docregion nested_paths
var template = Template('{{ author.name }}');
String output = template.renderString(<String, dynamic>{
'author': <String, String>{'name': 'Greg Lowe'}
});
// #enddocregion nested_paths
print(output);
}

/// Example for using partials.
void partialsSnippet() {
// #docregion partials
var partial = Template('{{ foo }}', name: 'partial');

Template? resolver(String name) {
if (name == 'partial-name') {
// Name of partial tag.
return partial;
}
return null;
}

var t = Template('{{> partial-name }}', partialResolver: resolver);

String output = t.renderString(<String, dynamic>{'foo': 'bar'});
// #enddocregion partials
print(output); // bar
}

/// Example for using lambdas in a template.
void lambdasSnippet() {
// #docregion lambdas
// Simple lambda
var t1 = Template('{{# foo }}inner{{/ foo }}');
Object lambda1(Object? _) => 'bar';
// #enddocregion lambdas
print(t1.renderString(<String, dynamic>{'foo': lambda1})); // bar

// #docregion lambdas
// Lambda returning text for a hidden section
var t2 = Template('{{# foo }}hidden{{/ foo }}');
Object lambda2(Object? _) => 'shown';
// #enddocregion lambdas
print(t2.renderString(<String, dynamic>{'foo': lambda2})); // shown

// #docregion lambdas
// Lambda Context
var t3 = Template('{{# foo }}oi{{/ foo }}');
Object lambda3(LambdaContext ctx) =>
'<b>${ctx.renderString().toUpperCase()}</b>';
// #enddocregion lambdas
print(t3.renderString(<String, dynamic>{'foo': lambda3})); // <b>OI</b>

// #docregion lambdas
// Lambda Context with variables
var t4 = Template('{{# foo }}{{bar}}{{/ foo }}');
Object lambda4(LambdaContext ctx) =>
'<b>${ctx.renderString().toUpperCase()}</b>';
// #enddocregion lambdas
print(t4.renderString(
<String, dynamic>{'foo': lambda4, 'bar': 'pub'})); // <b>PUB</b>

// #docregion lambdas
// Lambda Context re-parsing source
var t5 = Template('{{# foo }}{{bar}}{{/ foo }}');
Object lambda5(LambdaContext ctx) =>
ctx.renderSource('${ctx.source} {{cmd}}');
// #enddocregion lambdas
print(t5.renderString(<String, dynamic>{
'foo': lambda5,
'bar': 'pub',
'cmd': 'build'
})); // pub build
}
Loading