Magnanimous Gopher Magnanimous Documentation

2. Expression Language

Magnanimous defines a very simple expression language that is used to process documents in the source/processed directory.

This expression language is composed solely of instructions. Each instruction has the following form:

{{ instruction-name args }}

Where:

Only a single instruction may be present within double-braces (i.e. between {{ and }}).

Here’s a list of all Magnanimous instructions:

In this section, we’ll see details about every available instruction in Magnanimous, and how to use them.

Syntax:

{{ define <variable-name> <expression> }}

where:

The define instruction is used to define a variable that can later be used in one or more expressions.

For example, the following instruction defines a variable called text with the value Hello Magnanimous:

{{ define text "Hello Magnanimous" }}

After this declaration, any expression where the text variable is used will have its value, Hello Magnanimous, used upon evaluation.

For example, we could add the following definition after the one we’ve just shown above:

{{ define other_text text + ", this is great!" }}

Because text was previously defined as Hello Magnanimous, this definition results in other_text getting the value Hello Magnanimous, this is great!!

See Expressions for more details about the kind of expressions you can use.

Syntax:

{{ eval <expression> }}

where:

The eval instruction is similar to define, except that the result of the expression is inserted into the processed document instead of being set to a variable.

As an example, suppose you have the following md file:

## The dog is {{ eval "big" }}

After processing, this file would be turned into HTML and its instructions processed, resulting in the following content:

<h2>The dog is big</h2>

Of course, eval is mostly useful when combined with variables, slots and expressions.

For example, you could define a number of variables beforehand, to be used later in several other places (so you could change them in only one place if you ever changed your mind about their values):

{{ define title "OurWebsite" }}\
{{ define visitorsPerMonth 10000 }}\

The best website in the world, {{ eval title }}, has an average number of
{{ eval visitorsPerMonth / 30 }} visitors per day!

Many people love {{ eval title }} because it is so great!  

You may have noticed that instructions often end with a \ character. That’s to avoid a new-line character being inserted where the instructions were in the source file, as the \ can escape new lines.

Syntax:

{{ include <path> }}

where:

The include statement is used to include the contents of a file into another file.

Example:

<div id="other-file-contents">
{{ include path/to/other/file.md }}
</div>

When a markdown file includes a non-markdown file, the included contents are not treated as markdown. If you want that to hapen, you can define a variable called _forceMarkdown with a non-null value. For example, {{ define _forceMarkdown 1 }}.

Notice that paths can be expressions if starting with " or `, or explicitly with eval <expr>:

<!-- Include expression path -->
{{ include "other/" + fileName }}

<!-- Include evaluated path -->
{{ include eval fileName + "2" }}

Paths may be absolute, relative or up-paths (i.e. paths that refer to a file at the same directory, or a directory up the source tree from the current file being written).

Absolute path example: /processed/partials/_header.html

Relative path example: partials/_header.html

Up-path example: .../_header.html

See Paths and Links for more details.

Syntax:

{{ includeB64 <path> }}

where:

The includeB64 statement is used to include the base64-encoded contents of a file into another file.

Example:

<!-- include a gif file encoded as a data URL -->
<img src="data:image/gif;base64,{{ includeB64 path/to/file.gif }}">

See the include documentation for more information about paths that may be used.

Syntax:

{{ includeRaw <path> }}

where:

The includeRaw statement is used to include the raw (unprocessed) contents of a file into another file.

See the include documentation for more information about paths that may be used.

Syntax:

{{ component <path> }}
<content>
{{ end }}

where:

The component instruction is quite similar to include. It also includes the contents of another file, the component file (which is usually designed specifically for this purpose), into the file using it.

However, components are more powerful as they may also declare some content which can be placed anywhere inside the component (typically using slots).

Example:

A simple component that displays its contents inside a div element, with an optional custom class:

<div class="{{ eval cssClass || `component-example` }}">
{{ eval __contents__ }}
</div>

Including the above component in another file:

{{ component path/to/component.html }}
{{ define cssClass "large-text" }}
Include this in my component.
{{ end }}

See Components for more details about using components.

Syntax:

{{ slot <slot-name> }}
<content>
{{ end }}

where:

A slot instruction, like define, defines a variable and does not include any content at the location where it is declared. But unlike define, slot uses the body of its declaration as it value.

That means that evaluating the variable defined by slot with eval results in the body of the slot being inserted into the processed document.

Slots are commonly used together with Components (but may also be used on their own), so they are explained in more detail in the Components page.

Syntax:

{{ if <expression> }}
<content>
{{ end }}

where:

The if instruction can be used to include some content in a document only if some condition is true.

For example, you may want to include a certain CSS class on an element only if it’s the currently active element:

<div class="{{ if currentPage == page }}active{{ end }}"></div>

Syntax:

{{ for <variable-name> [ (<for-instruction>...) ] <iterable> }}
<content>
{{ end }}

where:

The for instruction allows some content to be repeated for each item of an iterable.

For example, you could use an array to iterate over some values, including the same content for each item:

