CLI for templating based on JSON, YAML or HCL configuration.
Inspired by Consul Template, Sprig, Handlebars, Mustache, Jinja, and the Kotlin, Rust Go and Python standard libraries.
Install with
cargo install template-cli
You can download a pre-compiled binaries for multiple architectures and operating systems from the Latest Release page.
The binaries are shipped as Docker images.
In the usage instructions below, replace all usages of template
with docker run --rm ghcr.io/hiddewie/template
.
template --template ./path/to/template.template --configuration ./path/to/configuration.json
Show the usage information with the --help
option:
template --help
which outputs:
CLI for templating based on JSON, YAML or HCL configuration
Usage: template [OPTIONS] --template <TEMPLATE> --configuration <CONFIGURATION>
Options:
-t, --template <TEMPLATE> Absolute or relative path to the template file
-c, --configuration <CONFIGURATION> Absolute or relative path to the configuration file. Provide `-` as path to read the configuration input from the standard input stream
-f, --format <FORMAT> Specify the format of the configuration input. Useful when the configuration file has a non-standard extension, or when the input is given in the standard input stream [possible values: json, hcl, yaml]
-h, --help Print help information
-V, --version Print version information
The output is rendered to the standard output stream. Log messages are output to the standard error stream. The log level can be controlled using the RUST_LOG
environment variable.
This tool combines well with other command-line utilities, following the UNIX philosophy, for example:
curl -s 'https://dummyjson.com/products' \
| jq '{ items: .products }' \
| template --configuration - --template products.template \
| head -n 5 \
> product-list.txt
0
: Success. The standard output contains the rendered template.1
: Template file cannot be read.2
: One of the required CLI options is not given.3
: Configuration file cannot be read.4
: Configuration file cannot be parsed.5
: Template file cannot be parsed.6
: Template cannot be rendered.101
: Panic. An unexpected error has occurred, and was not handled correctly. Please create an issue to report the configuration, the template and the error output.
This software:
- reads the configuration file or standard input
- reads the template file
- reads the environment to set the log level, and if the
environment
function is used - does not write any files
- does not make any network connections
If the configuration file has a different extension, or when the configuration is given in the standard input stream, you can configure the configuration format with the --format
option.
See Pest grammar for formal template grammar.
It is recommended to use the extension .template
for template files.
Configuration
{
"value": "Hello",
"nested": {
"value": "world"
}
}
with template
{% value %} {% nested.value %}{% does_not_exist %}!
renders output
Hello world!
The YAML configuration file
value: Hello
nested:
value: world
or the HCL configuration file
value = "Hello"
nested {
value = "world"
}
will give equivalent output.
Comments are ignored and the content will not be rendered as output
This is rendered {# comment #}
{# {% for item in array %}
This is not a loop
{% end %} #}
This is rendered
Null: {% null %}
Boolean: {% true %} and {% false %}
Integer: {% 0 %}, {% -0 %}, {% -100 %} and {% 100 %}
Floating point: {% .0 %}, {% 0. %}, {% -1.0 %}, {% 10.47e1 %} and {% -1.47e-10 %}
String: {% "" %} and {% "value" %}
Array: {% [] %}, {% [1] %} and {% ["", null, expression, [], {}, ] %}
Dictionary: {% {} %}, {% {a:1} %} and {% {item: expression, " space ": "spacy", "array": [], } %}
Test expressions with if
/ unless
, elif
and else
:
{% if some_expression %}
Rendered when the expression is truthy
{% elif else_if_expression %}
Rendered when the above is not true and the expression is truthy
{% else %}
Rendered when the above are not truthy
{% end %}
{% unless expression %}
Rendered when the expression is falsy
{% end %}
Loop over arrays with for
and else
:
{% for item in array_value %}
Rendered content for each item
reference a property in iteration array: {% item.property %}
0-based index: {% loop.index0 %}
1-based index: {% loop.index1 %}
true if this is the first iteration: {% loop.first %}
true if this is the last iteration: {% loop.last %}
the number of iterations in the loop: {% loop.size %}
alternate items in an array, treating it as circular: {% loop.index0 | alternate(["one", "two", "three"]) %}
{% else %}
Rendered when the array did not contain any values
{% end %}
Notice that within the for
loop, the loop
variable provides information about the loop iteration.
Set a variable within a block using with
:
{% with a = 1 %}
Value is {% a %}
{% end %}
Apply a function in a template by using the pipe |
operator:
{% value | function1 | function2 | function3 %}
Some functions take one or more arguments, which can be passed by using parentheses:
{% value | function(argument1, argument2) %}
default(value)
: default value if the argument is falsy.coalesce(value)
: default value if the argument isnull
.toString
: transform the value to a string.empty
: whether the value is not "truthy", i.e.null
,0
,0.0
,-0.0
,""
," "
,[]
or{}
.toJson
,toPrettyJson
: format a value to JSON, either compact or multi-line indented.
length
: length of the string.upperCase
: transform a string into upper case.lowerCase
: transform a string into lower case.kebabCase
: transform a string into kebab case:lowercase-words-joined-with-dashes
.snakeCase
: transform a string into snake case:lowercase_words_joined_with_underscores
.camelCase
: transform a string into camel case:capitalizedWordsWithoutSpaces
.pascalCase
: transform a string into pascal case:CapitalizedWordsWithoutSpaces
.capitalize
: make the first character uppercase.capitalizeWords
: make the first character of every word uppercase.environment
: read an environment variable.reverse
: the string in reverse order.split(splitter)
: split the string for each occurrence ofsplitter
.lines
: split the string into an array of lines. Leading and trailing whitespace will be dropped.parseBoolean
: parses a boolean.parseInteger
: parses an integer.parseDecimal
: parses a decimal number.parseNumber
: parses an integer or decimal number.substring(from)
,substring(from, to)
: creates a substring from the string.from
is inclusive,to
is exclusive.take(n)
: takes the firstn
characters from the string.drop(n)
: drops the firstn
characters from the string.fromJson
: parse a value from JSON.abbreviate
: ensure the value is not longer thann
characters. If it is longer, the value will be shortened untiln-1
characters, and suffixed with…
.trimLeft
,trimRight
,trim
: trim the left, right or both sides of the string from whitespace.matches(regex)
: checks if the string matches a regular expression.replace(search, replacement)
: replace the search string with the replacement.regexReplace(search, replacement)
: replace matches of the regular expression with the replacement. The replacement may contain$0
(entire match),$1
,$2
, etc. for matched groups, and$name
for matched named groups.contains(substring)
: whether the string contains the substring.startsWith(start)
,endsWith(end)
: whether the string starts or ends with the given value.
length
: length of the array.reverse
: the array in reverse order.take(n)
: takes the firstn
items from the array.drop(n)
: drops the firstn
items from the array.first
: the first item from the array, if it exists.last
: the last item from the array, if it exists.index(n)
: the nth item from the array, if it exists.contains(value)
: whether the array contains the value.unique
: remove all duplicates from the array.all
: true if all arguments are truthy, ∧.any
: true if any arguments are truthy, ∨.none
: true if all of the arguments are falsy.some
: true if any of the arguments are falsy.chunked(items, overlap)
: chunks the array into an array of arrays, each subarray at mostitems
items, withoverlap
overlapping items with the previous chunk.
length
: size of the dictionarycontainsKey(key)
: whether the array contains the key.containsValue(value)
: whether the array contains the value.keys
: the keys of the dictionary.values
: the values of the dictionary.invert
: the values become the keys and the keys become the values. Values can only be strings. Duplicate values are not preserved.
negate
: negation of the boolean, ¬.
parseFormatDateTime(parse, format)
: parse the date/time, and format it to the given format. If the input is the stringnow
, it will be parsed to the current instant. Otherwise, the parsing and formatting follows the strftime specifiers.
Horizontal whitespace before a flow control tag, and a single newline after a flow control tag will be removed. This behaviour ensures that tags can be put on a separate line without producing a significant amount of whitespace.
The debug
statement can be used to log an expression and it's evaluated result without outputting the content into the templated output:
Template content...
{% debug some_value %}
... more template content
Logged output:
[2023-03-05T11:18:56Z INFO template_cli::evaluate] Debug expression: some_value = {"b":"c"}
The assert
function can be used to assert the expected value of an expression. If the assertion fails, the template rendering will fail with an error indicated by the given assertion message. The template:
{% true | assert(true, "This is fine") %}
{% false | assert(true, "This is not OK") %}
will exit with an error code, and log the output
[2023-03-19T16:02:48Z ERROR template] ERROR: Could not render template: Assertion failed: Expected value 'true' but found 'false': This is not OK
cargo build
cargo test
Go to the Release workflow.
Click the Run workflow button, and give the following inputs:
- Branch:
master
- Version: The next version to release, e.g.
1.2.3