Text-template is an experiment in a language for static web sites that a) stores metadata with the content using the filesystem as the “database,” and b) converts hierarchical data to flat stream. Any web site of even a small size will soon discover a need for boiler-plate for page titles, headers, and footers. A typical solution is to throw everything into a relational database and have the web server generate files from a template. A relational database guarantees data consistency in the face of multiple updates at the same time and provides easy data queries, but increases complexity of the server, adds a point of failure, and does not provide a flexible data model. On a personal web site, such as the typical WordPress blog, the database provides little advantage because only one person is editing the content, and there is little need for run-time data queries because content rarely changes. Text-template is essentially a NoSQL file-system-based database that also has a rudimentary language for generating the content as a static page.

Download tt.py

Text-template combines a LaTEX style templating approach with a hierarchical data format syntax similar to JSON. There are two kinds of files: data (.t) and templates (.tt).

Data

Data objects have three fundamental object types: structures, arrays, and scalars. These are declared as follows:
Structures
\typename {
   object1
   object2
    ...
}
Arrays
\typename [
   object1
   object2
    ...
]
Scalars
\typename Any text here; can be multiple lines
The typename and a closing } or ] must be the first thing on the line, preceeded only by optional whitespace. A scalar continues until another typename, a } or a ] is encountered. A scalar is always treated as a string. An array need not be homogenous; it may have objects with different typenames.

The data types may be nested within each other (except obviously nothing can be nested inside a scalar). The definition of an object must start with the backslash as the first non-whitespace character on a line. Structures and arrays end with a bracket or brace (as appropriate) as the only non-whitespace character.

When processed, a structure will be substituted into the template named the same as its typename. Generally scalars do not have a template associated with them, in which case the text will be substituted directly. Each element of an array is substituted into the appropriate typename and the results concatenated together. Array elements can be different typenames if desired. Typenames can only have alphanumeric characters, as well as dashes and underlines.

An example of complex data is:
\resume {
  \objective
A software engineering job that is full of exciting challenges
doing cutting-edge work that saves the world
yet is well-crafted and beautiful. I am your 10X, rockstar developer!
  \name Brutus Buckeye
  \address 123 High St.
  \education [
    \degree {
      \school The Ohio State University
      \type Bachelors of Science in Physics
      \gpa 3.71
      \dates Sep 1994 - Jun 1998
    }
    \degree {
      \school University of Texas
      \type Masters of Science in Computer Science
      \gpa 3.85
      \dates Aug 1998 - Jun 2000
    }
  ]
  \jobs [
    \company {
      \name Awesome Startup
      \position Employee #1
      \dates May 2005 - present
    }
    \company {
      \name Startup 2.0
      \position Software Engineer
      \dates Jan 2003 - May 2005
    }
    \company {
      \name Dotcom Startup
      \position Software Engineer
      \dates Aug 2000 - Sept 2002
    }
  ]
}

Templates

Templates are text that substitutes elements from the data objects of that type. Substitutions are made with \typename. Parsing of the typename ends with the first invalid typename character (usually a space). You can also use curly braces if you need a valid character immediately following: \{typename}.

A template starts with \begin typename outputtype and ends with \end typename. The outputtype is actually unused at the moment, but is intended so that different templates could be created for, e.g. text, HTML, and PDF. A template consists of text, commands, and object references. Text is simply output as-is. Commands affect the text that is output. References are of the form \{name}, with the braces optional. name can be any of [A-Zz-a0-9_]; braces delimit the end of the reference name, and are used in a situation like \{uniqId}_id; with the braces the reference \uniqId will be substituted, without it \uniqId_id will be substituted. If the object reference is an structure, the object will be applied to the template of its type (which also doubles as its name). Arrays template each object and substitute the entire concents. Scalars are simply substituted as-is, with references being recursively resolved.

A simple example of a template is:
\begin resume html
<h1><center>\name</center></h1>
Objective:<br>
\objective
\end resume
Data objects can be manipulated by calling functions that alter the content of the text being substituted. So, you could do something like \name.toLower() which takes a scalar value and lowercases it. A complete list is given later.

Templates have a few commands that can be used. Commands must be on their own line.
\ifexists typename
  ...
