An open API service indexing awesome lists of open source software.

https://github.com/serverpod/whiskers

A templating library that implements the Mustache template specification with some extensions.
https://github.com/serverpod/whiskers

Last synced: 17 days ago
JSON representation

A templating library that implements the Mustache template specification with some extensions.

Awesome Lists containing this project

README

          

# Whiskers - Mustache templates with extensions

A Dart library for parsing and rendering [Mustache templates](https://mustache.github.io/). It is derived from [mustache_template](https://pub.dev/packages/mustache_template) and adds an optional `onMissingVariable` callback to `renderString` and `render`. This makes it possible to extend templates with custom behavior. It is used by the web server in [Serverpod](https://serverpod.dev) for cache busting.

![Whiskers logo](https://raw.githubusercontent.com/serverpod/whiskers/main/misc/whiskers.webp)

See the [mustache manual](https://mustache.github.io/mustache.5.html) for detailed usage information.

This library passes all [mustache specification](https://github.com/mustache/spec/tree/master/specs) tests.

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

main() {
var source = '''
{{# names }}

{{ lastname }}, {{ firstname }}

{{/ names }}
{{^ names }}
No names.

{{/ 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);
}
```

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` constructor allows passing a `name`. This name is used in error
messages. When working with multiple templates, it is helpful to pass a name so
that error messages clearly identify which template caused the error.

By default, all output from `{{variable}}` tags is HTML-escaped. This behavior
can be changed by passing `htmlEscapeValues: false` to the `Template`
constructor. You can also use a `{{{triple mustache}}}` tag or an unescaped
variable tag like `{{&unescaped}}`; output from those tags is not escaped.

## Handling missing variables

```dart
import 'package:whiskers/whiskers.dart';

void main() {
final template = Template(
'',
name: 'index.html',
);

final output = template.renderString(
const {},
onMissingVariable: (name, _) {
if (name == 'cacheBuster') {
return '20260408';
}
return null;
},
);

print(output);
}
```

If `onMissingVariable` returns a string, that value is rendered in place of the
missing variable. If it returns `null`, the default behavior is preserved:
strict mode throws and lenient mode renders nothing.

## Differences between strict mode and lenient mode

### Strict mode (default)

* Tag names may only contain the characters a-z, A-Z, 0-9, underscore, period and minus. Other characters in tags will cause a TemplateException to be thrown during parsing.

* During rendering, if no map key or object member which matches the tag name is found, then a TemplateException will be thrown.

### Lenient mode

* Tag names may use any characters.
* During rendering, if no map key or object member which matches the tag name is found, then silently ignore and output nothing.

## Nested paths

```dart
var t = Template('{{ author.name }}');
var output = template.renderString({'author': {'name': 'Greg Lowe'}});
```

## Partials - example usage

```dart

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);

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

```

## Lambdas - example usage

```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) => '${ctx.renderString().toUpperCase()}';
t.renderString({'foo': lambda}); // OI
```

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

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

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
```