A templating engine failure in every sense of the word.
Warning: This project has been flagged as absolute trash by it's creator. While he does have the power to destroy it, he wishes to show mercy and let it hang out as a reminder that some ideas are bad and just don't pan out.
Superceeded ideologically by arcana.
Use Very Good Templating Engine for all of your templating needs. Or do not. I am not a beggar.
- Ron Swanson, Parks and Recreation
Installation
From Source
To install from source, you must have git
and cargo
installed.
git clone "https://github.com/frankiebaffa/very-good" NAME
cd NAME
cargo install --path vgc
cargo install --path vgd
Crates
vg-core
The core functionality of the templating engine.
vgc
The command-line compiler.
vgd
A command line deployment program driven by a configuration file. Useful for moving/compiling files in bulk to a distribution/deployment directory.
Documentation
Templates
A vg
template is just a text file. A template can contain variables
(ex: {{ text }}
), tags (ex: {% block body %}{% endblock body %}
),
comments (ex: {# a comment #}
), and content. Variables are
placeholders waiting to be replaced by the output of tags. Tags can define
content and control the flow of a document. Comments are completely ignored.
Content is everything else that is not recognized by the parser.
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% if title %}{{ title }}{% else %}Home{% endif %}</title>
</head>
<body>
<ul>
{% for section in "/objects/sections" -%}
<li><a href="{{ section.localurl }}">{{ section.name }}</a></li>
{% else %}
<li>No sections have been added... yet.</li>
{%- endfor %}
</ul>
{% if title %}<h1>{{ title }}</h1>{% endif %}
{% if body %}{{ body }}{% endif %}
</body>
</html>
The above example shows an extendable template. It has variables which could be defined by tags within a child-template, but it will also compile to viewable output on its own.
If it were compiled directory using vgc
, assuming that nothing exists within
the /objects/sections
directory yet, the following would be the output:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Home</title>
</head>
<body>
<ul>
<li>No sections have been added... yet.</li>
</ul>
</body>
</html>
A child-template could extend the above template to define some of the variables
such as {{ title }}
and {{ body }}
:
{% extends "/objects/template.jinja" %}
{% block title %}Child{% endblock %}
{% block body -%}
<p>Here is a paragraph defined by the child-template!</p>
{%- endblock %}
If this child-template were compiled using vgc
, still assuming that nothing
exists within the /objects/sections
directory, the following would be the
output:
<html lang="en">
<head>
<title>Child</title>
</head>
<body>
<ul>
<li>No sections have been added... yet.</li>
</ul>
<h1>Child</h1>
<p>Here is a paragraph defined by the child-template!</p>
</body>
</html>
Tags
Defined as {% keyword [...] %}
.
Ignore
{% ignore %}
Instructs the parser to ignore the file entirely. If used on a page it will
return an error. vgd
will handle this error peacefully and
gives the optional functionality of deleting the destination file when the
source is ignored. If found in an included file or a for-loop item, the file
will be ignored.
Constraints
- Must be the first definition of the file, else will be handled as content.
- All subsequent
ignore
tags will be handled as content, which shouldn't be an issue given the file is ignored.
- All subsequent
Extends
{% extends "<PATH>" %}
Sets the implementing template as a child of the template specified in the
PATH
value. The implementing template's block
tags will be used to set the
values for variables found within the PATH
template.
Constraints
- Must be the first definition of the file.
- Only 1 can be defined per file.
- All subsequent
extends
tags will be handled as content.
- All subsequent
Include
{% include "<PATH>" %}
Includes the file content of PATH
. This inclusion occurs in during the parsing
pass on the implementing template, so included tags will be honored.
Consider the file /section.jinja
:
{# /section.jinja #}
{% block text %}
Here is some text.
{% endblock %}
As well as the file /page.jinja
:
{# /page.jinja #}
{% include "/section.jinja" %}
{{ text }}
Compiling /page.jinja
would result in the following:
Here is some text.
This is because the include
tag reads the content of the target file into the
vg parser, so blocks, loops, etc. are honored.
Include As
{% include "<PATH>" as item %}
The as
keyword allows scoping the templating items found in the included
template. For example: if the template found at <PATH>
contained a block named
name
, it will now be effectively named item.name
.
Include Raw
{% include raw "<PATH>" %}
The raw
keyword reads the file directly with no parsing. This can be used
when a file contains very-good
syntax.
Include Md
{% include md "<PATH>" %}
The md
keyword reads the file directly into the
no-flavor markdown parser and
outputs the result directly with no parsing. This is used to generate these
docs. If the processing of very-good
tags is needed, consider including within
a block and displaying using the markdown filter on the
variable.
Block/Endblock
{% block text %}Here is some text{% endblock text %}
Defines content used to set variables in parent templates or in the implementing
template occuring after the block
tag. The trailing name in the endblock
tag
is optional and matching the opening tag is completely ignored.
If/Else/Endif
Exists
{% if NAME %}{{ NAME }}{% else %}Default thing.{% endif %}
Checks for the existence of an implementation of NAME
and uses the contents
between the if
and else
tags if the implementation exists. Otherwise, it
will use the contents between the else
and endif
tags.
{% if !NAME %}Default thing.{% else %}{{ NAME }}{% endif %}
The inverse of the aforementioned example. Checks for the absence of an
implementation of NAME
and uses the contents between if
and else
if the
implementation does not exist. Otherwise, it will use the contents between the
else
and endif
tags.
Empty/Not Empty
{% if NAME not empty %}{{ NAME }}{% else %}Default name.{% endif %}
Checks for not only the existence of an implementation of NAME
, but also
verifies that the value of the implementation is not empty.
{% if NAME empty %}Default name.{% else %}{{ NAME }}{% endif %}
The inverse of the aforementioned example.
For/Else/Endfor
{% for item in "<PATH-TO-DIR>" %}
{% if item.text %}
<p>{{ item.text }}</p>
{% else %}
<p>{{ item }}</p>
{% endif %}
{% else %}
<p>No items.</p>
{% endfor %}
{% for item in "<PATH-TO-FILE>" %}
{% if item.text %}
<p>{{ item.text }}</p>
{% else %}
<p>{{ item }}</p>
{% endif %}
{% else %}
<p>No items.</p>
{% endfor %}
Clones the inner content and implements for the specified file or for each file in the specified directory. If the file(s) found extend another template, then the file will be completely compiled before handling the inner content.
Variables
Defined as {{ NAME }}
. Variables expect to be implemented by tags. The
output of the variable's implementation can be augmented with
filters. If a variable is not wrapped in an if
block
checking for it's existence, then its definition will be included in the output
when it is not implemented.
Nullability
Variables can be defined as nullable by succeeding the name with a
question-mark. This is effectively syntactic-sugar for wrapping the variable in
an if block with an exists condition. Consider the following file at
/page.jinja
:
{{ text? }}
If this file was compiled, its output would be blank.
Filters
Filters modify the content implementing the variable. They can be triggered by using a pipe after a variable name.
{% block text %}
Here is some text.
And here is some more.
{% endblock %}
{{ text | detab | flatten | trim }}
This example would compile to the following.
Here is some text. And here is some more.
Flatten
Replaces all newlines with spaces.
Detab
Removes all tabs.
Trim
Trims the start and end of the content.
Upper
Makes the content uppercase.
Lower
Makes the content lowercase.
Replace
{{ item | replace " " "_" }}
Replaces the first quoted item with the second.
Md
{{ item | md }}
Parses the item from No-Flavor markdown to html.
Comments
Defined as {# CONTENT #}
. Comments are ignored after the initial parsing
pass.
Tricks
Meta Paths
Consider the following template at /objects/template.jinja
.
{% for item in "{{ item-directory }}" %}
{{ item }}
{% endfor %}
The variable found within the PATH
portion of the for-tag definition can
be templated in a single pass of the compiler to allow for dynamic extends,
loops, and inclusions.