\else
  ...
\endif
If typename exists in the object (that is, if \typename would substitute something), substitute the following, otherwise, substitute from the \else section. The \else section is optional.
\if boolean-expression
  ...
\else
  ...
\endif
Substitutes the first section if the expression evaluates to true, otherwise the second section is substituted, if it exists.
\ifnot boolean-expression
  ...
\else
  ...
\endif
Same as \if except negated.
\for loopvar-name in \array
  ...
\endfor
Adds body of the loop for each element in the array. The element of the array being processed can be accessed with \loopvar-name. If this is a structure (usually the case), the items in the structure can be accessed as a member variable: \loopvar-name.name.

There are two ways to create templates for the data in the example above. The original intention when the language was designed was:
\begin resume html
<h1><center>\name</center></h1>
Objective:<br>
\objective
Education:<br>
\education
\end resume

\begin degree html
<div style="margin-left: 1em;">
\school   \dates
\type
</div>
\end degree
However, sometimes you need to iterate over the same data object but produce slightly different results (in my case, a list of book reviews sorted by category, title, and author). For this you can use a \for loop:
\begin resume html
<h1><center>\name</center></h1>
Objective:<br>
\objective
Education:<br>
\for degree in \eduction
<div style="margin-left: 1em;">
\degree.school   \degree.dates
\degree.type
</div>
\endfor
\end resume

Data Object Methods

Note that text parameters do not have double-quotes as is usual in languages.

Scalars

substr(startIndex, length)   (returns Scalar)
Returns the substring starting from startIndex and continuing for length characters. Both parameter must be integers.
toLower()   (returns Scalar)
Returns the text as all lowercase.
toUpper()   (returns Scalar)
Returns the text as all uppercase.
equals(text)   (returns boolean)
True if the scalar equals text (case-sensitive)
notEquals(text)   (returns boolean)
True if the scalar does not equal text (case-sensitive).
startsWith(text)   (returns boolean)
True if text is a substring starting from the first character (case-sensitive).
contains(text)   (returns boolean)
True if text is a substring (case-sensitive).
endsWith(text)   (returns boolean)
True if text is a substring ending with the last character (case-sensitive).
add(x)   (returns Scalar)
If scalar resolves to a floating point number, returns scalar + x. x is converted to floating point.
subtract(x)   (returns Scalar)
If scalar resolves to a floating point number, returns scalar - x. x is converted to floating point.
multiply(x)   (returns Scalar)
If scalar resolves to a floating point number, returns scalar * x. x is converted to floating point.
divide(x)   (returns Scalar)
If scalar resolves to a floating point number, returns scalar / x. x is converted to floating point.

Arrays

elementAt(index)   (returns Array)
Returns the object at index (or a scalar with error text if index is out of range.
sortBy(typename)   (returns Array)
Returns the array, sorted by the typename listed. Assumes that the array is an array of structures that include this typename.
reverseSortBy(typename)   (returns Array)
Same as sortBy(), but the results are in the reverse order.
sortByTitle(text)   (returns Array)
Returns the array, sorted by title. This is the same as regular text sorting except that “A” and “The” at the beginning are ignored.

Loop Variables

In \for loops, this is the loop variable specified. If this is part of an array, the special \template variable contains this information.

loopIndex()   (returns Scalar)
Returns the current index of the loop, starting from 1.
loopCount()   (returns Scalar)
Returns number of items in the loop.
isLoopStart()   (returns boolean)
Returns true if this is the first iteration of the loop (or first item in the array).
isLoopEnd()   (returns boolean)
Returns true if this is the last iteration of the loop (or last item in the array).

\option command

The \option is a global command, generally used as the first line in the file.

ignoreIgnores this file. Useful if you have incomplete .t files you have not finished yet, and don’t want generated.
useNewFileForTemplateArray templateName [filenamePrefix]
Each instantiation of templateName is placed in a file named templateNameNNN.html, where NNN is an incrementing number. If filenamePrefix is specified, then the file will be filenamePrefixNNN.html. This is useful, for example, if you are making a slideshow from a array. This also enables \prevURL() and \nextURL() methods on all objects, which return the URL for the previous file and the next file in the series.