Skip to content

Placeholders

Peter Johnson edited this page Feb 15, 2023 · 25 revisions

Placeholders

⚠️⚠️ THIS WIKI IS BEING USED TO JOT DOWN IDEAS AT PRESENT. DON'T ASSUME ANYTHING IS FIXED. ⚠️⚠️

Introduction

Placeholders are written using a mini language called Treacle.[1] They are delimited by pairs of opening and closing curly braces, i.e. {{ and }}. The content between the curly braces determines what text replaces the placeholder. Placeholders may span more than one line.

Placeholders may call other placeholders recursively. Each time a placeholder is evaluated it is replaced by the resulting value.

The content of a placeholder is made up of several components:

  • Source - This is the only required component.
  • Filters
  • Iterators
  • Assignment

All placeholders return a value.

There is also a special comment placeholder, delimited by triplets of opening and closing curly braces: i.e. {{{ ... }}}.

All the above are described in the following sections. There is also an appendix that formally defines the placeholder syntax in a BNF-like notation.

Source

Every placeholder has exactly one source. The simplest placeholder has only got a source and the value of the source is returned from the placeholder. Here's an example of a trivial placeholder:

{{ @now }}

This placeholder simply returns the date and time that the current execution of rpt was started.

  1. A user-defined variable
  2. A predefined command
  3. A literal value
  4. A nested placeholder
  5. A predefined special variable

Each of the above entities are described in a sub-section below. However please note that some of them can also be used elsewhere in a placeholder.

User-defined variables

A user-defined variable has a name, a type and stores data. It is represented by an identifier that starts with a letter and is followed by zero or more letter, digit, _ or - characters. The variable's value is set at run time in one of four ways:

  1. From an environment variable.

    An environment variable of the form RPTVAR_foo creates a variable named foo whose value is the environment variable's value. The type of the variable is determined as follows:

    • Null if the environment variable is not set.
    • Int if the environment variable can be interpreted as an integer.
    • Float if the environment variable can be interpreted as a floating point value.
    • Text otherwise.
  2. From a variable definition file.

    A variable definition file has entries of the form foo=bar. Such an entry creates a variable foo with value bar. The type of the variable is determined as follows:

    • Null if the definition has form foo=
    • Text if the value part is quoted text, e.g. foo="bar" and bob='alice'
    • Int if the value part can be interpreted as an integer, e.g. foo=42
    • Float if the value part can be interpreted as a floating point value, e.g. alice=56.789 and bob=42.0
    • Array is the value part is a comma separated list of values, enclosed in square brackets, e.g. alice=[12,"foo",42.6,["nested", "array"]] or bob=[] (empty array).
    • Text otherwise.
  3. From the command line.

    Each value is defined by creating a parameter of the form foo=bar where the name and value is determined using the same rules and for variable definition files.

  4. From an assignment statement with a placeholder

    This is done using the := operator, e.g. {{ alice := 42; }}. See below for details.

Variables are defined in the order given, with any later definition overriding an earlier definition for variables of the same name.

The value of the variable is used as the placeholder source.

If a variable is used that has not been defined it has a Null value.

Predefined commands

A command is an identifier preceded by an @ character. The identifier has the same format as a variable name (see above). A command's value is obtained by calling a predefined internal function of rpt.

Some commands are used on their own, while others take parameters (see below).

The return value of the command is used as the placeholder source.

Literal values

The source can be specified as a literal value.

🗒️ This type of source is pretty useless on its own since the placeholder just returns the literal value, and so has no advantage over just including the literal value directly and not bothering with the placeholder. Literal values are only of any use when combined with filters (see below).

There are two types of literal value: text and numeric.

🗒️ Behind the scenes, literal value sources are converted into objects of the related data type. For a data type T, and a literal value L, the literal value is an alias for {{ @construct < T,L }}

Text literals

