Arcana

Static-file templating engine.

View on Github


Installation

The arcana compiler can be downloaded with git and installed with cargo.

# download a copy of the repository
git clone "https://github.com/frankiebaffa/arcana"
cd arcana
# install the arcc compiler
cargo install --path compiler
arcc -h

Glossary

alias: A reference to a value within the current context (i.e. value.is.here).

chain: A hyphen (-) character following the closure of tags such as if, for, and their respective else. Tells the parser to ignore whitespace until the next block opening.

content: Characters included in the output of the parser.

context: A map of values.

pathlike: A literal path (i.e. "path/to/file.txt") or an alias to a path in the current context (i.e. context.path.to.file).

sealed: A context scope whose modifications will not propagate to a higher level.

stringlike: A string, number, or boolean in the current context.

Tags

Expression tags control the flow of the document and typically conform to the following pattern:

<EXP>{\s*<PATHORALIAS>\s*[|<MOD>[\s*<MODARGS>]*]*\s*}

Comment

#{This is a comment.}#

Comments are ignored by the parser. If a comment is followed by a trailing line-break, it will also be ignored. Comments are closed with a special endblock so they can span multiple lines and contain the templating syntax without prematurely closing.

#{ Uncomment in production
${alias}
}#

Ignore

!{This file is ignored.}!

Disregards anything following the tag. The inner content of this block is ignored, but is useful for placing the reason why the file is ignored. Ignores are closed with a special endblock so they can span multiple lines and contain the templating syntax without prematurely closing.

!{ File is not yet ready.
@{file in files}{
    &{file.path}{
        ={$root}<{file.content}
    }
}
}!

Extend-Template

+{pathlike}

A template to parse using the final context of the current file. The output content of the current file will be set to the special alias $content.

Source-File

.{pathlike}

A context file to include in the current context. Matching values will be overwritten.

Modifiers

As
.{pathlike|as obj}

The as modifier can be used to specify an alias at which to place the values sourced from the specified context file.

Consider the context file context.json:

{
    "name": "Jane Doe",
    "age": 42
}

And the template file template.arcana:

.{"context.json"|as person}
${person.name}: ${person.age}

When compiled, the template file would yield the result:

Jane Doe: 42

Include-File

&{pathlike}

A file to parse and include in the position of the tag. Any changes to the context while parsing the file are sealed.

An optional block can be included which will allow for modifications to the sealed context prior to parsing the given file. Any output of this block will also be included in the sealed context with the special $content alias.

&{pathlike}{
    ={alias-1}{This is a property.}
    ={alias-2}{This is another.}
}

Modifiers

Raw
&{pathlike|raw}

Does not parse the content, only includes directly. Can be used in conjunction with the markdown modifier.

Markdown
&{pathlike|md}

Parses the output of the include as No-Flavor Markdown. Can be used in conjunction with the raw modifier.

If

%{alias}-
{${alias}}-
{Alias does not exist.}

Evaluates the output of the condition as true or false. If true, the first trailing block is parsed. Else, the second trailing block is parsed. The default condition of the if tag is exists. The initial if tag as well as the true condition tag can be optionally trailed by a chain as shown above. The condition can be preceeded by the not operator (!) to negate the evaluated condition.

Exists

%{alias exists}{${alias}}{Alias does not exist.}

Evaluates to true if the given alias exists in the current context.

Empty

%{!alias empty}{${alias}}{Alias was empty.}

Evaluates to true if the given alias has an empty value in the current context.

For-Each-Item

@{item in alias}{
    ${item.name}
}{
    No items.
}

Loop through contents of an array in context. The inner context of the loop is sealed. The first trailing block will be parsed for every item found within the array. If there are no items, the second block will be parsed. The for-each-item tag can be trailed by a chain.

Loop Context

For loops initialize a special alias into the sealed context named $loop. This object's values are mapped to the following aliases.

$loop.index: The 0-indexed position of the current iteration.

$loop.position: The 1-indexed position of the current iteration.

$loop.length: The length of the array being iterated over.

$loop.max: The maximum index of the array begin iterated over.

$loop.first: Set iff the current value of $loop.index is 0.

$loop.last: Set iff the current value of $loop.index is $loop.max.

Modifiers

Paths

Treat the values found within the array as paths to files.

@{dir in alias|paths}{
    *{file in dir|ext "json"}{
        .{file|as subobj}
        ${subobj.description}
    }{
        No files in directory.
    }
}{
    No directories in alias.
}
Reverse

Reverse the order of the array.

@{item in alias|reverse}-
{%{ !$loop.first }{
}${ item }}-
{No items.}

For-Each-File

*{file in pathlike}{
    &{item}
}{
    No files in directory.
}

Loop through the files found within a given directory. The inner context of the loop is sealed. The trailing blocks function identically to the for-each-item tag.

Loop Context

The same loop context is set for this tag as for for-each-item.

Modifiers

With-Extension
*{file in pathlike|ext "arcana"}{
    &{file}{
        ={$loop}<{loop}
    }
}

Only include files with the matching extension.

Reverse
*{file in pathlike|ext "arcana"|reverse}{
    &{file}
}{
    No files found in directory.
}

Reverse the order of the files.

Include-Content

${alias.to.stringlike}

Includes the stringlike value of the alias from the current context in the content.

Modifiers

Lower
${alias|lower}

Changes the content to lowercase.

Replace
${alias|replace "x" "y"}

Replaces instances of x with y.

Upper
${alias|upper}

Changes the content to uppercase.

Path
${ alias | path }

Handles the content as a path.

Filename
${ alias | path | filename }

Outputs only the filename of the path.

Trim
${alias|trim}

Removes whitespace from the start and end of the content.

Split
${alias|split 2 0}

Splits the content into 2 parts and uses the part at index 0.

Consider the context file context.json:

{
    "name": "Jane Doe"
}

And the template file template.arcana:

.{"context.json"}
${name|split 2 1}

The output of the parser would be:

 Doe

Set-Item

={alias}{Here is the value}

Sets the value at alias within the current context to the content of the block. This can also be used with other items in the context.

={alias}{${item.name}}

Modifiers

Array

Initializes an array at alias if it is not an already an array and pushes the given value into it.

={alias|array}{First item}

This modifier can also be chained together using chains or inline.

={alias|array}{First item}{Second item}-
{Third item}{Fourth item}
Path

Sets alias to a pathlike built from the output of the block.

={alias|path}{path/to/file.txt}

Siphon-Item

={alias1}<{alias2}

Sets alias1 to the literal json value found at alias2. This is useful when an entire object needs to be set at a different alias while including another template. The special $root alias can be used in the first position iff alias2 is a map and all of the keys are wished to be moved to the root of the current context.

The following example utilizes the siphon-item tag as well as the $root alias. If the file album.arcana is used both from another template as well as within a standalone template, it may only reference the properties within album at their root alias, i.e. name, instead of accessing them from a higher scoped alias such as album.name.

@{album in artist.albums}{
    &{"album.arcana"}{
        ={$root}<{album}
    }
}

Unset Item

/{alias}

Unsets the value at alias.

Modifiers

Pop

Pops the last value off of the array.

/{alias|pop}