1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*!
Templar is both a Rust library and a CLI tool for working with templates. The usage and style is
inspired by both Jinja2 and Ansible, though it is not intended to be a clone of either of these.

The goal of the project is to provide fast and flexible dynamic templating, particularly for use with
configurations and local tooling. Despite this, it likely can be adapted for HTML and front end rendering
as well.

# Examples

The templating syntax is likely familiar considering the frameworks that it is based on. For instance, a
simple template may look like this:

```properties
user_name={{ user.name }} {# Replace with the context property 'name' in 'user' #}
full_context={{ . | json("pretty") }} {# Dump the entire context as JSON, '.' is the root node #}
password={{ script('echo hunter2 | md5sum') }} {# Execute a shell command and calculate the MD5 sum #}
```

In addition to simple replacements, more complex expressions can be used.

```markdown
The calculated result is {{ 100 * 5 / 10 }} {#- Prints '50' #}

Today's guest list:
{%- for person in ['Bob', 'Joe', 'Jen', 'Amy')] %}
* {{ person }} will come to the party!
{%- endif %} {#- This will loop everyone in the inline array above, but they array could also come from the context #}
```

# Another templating framework?

Well... yes.

There are many great templating frameworks out there, however they are mostly intended for web or HTML rendering. This leads
to a few drawbacks when used for other purposes.

* Templar has first class support for parsed configuration files. You can create a context directly from a config that is parsed with
  serde or, alternatively, use it for templating serde sub-elements.
* You can opt to parse expressions directly instead of an entire template.
* Context values are lazily processed on access.
* Context values can refer to other context values.
* Extending the base functionality is easy.
* Support for dynamic context nodes that are recalculated on every access. e.g. repeated calls to a template with this content
  `{% if user.isRoot %} {{ do_something() }} {% end if %}` would change if the `user.isRoot` value changes.

# Template Syntax

Much of the syntax is based on the wonderful [Jinja2](https://jinja.palletsprojects.com/en/2.10.x/) project. Here are some
of the currently supported features.

* Value replacement can be done using the `{{ }}` syntax.
    * Literals supported are strings (single, double, or backtick quoted), boolean, numbers (currently parsed as i64), null, arrays, and maps
    * Identifiers that start with an alphabetic character can be referred to directly e.g. `{{ some.value.path }}`
    * The root node can be referred to with `.` allowing things like `{{ . | json }}` to be used to dump the entire context as JSON
    * Identifiers of non-standard type, e.g. starting with a non-alphabetic character, spaces, etc. can be referred to using the
      bracket syntax. e.g. `{{ .['565'] }}`. This also allows array access and identifier of non-standard types (such as boolean).
    * Inline arrays: `{{ [1,2,3,4] }}` and complex nesting also possible e.g. `{{ [1,2, script("echo 'hello world!'"), (5 + 5 | base64)] }}`
    * Inline maps: `{{ {'key': 'value', 'otherKey': { 'nested': 'map' } } }}`
* Control flow can be done using the `{% %}` syntax
    * If/else if: `{% if 10/2 == 5 %}The world is sane!{% else if false %}What universe are we in?{% end if %}`
    * Scoping can be done manually: `{% scope %}I'm in a scope!{% end scope %}`
    * For loops: `{% for thing in lots.of.stuff %} {{ thing['name'] }} {% end for %}`. For loops always enter a new scope.
* Comments use the `{# #}` syntax and will be remitted from the output.
* Whitespace control can be accomplished by adding a `-` to any of the above blocks e.g. `{{- 'no whitespace! -}}`.
    * Whitespace control can be added to one or both sides of the tags. All spaces, new lines, or other whitespace on the side with the `-`
      on it will be removed as if the block is immediately next to the other element.

As documentation is still in progress, see the [kitchen sink](./examples/kitchen_sink.tmpl) for examples of template usage.

# Expression syntax

Everything inside the standard `{{ }}` block is an expression. Each block holds exactly one expression, but that expression can be chained with
many individual operations. A quick overview:

* Math operations: `+ - * / %` these operations are only valid with numeric types
* Equality: `== != < <= > >= && ||`
* Value setting: `=` the left side of this operation must be some identifier e.g. `{{ some.val.path = 'hello world!' }}`
* String concatenation: `~` e.g. `{{ 'Hello' ~ ' ' ~ 'world!' }}` prints "Hello world!"
* Functions: `ident()` e.g. `{{ env('USER') }}` would retrieve the value of the environment variable "USER".
* Filters: `|` e.g. `{{ 'hello world' | upper }}` would use the 'upper' filter to print "HELLO WORLD"

As documentation is still in progress, see the [expression tests](./src/test/expressions.rs) for examples of expression usage.

# Performance

Templar prefers rendering performance over parsing performance. While you should take most benchmarks with a grain of salt, simple templates
render in a few microseconds. On my AMD Ryzen 2700U processor, I can render a simple template about 300,000 times a second on a single thread.

Templates vary a lot though and templates that call out to shell commands or do other complex things will get less performance.

# API

Full API documentation can be found on [docs.rs](https://docs.rs/templar/)
*/

// #![warn(missing_docs)]

#[macro_use]
extern crate lazy_static;

#[macro_use]
extern crate templar_macros;

#[macro_use]
pub mod macros;

pub(crate) use error::*;
use std::{collections::HashMap, sync::Arc};

pub(crate) use execution::*;

pub use {
    self::{
        context::{Context, StandardContext},
        error::TemplarError,
        execution::{Data, InnerData},
        templar::{Templar, TemplarBuilder, Template, TemplateTree},
    },
    unstructured::Document,
};

// #[cfg(feature = "shared-context")]
// pub use context::SharedContext;

pub mod error;

#[cfg(test)]
mod test;

mod context;
mod execution;
mod parser;
mod templar;

// We export these for documentation purposes, but they have no directly usable code
pub mod filters;
pub mod functions;