Skip to content
Draft
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
1 change: 1 addition & 0 deletions third_party/packages/mustache_template/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* Updates metadata for move to https://github.com/flutter/core-packages.
* Updates minimum supported SDK version to Flutter 3.38/Dart 3.10.
* Adds example app demonstrating basic templates, nested paths, partials, and lambdas.

## 2.0.4

Expand Down
112 changes: 45 additions & 67 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,29 +9,25 @@ 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

<?code-excerpt "readme_excerpts.dart (basic)"?>
```dart
import 'package:mustache_template/mustache_template.dart';

main() {
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);
}
final 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 output = template.renderString({
'names': [
{'firstname': 'Greg', 'lastname': 'Lowe'},
{'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.
Expand All @@ -53,65 +51,45 @@ By default all output from `{{variable}}` tags is html escaped, this behaviour c

## Nested paths

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

## Partials - example usage

<?code-excerpt "readme_excerpts.dart (partials)"?>
```dart

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

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

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

var output = t.renderString({'foo': 'bar'}); // bar

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

## Lambdas - example usage

<?code-excerpt "readme_excerpts.dart (lambda-render-string)"?>
```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>
final template = Template('{{# foo }}{{bar}}{{/ foo }}');
final lambda =
(LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
final output =
template.renderString({'foo': lambda, 'bar': 'pub'}); // <b>PUB</b>
```

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.

<?code-excerpt "readme_excerpts.dart (lambda-render-source)"?>
```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
```
final template = Template('{{# foo }}{{bar}}{{/ foo }}');
final lambda =
(LambdaContext ctx) => ctx.renderSource(ctx.source + ' {{cmd}}');
final output = template
.renderString({'foo': lambda, 'bar': 'pub', 'cmd': 'build'}); // pub build
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// ignore_for_file: avoid_print

import 'package:mustache_template/mustache_template.dart';

void renderBasicTemplate() {
// #docregion basic
final 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 output = template.renderString({
'names': [
{'firstname': 'Greg', 'lastname': 'Lowe'},
{'firstname': 'Bob', 'lastname': 'Johnson'},
],
});
// #enddocregion basic
print(output);
}

void renderNestedPaths() {
// #docregion nested
final template = Template('{{ author.name }}');
final output = template.renderString({
'author': {'name': 'Greg Lowe'},
});
// #enddocregion nested
print(output);
}

void renderPartials() {
// #docregion partials
final partial = Template('{{ foo }}', name: 'partial');
final resolver = (String name) {
if (name == 'partial-name') {
return partial;
}
return null;
};
final template = Template('{{> partial-name }}', partialResolver: resolver);
final output = template.renderString({'foo': 'bar'}); // bar
// #enddocregion partials
print(output);
}

void renderLambdaRenderString() {
// #docregion lambda-render-string
final template = Template('{{# foo }}{{bar}}{{/ foo }}');
final lambda = (LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
final output = template.renderString({'foo': lambda, 'bar': 'pub'}); // <b>PUB</b>
Comment on lines +55 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This line exceeds the 80-character limit and is not formatted according to the Dart auto-formatter. Formatting this file is also important because the code excerpt tool will otherwise overwrite the formatted code in README.md with this unformatted version.

  final lambda =
      (LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
  final output = template.renderString({
    'foo': lambda,
    'bar': 'pub',
  }); // <b>PUB</b>
References
  1. Code should follow the Flutter style guide, and use the Dart auto-formatter. (link)

// #enddocregion lambda-render-string
print(output);
}

void renderLambdaRenderSource() {
// #docregion lambda-render-source
final template = Template('{{# foo }}{{bar}}{{/ foo }}');
final lambda = (LambdaContext ctx) => ctx.renderSource(ctx.source + ' {{cmd}}');
final output = template.renderString({'foo': lambda, 'bar': 'pub', 'cmd': 'build'}); // pub build
Comment on lines +64 to +65

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These lines exceed the 80-character limit and are not formatted according to the Dart auto-formatter. Formatting this file is also important because the code excerpt tool will otherwise overwrite the formatted code in README.md with this unformatted version.

Suggested change
final lambda = (LambdaContext ctx) => ctx.renderSource(ctx.source + ' {{cmd}}');
final output = template.renderString({'foo': lambda, 'bar': 'pub', 'cmd': 'build'}); // pub build
final lambda =
(LambdaContext ctx) => ctx.renderSource(ctx.source + ' {{cmd}}');
final output = template.renderString({
'foo': lambda,
'bar': 'pub',
'cmd': 'build',
}); // pub build
References
  1. Code should follow the Flutter style guide, and use the Dart auto-formatter. (link)

// #enddocregion lambda-render-source
print(output);
}

void main() {
renderBasicTemplate();
renderNestedPaths();
renderPartials();
renderLambdaRenderString();
renderLambdaRenderSource();
}
11 changes: 11 additions & 0 deletions third_party/packages/mustache_template/example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: mustache_template_examples
description: Example code for mustache_template usage
version: 0.0.1
publish_to: none

environment:
sdk: ^3.10.0

dependencies:
mustache_template:
path: ../
59 changes: 59 additions & 0 deletions third_party/packages/mustache_template/test/example_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'package:mustache_template/mustache_template.dart';
import 'package:test/test.dart';

void main() {
test('renders basic list template', () {
final source = '''
{{# names }}
<div>{{ lastname }}, {{ firstname }}</div>
{{/ names }}
{{^ names }}
<div>No names.</div>
{{/ names }}
''';
final template = Template(source, name: 'template-filename.html');
final output = template.renderString({
'names': [
{'firstname': 'Greg', 'lastname': 'Lowe'},
{'firstname': 'Bob', 'lastname': 'Johnson'},
],
});
expect(output, contains('Lowe, Greg'));
expect(output, contains('Johnson, Bob'));
});

test('renders nested paths', () {
final template = Template('{{ author.name }}');
final output = template.renderString({
'author': {'name': 'Greg Lowe'},
});
expect(output, equals('Greg Lowe'));
});

test('renders partials', () {
final partial = Template('{{ foo }}', name: 'partial');
final resolver = (String name) {
if (name == 'partial-name') {
return partial;
}
return null;
};
final template = Template('{{> partial-name }}', partialResolver: resolver);
final output = template.renderString({'foo': 'bar'});
expect(output, equals('bar'));
});

test('renders lambda using renderString', () {
final template = Template('{{# foo }}{{bar}}{{/ foo }}');
final lambda = (LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
final output = template.renderString({'foo': lambda, 'bar': 'pub'});
expect(output, equals('<b>PUB</b>'));
});

test('renders lambda using renderSource', () {
final template = Template('{{# foo }}{{bar}}{{/ foo }}');
final lambda = (LambdaContext ctx) => ctx.renderSource(ctx.source + ' {{cmd}}');
final output = template.renderString({'foo': lambda, 'bar': 'pub', 'cmd': 'build'});
expect(output, equals('pub build'));
});
}