Skip to main content

Templates

Templates can be used in several places to make configuration dynamic or parametrize text outputs, be them strings, messages or even whole text files.

Nyx uses Handlebars templates passing the engine the internal state for the template scope (the template input value) so it's easy to figure out which values are available. Moreover a few functions (lambdas) are available for common needs.

info

Handlebars is also compatible with Mustache templates so you can reuse them.

info

Tip: when writing templates you may find useful to serialize the state to a file for reference or troubleshooting.

Reference

Render a static output

At times you just need to output a simple hardcoded string instead of rendering even the simplest template. Don't even bother with {{ }} delimiters in this case and simply enter the value.

This means that wherever templates are allowed, you can still use static text, with no templates.

Let's assume you want to set the Hello World value for a configuration option, then your template is just the plain Hello World:

option = "Hello World"

Render a simple string

Often times you just need to use a simple value from the Nyx state to be the result of the template.

This is fairly simple as you just need to wrap the name of the state attribute with double curly braces, like {{attribute}}. This is the simplest form of a Handlebars expression (and yes, the double curly brances are the handlebars, or mustaches).

info

Please note that only simple attributes from the state can be rendered this way, while complex objects like changelog, configuration and releaseScope cannot be rendered as a whole. Instead, their children can be rendered as simple attributes.

So if we want to set our configuration option to the current value of the version attribute from the state our definition will look like:

option = "{{version}}"

When the value you need to render is nested into another (i.e. section/attribute or section.attribute, depending on the notation), the template looks like:

option = "{{#section}}{{attribute}}{{/section}}"