{{ for item ["Home", "About", "Documentation"] }}
<div>{{ eval item }}</div>
{{ end }}

More commonly, the for instruction is used to show properties of files in a certain directory.

Example:

{{ for item (sortBy date reverse limit 10) /path/to/directory }}
<div>Date: {{ eval item.date }}</div>
<div>Post name: {{ eval item.name }}</div>
{{ end }}

Notice that the path is not normally given as an expression, but as simple text (notice that the path is not wrapped into double-quotes). If you need to pass in an expression, or just a variable instead of a hardcoded path, you must call eval first:

{{ define postDirectories "/path/to/directory" }}

<!-- Somewhere else in the file -->
{{ for item eval postDirectories }}
<div>Date: {{ eval item.date }}</div>
<div>Post name: {{ eval item.name }}</div>
{{ end }}

See Iterables for details about what iterable types can be used with the for instruction.

Syntax:

{{ doc <text> }}

where:

The doc instruction is used to document your templates and does not produce any visible output in generated documents.

You can use it to make clear what some complex parts of your templates work, or document the variables expected to be set for a Component, for example.

Syntax:

{{ end }}

end is not an independent instruction; it is used to end the scope of a previously started scoped instruction.

It does not accept any argument.

All scoped instructions must always be finalized with an {{ end }}.

Scoped instructions are:

Each end instruction always matches the nearest unclosed scoped instruction.


Magnanimous expressions use a syntax that’s similar to C-like languages, including Java, JavaScript and Go.

For example, the following are all valid expressions:

2
2 + 2.42
(5 * 2) - 10
variable == true
!negated == false
something + other != "hello" + yet_another
thing == null
`multiline
string`
[1, 2, 3, 4]
path["path/to/some/file.html"]
date["2019-03-20"]
date["now"]
date[some_path]
date["2019-03-20T08:24:00"]["Mon Jan 2 15:04:05 2006"]

The above expressions include all types of variables available:

They also show the use of variables, such as variable and negated above, which must be declared via the define instruction.

The names of the variables must be valid identifiers, as we’ll see below.

Identifiers are used to name variables declared with the define instruction.

They must start with a letter or _, but may contain any of the following characters:

Examples of valid identifiers:

hello
_first_character
abs123
CONSTANT_1

As shown above, expressions may comprise identifiers, numbers, Strings and also operators.

The following operators are supported:

Arithmetic operators

Comparison operators

Boolean operators

The || (OR) operator can be used to declare default values for variables that may be missing a value.

For example, in the following eval declaration, the document should get the value of the example_var if it’s been defined, or the default value, Not here, otherwise.

{{ eval example_var || "Not here" }}

Dates

Dates are represented in the following full format (some parts are optional, as we’ll see):

date["2018-03-20T22:55:00"]["Mon Jan 2 15:04:05 2006"]

The String between the first brackets is the actual date value, 20th of March 2018, 10:55:00AM in this example, and the second is the optional layout, which uses the fixed date-time 02nd of January 2006, 03:04:05PM as a layout-by-example (this is copied from the Go Time Formatter).

If not given, the following layout is used:

02 Jan 2006, 03:04 PM

The first String can also be "now" to evaluate the current time (when Magnanimous is running):

date["now"]

When a path value is given to date, it resolves to the time the relevant file was last updated.

{{ define p1 path["path/to/file.txt"] }}
File path/to/file.txt was last updated on {{ eval date[p1] }}

To make things clearer, here are a few examples:

Date expression Evaluated result
date["2018-03-20T22:55:00"]["Mon Jan 2 15:04:05 2006"] Tue Mar 20 22:55:00 2018
date["2018-03-20T22:55"]["2 Jan 2006 03:04:05PM"] 20 Mar 2018 10:55:00PM
date["2018-03-20"]["2 Jan 2006"] 20 Mar 2018
date["2018-03-20"] 20 Mar 2018, 12:00 AM
date["2018-03-20T22:55"] 20 Mar 2018, 10:55 PM
date["now"]["2016"] 2019 (current year)

Iterables are collections of values which can be used with the for instruction.

They can be of two types:

Arrays

Arrays have the following form:

[ expr1, expr2 ... ]

Examples:

{{ for number [1, 2, 3, 4, 5] }}{{ end }}
{{ for word [ "ABC" ] }}{{ end }}
{{ for section [ "Home", "About", "Docs" ] }}{{ end }}

To iterate over an array defined elsewhere, the variable must be eval’d:

{{ define array [1,2,3] }}
{{ for item eval array }}
    {{ eval item }}
{{ end }}

Paths to directories

A for expression may iterate over each file of a directory by declaring a path to a directory as its iterable.

For example, suppose there is a directory with the following contents:

my-sections/
    /something.md
    /other.md

If each file defines a variable named section_name, for example, then a summary file could contain the following for instruction to display the section_name of each file:

{{ for section my-sections/ }}\
  The name of the section is {{ eval section.section_name }}!
{{ end }}\

See Paths and Links for more details about paths.