Hoot is a Tcl-powered text preprocessor. That is, it's is a tool for dynamically generating any kind of textual content -- especially where the textual content outweighs the generating program. It's useful for generating all kinds of things: prose, HTML, CSS, LaTeX, SQL, even C.
By way of rough analogy:
But unlike Scribble or Jinja, which introduce their own (complicated) syntax on top of their host language, Hoot embraces Tcl and modifies its syntax as little as possible. In fact, Hoot makes only one change to Jim, its host Tcl dialect.
And it's a 375k statically-linked binary that can be built on virtually
any system with a C compiler (or you can source hoot.tcl
from a Tcl
program).
The input
$[+ template greet {name} +]
"Hello, ${name}!"
$[- end template -]
$[set dogName "Charlie"]
$[set place "building"]
# A Visit from $dogName
Here comes my dog, $dogName.
$[greet $dogName]
$dogName sniffs around, wandering here and there.
Then he comes over for some pats on the head
and a chin scratch. Then it's time to go.
"So long, $dogName!"
*$dogName has left the $place*
produces the output
# A Visit from Charlie
Here comes my dog, Charlie.
"Hello, Charlie!"
Charlie sniffs around, wandering here and there.
Then he comes over for some pats on the head
and a chin scratch. Then it's time to go.
"So long, Charlie!"
*Charlie has left the building*
Hoot can be run from the command line. You can pass in a filename:
hoot myfile.hoot.md
or you can pass -
as the filename to read from stdin:
mycmd | hoot -
Hoot outputs to stdout, so you can redirect its output to a file or pipe it to another command:
hoot myfile.hoot.md > myfile.md
hoot myfile.hoot.md | another-command
Hoot can also be used directly from a Tcl program. More details in the Usage section below.
Tcl is a simple, yet surprisingly powerful
language. Hoot was
inspired by Tcl's
subst
command, which makes Tcl tantalizingly close to being a proper
templating language in its own right.
The main thing that is holding it back is that the Tcl syntax for
commands ([...]
) is pretty common in normal prose (in Markdown for
example). Plus, in a templating environment, we'd rather that some
commands (such as set
) don't produce any output.
Tcl is commonly pronounced like "tickle", and a tickle makes you laugh, or... "hoot". Plus I like owls. What can I say?
If you already know Tcl -- specifially the Jim
dialect -- then
all you need to know about Hoot syntax is that you use $[command ...]
instead of [command ...]
. Note the $
at the beginning. Now you know
how to use Hoot. There is more syntax sugar, but it's optional.
Very succinctly, Hoot's syntax can be described as consisting of three forms:
${…}
inserts a variable$[…]
inserts the output of a command$(…)
inserts the result of an expression
Let's discuss each one in more detail…
The most basic and most common thing to do with Hoot is set and use variables. You set variables like this:
$[set myVariable "some really long text I'd rather not type"]
and you use them in text like this:
The rest of this line is ${myVariable}
And this line is also ${myVariable}
which results in:
The rest of this line is some really long text I'd rather not type
And this line is also some really long text I'd rather not type
One neat thing about Tcl is that variables can use almost any characters
in their names. The only exception is whitespace. You can name
a variable foo/bar
or even this.is/my#variable-name
.
If the variable name consists of only letters and numbers, you can omit the curly braces when using them. For example:
$[set myVar "some really long text..."]
The rest of this line is $myVar
Insert the output of any Tcl commands by using $[…]
.
For example:
$[set foo "Hello"]
$[string reverse $foo]
will produce
olleH
This is a big part of what makes hoot so powerful. Unlike say, Jinja, which requires writing custom filters or extensions, you can write Tcl code directly inside your text.
Sometimes we want to run a command, but we don't want its output
included in our text. For these situations, we can use the form
$[. …]
. Whitespace is optional after the .
. For example:
$[set name "Sam"]
$[set greeting "Hello"]
$[. append greeting ", $name"]
$[.append greeting " there"]
A common greeting goes like this: "$greeting".
will produce
A common greeting goes like this: "Hello there, Sam".
If you know Tcl, you may have been thinking "Hey,
set
returns a value. Why isn't it included in the text?". In Hoot, some commands are implicitly silent, since they are very common and we almost never want their output.set
is one such command.
Sometimes we want to insert the result of some math in our text. In
standard Tcl, we do this using the expr
command, but Jim introduces
a convenient shorthand: $(…)
.
For example:
$[set x 123]
$[set y 456]
The result of $x + $y is $($x + $y).
The result of $x squared is $($x ** 2).
will produce
The result of 123 + 456 is 579.
The result of 123 squared is 15129.
A wide range of mathematical and logical operators are supported.
Hoot supports all of the standard Tcl backslash escape sequences.
Additionally, you can backslash-escape the dollar sign in any of the the big 3 syntax forms. For example:
\${myVariable}
\$[myCommand]
\$(1 + 2)
will produce
${myVariable}
$[myCommand]
$(1 + 2)
Sometimes we may want to produce text that uses a lot of backslashes as part of its syntax (cough LaTeX cough). In those situations, backslash escapes can be surprising and annoying.
For instance, you would not expect
\textbf{My bold text}
to produce
extbf{My bold text}
That happens because Hoot interprets \t
as a tab character. In these
settings, we can disable Hoot's backslash escape processing by setting
the BS
environment
variable. We'll
discuss how to do that a little later on.
In Hoot, the !
command ignores anything passed into it and produces no
output. Thus it works effectively as a way to add comments to text.
For example:
$[! This is a comment]
And this is text
will produce
And this is text
Control structures are all those things that control the flow of
a program. In Hoot, there are 2: each
and if
.
In general, Hoot control structures begin with an "opening tag" of the
form $[+ command …arguments… +]
.
They end with a "closing tag" of the form $[--]
. The closing tag may
have any text in between the hyphens. That is, the following are all
identical:
$[--]
$[-end-]
$[- end -]
$[- I love Hoot -]
Loops over the items in a list, and outputs the text between the open and close tag separated by newlines. For example:
$[+ each number {1 2 3} +]
$number squared is $($number ** 2)
$[- end each -]
will produce
1 squared is 1
2 squared is 4
3 squared is 9
The syntax of each
is the same as Tcl's
foreach
,
so you can loop over the keys and values of a dictionary. For example:
$[+ each {k v} {a 1 b 2 c 3} +]
The key $k has the value $v
$[--]
will produce
The key a has the value 1
The key b has the value 2
The key c has the value 3
Additionally, the variable name can be omitted, in which case it will
default to it
. For example:
$[+ each {a b c} +]
It is $it
$[--]
will produce
It is a
It is b
It is c
if
is comparable with Tcl's
if
. For
example:
$[set x abc]
$[+ if {$x eq "abc"} +]
x is abc
$[--]
$[+ if {$x eq "xyz"} +]
x is xyz
$[--]
will produce
x is abc
As in Tcl, we can also use else
and elseif
, by using the syntax $[~ else ~]
or $[~ elseif … ~]
. For example:
$[set x "foo"]
$[+ if {$x eq "abc"} +]
x is "abc"
$[~ elseif {$x eq "foo"} ~]
x is "foo"
$[~ else ~]
x is neither "abc" or "foo"
$[- end if -]
will produce
x is "foo"
A core part of most templating systems is the ability to define and name large chunks of text, and the ability to include other files which may use these large chunks of text.
Hoot introduces a convenient way of assigning a large amount of textual
content to a variable without using $[set …]
. It's analogous to
Jinja's {% block %}
.
For instance, an HTML template might be
<html>
<head>
<title>${title}</title>
</head>
<body>
${content}
</body>
</html>
While $[set title "Some Title"]
feels fine, it would be annoying to
have to define the entirety of the HTML content using $[set content ...]
- if for no other reason than that our editor won't
syntax-highlight it properly. In these cases, we can use a syntax
similar to control structures to define variables:
$[+ block content +]
<div>
This is some content
…
</div>
$[--]
This sets the value of content
to everything between $[+ block content +]
and $[--]
, excluding any whitespace at the beginning and
end. So in this case, $content
will begin with <div>
and end with
</div>
, without newlines before and after.
As with control structures, you can put text between $[-
and -]
. For
example, you could write $[- end block -]
or $[- end content -]
.
$[include path]
or $[> path]
Reads the content of a file, processes it with Hoot, and inserts the result.
Analogous to Jinja's {% include %}
or
Sass's
@import
.
path
is interpreted in a few ways, depending on its first few characters:
./
or../
-- means "relative to the path of the file being processed". That is, if/the/path/to/my/file.md
is being processed, and it contains$[> ../other/file.md]
, the contents of/the/path/to/other/file.md
is inserted.~/
-- means "relative to the directory from which thehoot
command was run"./
-- means it is an absolute path to a file.- Otherwise,
path
is considered relative to the current working directory. This usually behaves the same as~/
, but can be different in some edge-cases (ie. if$[cd /some/other/path]
is at the top of the file)
Blocks and includes can be combined for an effect similar to Jinja's template inheritance. For instance:
base.hoot.html:
<html>
<head>
<title>MegaCorp - ${title}</title>
</head>
<body>
${content}
<script src="${pageScript}"></script>
</body>
</html>
index.hoot.html:
$[set title "Home"]
$[set pageScript "/home.js"]
\
$[+ block content +]
<h1>This is the home page of MegaCorp</h1>
<div>More content here</div>
$[- end content -]
\
$[> base.hoot.html]
Running hoot index.hoot.html
will output:
<html>
<head>
<title>MegaCorp - Home</title>
</head>
<body>
<h1>This is the home page of MegaCorp</h1>
<div>More content here</div>
</body>
</html>
Optionally, a dictionary can be passed as a second argument to
include
, and it will be used to set variables in that file's context.
In the example above, we could remove the first two lines and replace the last line with:
$[> base.hoot.html {
title "Home"
pageScript "/home.js"
}]
which would generate identical output.
Sometimes we might want to have a some content which is chunked into blocks, where each block has some default value but can be overridden.
In these cases, we can use defblock
, which defines the content of
a block only if it is not already defined. By default, defblock
also
outputs its contents in-place. This is particularly useful when combined
with includes. For example:
super.hoot.html
<html>
<head>
$[+ defblock head +]
<link rel=stylesheet href="/default-styles.css">
$[- end head -]
</head>
<body>
$[+ defblock body +]
This page has no content.
$[- end body -]
</body>
</html>
sub.hoot.html
$[+ block head +]
<link rel stylesheet href="/alt-styles.css">
$[--]
$[+ block body +]
<b>This page has content!</b>
$[--]
$[> super.hoot.html]
Running hoot sub.hoot.html
will output:
<html>
<head>
<link rel stylesheet href="/alt-styles.css">
</head>
<body>
<b>This page has content!</b>
</body>
</html>
In the uncommon case where you need defblock
functionality, but you
want it to not output its contents right away, you can use the .
command:
$[+ . defblock myblock +]
This is myblock
$[--]
Will define myblock
and output nothing.
Templates are comparable with functions in regular programming languages, and are defined using the same syntax as control structures and blocks.
They also share syntax with Tcl's
proc
,
which means arguments can have default values, and templates can take
variadic arguments.
Here's a template that renders an HTML <input>
element:
$[+ template input {name {type text} {value ""}} +]
<input name="${name}" type="${type}" value="${value}">
$[- end template -]
To use them, we call them like commands. For example:
<form>
$[input userEmail]
$[input userPass password]
</form>
will output
<form>
<input name="userEmail" type="text" value="">
<input name="userPass" type="password" value="">
</form>
A common pattern for simulating named arguments is to define a template
with variadic arguments, and then use the @
command (discussed below)
to treat them like a dictionary. Here's the same template from above,
rewritten to use named optional arguments:
$[+ template input {name args} +]
$[set type $[@ $args type "text"]]
$[set value $[@ $args value ""]]
<input name="${name}" type="${type}" value="${value}">
$[- end template -]
To use this template:
<form>
$[input userEmail]
$[input userPass {type password}]
</form>
which will have the same output as above.
In addition to all the standard Tcl commands, Hoot includes a number of commands that are particularly useful in a templating environment.
-
$[or $x $y]
Outputs the value of$x
, unless it is an empty string, in which case it outputs the value of$y
. -
$[= $x if {expression}]
Outputs the value of$x
if the result ofexpression
is truthy. Theif {expression}
portion may be omitted, in which case it outputs the value of$x
directly. -
$[? {expression} $x $y]
Outputs the value of$x
if the result ofexpression
is truthy, otherwise outputs the value of$y
. -
$[@ $dict key defaultValue]
A synonym fordict getwithdefault
. Outputs the dictionary's value forkey
, if it exists. Otherwise, outputsdefaultValue
.defaultValue
may be omitted, in which case it is an empty string. -
$[source path]
Analogous to Tcl'ssource
. Loads and evaluates the contents ofpath
, but outputs nothing.path
is interpreted in the same way as withinclude
. -
$[do code]
Analogous to Tcl'seval
. Executescode
, but outputs nothing. -
$[contentsOf path]
Outputs the contents of the file atpath
without processing them in any way.path
is interpreted in the same was as withinclude
.
$[first $list]
- an alias for$[lindex $list 0]
$[last $list]
- an alias for$[lindex $list end]
$[rest $list]
- an alias for$[lrange $list 1 end]
$[str/len $x]
- an alias for$[string length $x]
$[str/first $x]
- an alias for$[string index $x 0]
$[str/last $x]
- an alias for$[string index $x end]
$[str/rest $x]
- an alias for$[string range $x 1 end]
Hoot can be run from the command line, or from a Tcl program.
Usage:
hoot - Process input from stdin
hoot <path> Process file at <path>
hoot -t/--tcl Output Hoot Tcl code
hoot -h/--help Print this message
There are a few environment variables that can be used to affect Hoot's execution. They are:
PWD=path
causes Hoot to considerpath
to be the "root" directory, from which it interprets other paths.FILE=path
tells Hoot the file path of the input. This is only useful when piping stdin into Hoot.BS=1
causes Hoot to ignore backslash escapes. This is helpful when processing backslash-heavy content such as LaTeX.PREP=1
is used for debugging. This causes Hoot to skip rendering the input and instead output the intermediate raw Tcl that would be passed tosubst
.
Here is an example of using all 3:
PWD=/path/to/files BS=1 PREP=1 hoot myfile.md
The core functionality of Hoot is implemented entirely in around a hundred lines of Tcl.
To use it directly from a Tcl program, first create a file called
hoot.tcl
with the Hoot Tcl code. You can do this in one of two ways:
hoot -t > hoot.tcl
# or
cp /path/to/hoot/code/hoot.tcl hoot.tcl
Once you have that file, simply source hoot.tcl
.
The procs beginning with H/
can be used to do everything the hoot
command line program does. In fact, when you run hoot myfile
from the
command line, it simply executes the Tcl code H/file myfile
.
These procs are short and easy to read, and their source in your
hoot.tcl
file should be considered their canonical
documentation. However, here is a brief description:
H/prep text
Convertstext
from Hoot syntax into raw Tcl.H/subst text
Runs the raw Tcl fromH/prep
.H/render text
Essentially returnsH/subst [H/prep text]
H/path
Interprets a path as perinclude
H/file path
Essentially returnsH/render [H/path path]
Hoot is written in the Jim dialect of Tcl, and will likely not run as core Tcl.
Hoot can be built on almost any system that has a C compiler, including many embedded systems. The only required library is libm.
Building Hoot is simple. After cloning the repository and cd
ing into
the directory, simply run:
make
This will build the statically-linked hoot
executable.
If you want to install Hoot, run:
make install
# or
prefix=/installation/path make install
By default, prefix=/usr/local
.
Right now Hoot's test suite is one pathological input file.
To run the tests:
make test
The tests have passed if the output is
PASS
If an error occurred, it is displayed. If no error occurred, but the actual output doesn't match the expected output, a side-by-side diff is displayed.
A better testing setup is planned, and contributions are welcome.
,_,
(o,o) Hoot: A Tcl-powered
{`"'} text preprocessor
-"-"-