As you can see this is a little cumbersome for a simple value but you will soon understand how powerful it can be. What is important to note here is that the {{attribute}} expression is now enclosed between a {{#section}} opening tag and a {{/section}} closing tag (denoted by the # and / characters).

Alternatively you can simply use the dotted notation, like:

option = "{{section.attribute}}"

Type conversions

All templates return text values but ofter times the template output needs to be converted to other types. This may happen for configuration options, for example, when a dynamically computed value needs a boolean or a number. You can always tell if some conversion is to be performed by the type of the option.

This table gives you the overall rules used to convert text values to types other than strings:

Expected typeTranslation criteria
booleanIf the expression returns an empty or blank string translates to false, otherwise returns the boolean evaluation of the string value
numberTranslates to the number representation of the string when it contains a valid number, 0 in all other cases, including when the string does not contain a valid number. Different numeric types (i.e. integers and floats) require specific constraints to be met in order for the conversion to succeed, as per the standard number representation rules

Functions

Wherever templates are allowed you can also use functions to produce outputs or transform an input value. These functions are provided by lambdas and the syntax is like the one we've seen for nested values, like in this example:

option = "{{#upper}}{{ attribute }}{{/upper}}"

Here upper is a function accepting one parameter (attribute) and returning the same output, with upper case. Below you can find the list of available lambdas.

info

The underlying Handlebars template engine also provides an powerful set of built-in functions to handle conditionals, loops, lookups and log messages. Those functions (a.k.a. helpers) can be freely used in templates but they are not officially supported by Nyx. For more on those helpers see Built-in Helpers.

Functions can be nested for combined output. This example is valid and shows how to take the first 20 characters from the given {{ attribute }} and transform it to upper case:

option = "{{#upper}}{{#cutRight length="20"}}{{ attribute }}{{/cutRight}}{{/upper}}"

replace

Replaces all occurrences of a given character sequence with another character sequence in the input string. The from option defines the sequence to be replaced while the to option defines the sequence to use for replacement.

Example:

output = "{{#replace from="X" to="Y"}}{{input}}{{/replace}}"

Example inputs and corresponding outputs:

InputOptionsOutput
01234567890from="0"123456789
01234567890from="0" to=""123456789
01234567890from="0" to="X"X123456789X
01234567890from="45" to="_"0123_67890

lower

Transforms the input characters to lower case. Example:

output = "{{#lower}}{{input}}{{/lower}}"

Example inputs and corresponding outputs:

InputOutput
camelCasecamelcase
featurefeature
FEATUREfeature
feature/XX-12345feature/xx-12345

upper

Transforms the input characters to upper case. Example:

output = "{{#upper}}{{input}}{{/upper}}"

Example inputs and corresponding outputs:

InputOutput
camelCaseCAMELCASE
featureFEATURE
FEATUREFEATURE
feature/XX-12345FEATURE/XX-12345

trim

Removes the leading and trailing spaces from the input. Example:

output = "{{#trim}}{{input}}{{/trim}}"

Example inputs and corresponding outputs:

InputOutput
camelCaseCAMELCASE
featurefeature
FEATUREFEATURE
feature/XX-12345feature/XX-12345

first

Discards everything from the first occurrence of a character other than letters and positive digits. Example:

output = "{{#first}}{{input}}{{/first}}"

Example inputs and corresponding outputs:

InputOutput
featurefeature
1234512345
feature/XX-12345feature

firstLower

Discards everything from the first occurrence of a character other than letters and positive digits and transforms the remaining characters to lower case. Example:

output = "{{#firstLower}}{{input}}{{/firstLower}}"

Example inputs and corresponding outputs:

InputOutput
featurefeature
1234512345
feature/XX-12345feature
FEATURE/XX-12345feature

firstUpper

Discards everything from the first occurrence of a character other than letters and positive digits and transforms the remaining characters to upper case. Example:

output = "{{#firstUpper}}{{input}}{{/firstUpper}}"

Example inputs and corresponding outputs:

InputOutput
featureFEATURE
1234512345
feature/XX-12345FEATURE
FEATURE/XX-12345FEATURE

last

Discards everything before the last occurrence of a character other than letters and positive digits. Example:

output = "{{#last}}{{input}}{{/last}}"

Example inputs and corresponding outputs:

InputOutput
featurefeature
1234512345
feature/XX-1234512345

lastLower

Discards everything before the last occurrence of a character other than letters and positive digits and transforms the remaining characters to lower case. Example:

output = "{{#lastLower}}{{input}}{{/lastLower}}"

Example inputs and corresponding outputs:

InputOutput
featurefeature
FEATUREfeature
1234512345
feature/XX-1234512345

lastUpper

Discards everything before the last occurrence of a character other than letters and positive digits and transforms the remaining characters to upper case. Example:

output = "{{#lastUpper}}{{input}}{{/lastUpper}}"

Example inputs and corresponding outputs:

InputOutput
featureFEATURE
FEATUREFEATURE
1234512345
feature/XX-1234512345

sanitize

Removes all characters other than letters and positive digits from the input string, leaving all other characters untouched. Example:

output = "{{#sanitize}}{{input}}{{/sanitize}}"

Example inputs and corresponding outputs:

InputOutput
featurefeature
1234512345
feature/XX-12345featureXX12345

sanitizeLower

Removes all characters other than letters and positive digits from the input string, and transforms all others to lower case. Example:

output = "{{#sanitizeLower}}{{input}}{{/sanitizeLower}}"

Example inputs and corresponding outputs:

InputOutput
featurefeature
1234512345
feature/XX-12345featurexx12345

sanitizeUpper

Removes all characters other than letters and positive digits from the input string, and transforms all others to upper case. Example:

output = "{{#sanitizeUpper}}{{input}}{{/sanitizeUpper}}"

Example inputs and corresponding outputs:

InputOutput
featureFEATURE
1234512345
feature/XX-12345FEATUREXX12345

capture

Matches a regular expression against the input value and extracts a portion of it. The portion to be extracted must be a capturing group (identified by its index or named) defined in the regular expression. Remember that when using indexes, 0 is the group that returns the whole string.

The expression option defines the regular expression, while the group option can be either a positive integer (in which case the group will be extracted by its index) or a string (to extract the capturing group by its name).

info

Use of named capturing group is recommended in place of using groups by their index as it's consistent between the Java (Gradle) and Go (command line) implementations. Retrieving groups by index may lead to different outcomes as the two underlying frameworks use a different numbering for groups.

Example:

output = "{{#capture expression="(?<type>[a-zA-Z0-9_]+)(\((?<scope>[a-z ]+)\))?:( (?<title>.+))" group="type"}}{{input}}{{/capture}}"

Example inputs and corresponding outputs:

InputOptionsOutput
mytype(myscope): mytitleexpression="(?<type>[a-zA-Z0-9_]+)(\((?<scope>[a-z ]+)\))?:( (?<title>.+))" group="1"mytype
mytype(myscope): mytitleexpression="(?<type>[a-zA-Z0-9_]+)(\((?<scope>[a-z ]+)\))?:( (?<title>.+))" group="type"mytype
info

Use tools like regular expressions 101 to write and test your regular expressions.

cutLeft

Returns only the last N characters of the input, where N is an arbitrary positive integer represented by the length option. If the input is shorter than N characters it's returned untouched. This is often useful to shorten SHAs. Example:

output = "{{#cutLeft length="3"}}{{input}}{{/cutLeft}}"

Example inputs and corresponding outputs:

InputOptionsOutput
7b9da5286d4724dd7385bb80639a08841fa26606length=3606
7b9dalength=39da
7blength=37b

cutRight

Returns only the first N characters of the input, where N is an arbitrary positive integer represented by the length option. If the input is shorter than N characters it's returned untouched. This is often useful to shorten SHAs. Example:

output = "{{#cutRight length="3"}}{{input}}{{/cutRight}}"

Example inputs and corresponding outputs:

InputOptionsOutput
7b9da5286d4724dd7385bb80639a08841fa26606length=37b9
7b9dalength=37b9
7blength=37b

short5

Returns only the first 5 characters of the input. If the input is shorter than 5 characters it's returned untouched. This is often useful to shorten SHAs. Example:

output = "{{#short5}}{{input}}{{/short5}}"

Example inputs and corresponding outputs:

InputOutput
7b9da5286d4724dd7385bb80639a08841fa266067b9da
7b9da7b9da
7b7b

For arbitrary length strings see cutLeft and cutRight.

short6

Returns only the first 6 characters of the input. If the input is shorter than 5 characters it's returned untouched. This is often useful to shorten SHAs. Example:

output = "{{#short6}}{{input}}{{/short6}}"

Example inputs and corresponding outputs:

InputOutput
7b9da5286d4724dd7385bb80639a08841fa266067b9da5
7b9da57b9da5
7b7b

For arbitrary length strings see cutLeft and cutRight.

short7

Returns only the first 7 characters of the input. If the input is shorter than 5 characters it's returned untouched. This is often useful to shorten SHAs. Example:

output = "{{#short7}}{{input}}{{/short7}}"

Example inputs and corresponding outputs:

InputOutput
7b9da5286d4724dd7385bb80639a08841fa266067b9da52
7b9da527b9da52
7b7b

For arbitrary length strings see cutLeft and cutRight.

timeFormat

Returns a timestamp formatted according to the given format string.

The value for the timestamp is the current system time in milliseconds since January 1, 1970, 00:00:00 GMT unless a value is provided.

The returned string is the timestamp as an integer value unless the format option is passed with a valid format string.

warning

Since the underlying frameworks have different behaviors, the output from this function may not be consistent between the command line version (written in Go) and the Gradle version (written in Java). The format string passd in the format option also depends on the implementation. Please see here for the available patterns available in Java and here and here for those available in Go. Moreover, when invalid patterns are passed in the format option, the behavior is different between the two implementations: the Java implementation returns and empty string (and logs an error), while the Go implementation just returns the plain value passed in the format option. For consistent behavior functions see timestampISO8601 and timestampYYYYMMDDHHMMSS.

Example inputs and corresponding outputs:

output = "{{#timeFormat}}{{/timeFormat}}"
output = "{{#timeFormat}}{{timestamp}}{{/timeFormat}}"
output = "{{#timeFormat format="yyyyMMdd"}}{{/timeFormat}}"
output = "{{#timeFormat format="yyyyMMdd"}}{{timestamp}}{{/timeFormat}}"
output = "{{#timeFormat format="20060102"}}{{/timeFormat}}"
output = "{{#timeFormat format="20060102"}}{{timestamp}}{{/timeFormat}}"

Example inputs and corresponding outputs:

InputOptionsOutput
1577880000000
format=yyyyMMdd or format=yyyyMMdd20200101
15778800000001577880000000
1577880000000format=yyyyMMdd or format=yyyyMMdd20200101

timestampISO8601

Provided a timestamp in the unix format returns it formatted as ISO 8601 UTC. If the input is not a Unix timestamp returns an empty string. Example:

output = "{{#timestampISO8601}}{{timestamp}}{{/timestampISO8601}}"

Example inputs and corresponding outputs:

InputOutput
16082103962020-12-17T13:06:36

For arbitrary timestamp formats see timeFormat.

timestampYYYYMMDDHHMMSS

Provided a timestamp in the unix format returns it formatted as YYYYMMDDHHMMSS UTC. If the input is not a Unix timestamp returns an empty string. Example:

output = "{{#timestampYYYYMMDDHHMMSS}}{{timestamp}}{{/timestampYYYYMMDDHHMMSS}}"

Example inputs and corresponding outputs:

InputOutput
1608210396 20201217130636

For arbitrary timestamp formats see timeFormat.

environmentUser

Returns the current system user name. Example:

user = "{{environmentUser}}"

or, if you prefer the open/close tags:

user = "{{#environmentUser}}{{/environmentUser}}"

In case you pass a value to this function it is ignored.

environmentVariable

Returns the value of the environment variable used as parameter, if any. Example:

os = "{{#environmentVariable}}OS{{/environmentVariable}}"

returns the value of the OS environment variable, if present, or an empty string.

fileContent

Returns content of the given file, if it exists, or an empty string. Example:

filecontent = "{{#fileContent}}example.txt{{/fileContent}}"

returns returns the entire content of the example.txt file (if it exists).

The file name can be a relative or an absolute path. Please note that when a relative path is used, it's always resolved to the current working directory and other configured directories are ignored.

Be careful with the file content type, which must be text, and its size.

fileExists

Returns the string representation of a boolean, depending on whether the the given file exists. Example:

fileexists = "{{#fileExists}}example.txt{{/fileExists}}"

returns true if the example.txt file exists, false otherwise.

The file name can be a relative or an absolute path. Please note that when a relative path is used, it's always resolved to the current working directory and other configured directories are ignored.

Example

Here is a more complex example where we combine several state attributes to produce a multi-line text content. This example is only useful to show the use of templates and is not meant to be used anywhere.

Consider this template:

Version: {{version}} (bumping '{{bump}}' on {{configuration.initialVersion}} using lenient ({{configuration.releaseLenient}}))
Scheme: {{scheme}}
Timestamp: {{timestamp}}
OS: {{#environmentVariable}}OS{{/environmentVariable}}
User: {{environmentUser}}
Previous Version: {{releaseScope.previousVersion}} at {{#short5}}{{releaseScope.previousVersionCommit}}{{/short5}}

Commits:
{{#releaseScope.commits}}
{{.}}
{{/releaseScope.commits}}

When rendered, it yields to an output like:

Version: 9.8.6 (bumping 'theta' on 1.2.3 using lenient (true))
Scheme: SEMVER
Timestamp: 9223372036854775807
OS: Linux
Users: jdoe
Previous Version: 4.5.6 at 05cbf

Commits:
d40fcded9e516158a2901f5657794931528af106
9bed70fac8a27a4b14b6b12307d034bc59da85c3
ef6a6481adb2df26bc7eebfde465e5c2f3e93539

As you can see there are state attributes like version, bump, scheme and timestamp used in the template and their usage should already be clear.

But since the resolved configuration is also available as a nested state object, a couple of attributes are fetched from there, as you can see by configuration.initialVersion and configuration.releaseLenient.

Some values are retrieved from the current environment: the OS environment variable is fetched by the {{#environmentVariable}}OS{{/environmentVariable}} block (where the function parameter OS is the name of the variable to retrieve) and also the system user name is retrieved by {{{environmentUser}}.

Moreover, the release scope is also used to get the releaseScope.previousVersion and releaseScope.previousVersionCommit. By pay attention here: the previousVersionCommit is not used as is but only the shortened SHA-1 is used, thanks to the short5 function.

Finally, all the SHA-1 IDs of the release scope commits are printed, as you can see by the final block enclosed within #releaseScope.commits and /releaseScope.commits.