Literal text is plain text enclosed in double or single quotes. If the literal text contains the quote character it must be preceded by a backslash \ character (\' or \"). Similarly to enter a backslash it must be doubled (\\).

String literals can not be split across lines. It is a syntax error not to close a string literal before the end of a line. To enclose a line feed or carriage return within a string literal use the C style \n for a line feed (ASCII 10) and \r for a carriage return (ASCII 13). Tabs can also be entered using \t.

The value of a text literal is used as the placeholder source after replacing any escape characters with their associated characters.

Numeric literals

Numeric literals are decimal numbers that may be integers, such as 42, or real (floating point) numbers such as -42.56. To force a whole number to be treated as a real number, include a decimal point followed by a zero, so 42 is an integer while 42.0 is a real number.

Real numbers may also be entered in scientific format, for example 0.56e-42 and 10E6. Either E or e can be used to prefix the exponent.

The value of a numeric literal is used unchanged as the placeholder source.

Nested placeholders

The source of one placeholder can come from the output of another placeholder. The use cases for this are arcane, and unless filters are used, pointless.

Predefined special variables

These variables have names that begin with % and are followed by another punctuation character. Most are designed for use in placeholders nested within iterators (see below). Other than %%, which is also used as the source of a placeholder that is being used as a filter, these special variables have no use in placeholders used in any other context and have value Null.

The variables, and the contexts in which they are defined, are:

  • %% - the current element in an iteration, or the input to a placeholder used as a filter.
  • %@ - the currently accumulated value in iterators that reduce the dimension of arrays.
  • %_ - the whole of the array or text string being enumerated.
  • %# - the index of the current element in the array or text string being enumerated.

Examples of source-only placeholders

Here are five examples of simple placeholders:

  1. {{ my-variable }} - replaced by the value of my-variable
  2. {{ @cmd }} - replaced by the output the @cmd command
  3. {{ 42 }} - replaced by the value 42
  4. {{ "pointless" }} - replaced by the text pointless
  5. {{ {{ 56.7 }} }} - replaced by the floating point value 56.7
  6. {{ %% }} - replaced by Null since outside the contexts for which it is designed.

Filters

A source value can be manipulated by passing the value through one or more filters. Filters are predefined functions that can be chained together. They are delimited by > characters. Data is piped from left to right through the filters. Some filters take one or more parameters (see below).

Here are some example placeholders that use simple filters:

  1. {{ my-variable > foo }} - The value of my-variable is passed through filter foo to get the text that replaces the placeholder.
  2. {{ @cmd > foo > bar }} - The value of command @cmd is first passed through filter foo then its output is passed through filter bar that outputs the value used to replace the placeholder.
  3. {{ "plain text" > foo }} - The text plain text is piped into filter foo which processes the text and returns the result.

🗒️ You may use | in place of > to make the placeholder look more like Liquid.

Parameters

Parameters send their data to the preceding command or filter like a reversed pipe. The "parameter pipe" is denoted by a < character. Data flows from right to left through the parameter pipe. There may be more than one parameter, in which case they are separated by commas.

Here are some placeholders that use parameters:

  1. {{ @cmd > foo < 'some text', 42 > bar }} - The output of @cmd is piped to the foo filter, which accepts two parameters, the text some text and the integer 42. The output of foo is then piped to bar which finally returns the replacement text.
  2. {{ @cmd > foo < {{ param-variable }}, {{ @param-cmd }} }} - Here we have an example that further illustrates rpt's recursive nature. Filter foo takes two parameters, both of which have their values given by placeholders, param-variable and @param-cmd respectively.
  3. {{ @cmd < 'some text', 42 > foo }} - Here @cmd itself takes two parameters then pipes the resulting output to filter foo.

🗒️ You may use : in place of < to make the placeholder look more like Liquid.

Assignment

Variables can be given values within placeholders using the := assignment operator. If the variable already exists then its value is overwritten. If the variable does not exist then it is created with the given value. The variable is set to the return value of the last filter, or the source if there are no filters.

Note that, by default, the placeholder returns its value as normal. However, this behaviour is often inconvenient when simply assigning a variable whose value is to be used later. See below for details of how to use the ; operator to suppress placeholder output.

Here are some example assignment placeholders:

  1. {{ foo := 42 }} - variable foo is given the integer value 42. The placeholder returns 42.
  2. {{ foo := 56 ; }} - variable foo is given the integer value 56. The placeholder returns Null.
  3. {{ bar := "alice" > upcase }} - variable bar is assigned value ALICE. First "alice" is piped through the upcase filter (which outputs ALICE) and then ALICE is assigned to bar and then returned from the placeholder.
  4. {{ bar := "bob" > upcase ; }} - similar to the above example variable bar is assigned value BOB, which is the output of the filter. The placeholder returns Null.

Return value & type

By default each placeholder returns a value. The value is the output of the last filter in the placeholder, or if there are no filters, the output of the source. However the placeholder can be forced to return a null value by inserting a ; character before the placeholder's closing }}.

The data type of the returned value is that of the last filter's output, the source output if there are no filters or null if the output suppression operator (;) has been used.

When a placeholder is resolved recursively then the returned data type is used. When a placeholder is called directly from within a document then the data is converted to text using the default text conversion method for the data type. Note that a Null output is rendered as an empty string. For details of other default text conversions see Data Types.

In the special case where where a placeholder returns null, and it is placed on a line on its own (except for white space), then the whole line will be stripped. For example:

Lorem
{{ "" ; }}
  {{ @cmd ; }}
Ipsum

Renders as:

Lorem
Ipsum

Comments

There is one special placeholder for adding comments to a text file that will be stripped from the rendered version. Comments take the form:

{{{ comment text here }}}

Comments can span lines like this:

{{{ multi
Line comment
}}}

Everything between the opening { and closing }, inclusive is removed from output (strictly speaking the placeholder returns Null). Note that any whitespace surrounding the comment is retained. This means that text like this:

Lorem ipsum {{{ profound comment }}} sit amet.

Will be rendered with two spaces between "sit" and "amet".

Note that, because the comment placeholder returns Null, the special case discussed above applies when the placeholder is placed on a line (or lines) on its own: i.e the whole line is deleted. For example:

Lorem
{{{ comment 1 }}}
Ipsum
  {{{ comment 
  number
2 }}}
Delorum

Renders as

Lorem
Ipsum
Delorum

Examples

Here are a few examples how source text is transformed after running rpt.

Example #1

Lorem ipsum {{ foo }} sit amet.

This is the simplest possible example, containing just one placeholder. If foo = "delorum" then the resulting text is:

Lorem ipsum delorum sit amet.

Example #2

The file name is {{ file }} with extension .{{ ext }}.

We're going to use recursive placeholders here.t If we have variables:

  • file = "foo.{{ ext }}"
  • ext = "bar"

Then file will evaluate to "foo.bar" the result will be:

The file name is foo.bar with extension .bar.

Example #3

Lorem ipsum dolor {{ foo > to-upper }}.
{{ bar > append: " " > append: {{ extra }} }}.
  -- copyright (c) {{ @now > month-name > to-lower }} {{ @now > year }}.

This shows filters in use, one of which has recursive parameters. Assume that the following variables are defined:

  • foo = "sit amet"
  • bar = "Donec tristique pharetra"
  • extra = "odio"

Also assume that @now returns the date and time that rpt was executed - say it's some time on 1st January 2023. Finally assume that the following filters are provided:

  • to-upper - converts the input string data to upper case.
  • to-lower - converts the input string data to lower case.
  • append - appends the string given by the parameter to the input string data.
  • month-name - extracts the full month name from the input date.
  • year - extracts the 4 digit year from the input date.

In the first line the value of foo is converted to upper case. In the second line a space is appended to the value of bar and then the value of extra is appended to the result - admittedly there's a simpler way to do this! In the third line the month name in lower case and the year are inserted. Here's the result:

Lorem ipsum dolor SIT AMET.
Donec tristique pharetra odio.
  -- copyright (c) january 2023.

Example #4

Here's a simple example that illustrates the use of plain text as placeholder source. For a change we use the | character instead of > as the filter delimiter:

lower {{ "upper" | to-upper }} lower

The text in the placeholder is simply converted to upper case resulting in this output:

lower UPPER lower

Example #5

Given that the character sequences {{ and {{{ are interpreted by rpt as placeholders, how do you include those sequences in your output unchanged? The following input does the job:

This is a simple placeholder: {{ "{{" }} @command > filter }}.

This is rendered as:

This is a simple placeholder: {{ @command > filter }}.

This works because {{ "{{" }} takes the text literal "{{" as its source and returns it from the placeholder as {{, which replaces the placeholder. The rest of the line after the placeholder is output literally. Note that the }} sequence is output literally because it is not matched with an opening {{.

The comment placeholder opener, {{{, is dealt with similarly using the placeholder {{ "{{{" }}.

Example #6

Say you want to include a blank line in your text if and only if the variable want-blank is truthy. Here's a way to use the fact that placeholders on a line of their own delete the line when Null is returned:

Lorem
{{ @if < want-blank, "", @null }}
Ipsum

If want-blank is truthy then the placeholder returns an empty string, which means the placeholder is replaced by nothing and the following new line is written, resulting in a blank line. But, when want-blank is falsey, the placeholder returns null, causing the placeholder and the line containing it to be deleted.

In fact, because @if returns null by default if its third parameter is omitted, the code above can be shortened as follows (although the intent of the code is now less clear):

Lorem
{{ @if < want-blank, "" }}
Ipsum

Footnotes

[1] Treacle placeholders are influenced by Liquid filters. Treacle can be thought of as a less intelligent (or stupid) version of Liquid because it lacks Liquid's flow of control operations. In British slang the word thick is used to mean stupid. And treacle is a very thick liquid 😄.