Onionspray Templates¶
The Onionspray (and EOTK) template engine is an enormous (but reasonably clean) kludge for automating repetitive tasks in template-building; it started off as a small Perl hack (because existing templaters really sucked, or required YAML or other such nonsense) and then grew to add functionality in a mostly-considered, mostly-orthogonal way.
For further examples, see also the unit tests in
lib/test-expand-template.sh
.
Variables¶
The templater reads/uses variables from three places:
- The Unix Process Environment (lowest priority).
- Space-separated columnar input from STDIN.
- Local "Range" or builtin variables (highest priority).
Attempts to interpolate a nonexistent (rather than empty) variable anywhere are a fatal error.
Environment¶
A simple example of the first case:
$ echo $USER $SHELL TERM
alecm /bin/bash TERM
$ cat > foo.template
Hello %USER% using %SHELL% in %TERM%
$ ./lib/expand-template.pl foo.template </dev/null
Hello alecm using /bin/bash in xterm
Columnar¶
The second (columnar) kind of data is read from stdin (hence the </dev/null
in the previous example; in a manner akin to a spreadsheet, the templater reads
a line containing a list of space-separated column-names, and then further
lines containing space-separated column values.
Columnar variables are then applied between %%BEGIN
and %%END
blocks:
$ cat foo.template
We learned that:
%%BEGIN
- %NAME% (age %AGE%) likes %SHELL%
%%END
...really
$ cat foo.input
NAME AGE
Alice 22
Bob 31
Charlotte 14
$ ./lib/expand-template.pl foo.template <foo.input
We learned that:
- Alice (age 22) likes /bin/bash
- Bob (age 31) likes /bin/bash
- Charlotte (age 14) likes /bin/bash
...really
There is no "escaping" mechanism for whitespace in column input, and so far there has not actually needed to be one, but possibly the input format may migrate in future to permit an additional CSV alternative.
Local¶
Finally, there are builtin functions to provide primitive loop
constructs; there are %%RANGE
and %%CSV
:
$ cat foo.template
Let's count from 1 to 5!
%%RANGE I 1 5
* %I%
%%ENDRANGE
Also, we learned:
%%CSV alice,apples,dogs bob,carrots,cats %EXTRA_CSV_ENV%
* %1% likes %2%, but not %3%
%%ENDCSV
...which executes as:
$ env EXTRA_CSV_ENV=charlotte,coffee,tea ./lib/expand-template.pl foo.template </dev/null
Let's count from 1 to 5!
* 1
* 2
* 3
* 4
* 5
Also, we learned:
* alice likes apples, but not dogs
* bob likes carrots, but not cats
* charlotte likes coffee, but not tea
Environment (and other) Variables are interpolated into a %%RANGE
or %%CSV
before being executed, as you can see from EXTRA_CSV_ENV
, above.
In %%RANGE
loops the first argument I
becomes the interpolatable as %I%
.
In %%CSV
loops, the arguments alice,apples,dogs
are automatically split on
commas to become numbered variables:
%0%
=alice,apples,dogs
%1%
=alice
%2%
=apples
%3%
=dogs
... and this parse/expansion is iterated over all arguments to the %CSV%
line.
Literal Percent Signs¶
Similar to printf()
, a double-percent-sign in substituted/printed text will
be rendered as a single percent sign, i.e.: Foo%%Bar
-> Foo%Bar
Control Statements¶
There are simple conditionals:
%%IF %BOOLEAN%
<text to be included if value of %BOOLEAN% evaluates to "true">
%%ENDIF
%%IF %BOOLEAN%
<text to be included if value of %BOOLEAN% evaluates to "true">
%%ELSE
<text to be included if value of %BOOLEAN% evaluates to "false">
%%ENDIF
... and simple conditional operations:
$ cat foo.template
%%IF %HOME% contains /Users/
you are probably on a Mac (%HOME%)
%%ELSE
you are probably NOT on a Mac (%HOME%)
%%ENDIF
$ ./lib/expand-template.pl foo.template </dev/null
you are probably on a Mac (/Users/alecm)
... HOWEVER these are intentionally implemented in an exceedingly dangerous fashion, for maximum creative possibilities and laziness:
$ cat foo.template
%%IF %A% %COND% %B%
eval to true
%%ELSE
eval to false
%%ENDIF
$ env A=foo B=foo COND=eq ./lib/expand-template.pl foo.template </dev/null
eval to true
$ env A=foo B=foo COND=ne ./lib/expand-template.pl foo.template </dev/null
eval to false
$ env A=ohfooboo B=foo COND=contains ./lib/expand-template.pl foo.template </dev/null
eval to true
... but woe betide you if you introduce extra spaces or unset variables into a comparison, because whitespace-splitting and parsing happens after variable expansion:
$ env A='' B='' COND='TEAM !contains ME' ./lib/expand-template.pl foo.template </dev/null
eval to true
... and with debugging enabled, note the if-expand
line:
$ env A='' B='' COND='TEAM !contains ME' ./lib/expand-template.pl --debug foo.template </dev/null
PrintBlock: begin at 0, end at 4
found %%IF at 0: %%IF %A% %COND% %B%
found %%ELSE at 2: %%ELSE
found %%ENDIF at 4: %%ENDIF
if-expand: %%IF TEAM !contains ME
Evaluate TEAM !contains ME at ./lib/expand-template.pl line 16.
Evaluate2 TEAM at ./lib/expand-template.pl line 16.
Evaluate2 !contains at ./lib/expand-template.pl line 16.
Evaluate2 ME at ./lib/expand-template.pl line 16.
result: 1
print true block
PrintBlock: begin at 1, end at 1
Echo1 eval to true
Echo2 eval to true
eval to true
symbol dump:
A
B
COND
This is, after all, essentially a macro-processor and not a programming
language; this also reflects the importance of whitespace, that %%IF %I% < 6
is not the same as %%IF %I%<6
; the latter is a non-empty string which will
always evaluate to true
.
Numeric Operators¶
eg: %%IF %NUMDAEMONS% < 6
==
!=
>=
<=
>
<
File Operators¶
eg: %%IF exists templates/nginx-site-%ONION_ADDRESS%.conf
True if the (substituted) filename exists.
String Operators¶
eg: %%IF %ONION% eq facebookcorewwwi
eq
ne
ge
le
gt
lt
contains
!contains
Logic Operators¶
eg: %%IF %BOOL1% and %BOOL2%
- no subexpressions, sorry
and
or
xor
Also, simple boolean conditionals may use !
or not
to invert the
sense of an if-statement, so this is valid:
%%IF not %VALID%
Where VALID is boolean-evaluatable like 0 or 1
...
%%ENDIF
... but the following is invalid because there is no expression parser:
%%IF ! %FOO% eq BAR
*THIS WILL NOT WORK AS INTENDED*
*YOU WANT: %FOO% ne BAR*
...
%%ENDIF
Operator Notes¶
If you are at risk of empty strings for string comparisons, disambiguate the string interpolation with quotes, thusly:
%%IF "%FOO%" eq "%BAR%"
... and please remember that the whole thing will explode messily (and the logic will possibly change) if %FOO% contains a whitespace character.
Relevance to Onionspray/EOTK Config-File Format¶
Variables are set in a config file as:
set foo bar baz
This will result in the (uppercase) Environment Variable FOO
being
set to the string bar baz
, and then being available to the templater
as %FOO%
.
Therefore it is entirely possible (though dangerous) to do this:
set path /usr/bin:/bin:...
... and set the $PATH
environment variable for the rest of template
generation. Doing so will probably break everything, so on your head be it.
IMPORTANT: all variables, excepting project
, are retroactively global in
scope; even if you set them at the bottom of a config file, they impact the
projects at the top. For clarity, keep all globals at the top, and if you
have projects which need different settings then use different config files and
different runs of onionspray configure
.
Variables¶
Template Variables¶
A list of template variables - and their default values - is provided in the example template configuration file.
Fake Variables¶
These are used in Template Configuration (.tconf
) files, and do not
represent real environment variables.
NEW_V3_ONION
: used only in template configs (*.tconf
files) to show the point where a newly created onion address should be inserted.