Follow on GitHub

Texthon

Half Text, Half Python

«  Introduction   ::   Contents   ::   Python API  »

Documentation

Overview

Texthon compiles a template file into a template module. Each template module contains one or more template functions.

During the parsing stage, a template file is processed line by line, where each line may be a directive or text. Text lines may contain placeholders that will be replaced by evaluated Python expressions.

Once compiled, a template function may be called with corresponding parameters. The result of the call is a Python string.

This snippet demonstrates all the stages:

import texthon
engine = texthon.Engine()

# parse and store the parsed module
module = engine.load_file("template.txt")

# Store the path so we can find the compiled module later.
path = module.path

# compile all modules
engine.make()

# call the template function named 'main'
print(engine.modules[path].main())

Command Line Script

A command line script is available to evaluate template files without having to write your own Python code.

Invoke texthon --help for documentation on the script parameters.

Samples

The tests directory contains sample templates. They are:

  • hello - a minimal example
  • basic - shows basic directive use and template module loads
  • html - shows the use of template mixins and parser parameters
  • nest - using the utility class to control indentation
  • cpp - templates that generate c++ structs and type information

Directives

directive     ::=  <dir_prefix> dir_statement
dir_statement ::=  comment | exec_stmt | keyword_stmt
exec_stmt     ::=  single_exec | compound_begin | compound_end
keyword_stmt  ::=  load_stmt | import_stmt | attribute_stmt | template_stmt | end_stmt

Directives are Texthon control commands and do not affect the output text directly. A directive is issued on its own line, beginning with the directive prefix token. The rest of the documentation assumes ‘#’ is the prefix.

Prepend ‘\’ to escape the token. Escapes are only processed at the beginning of the line.

Directives operate either in the module scope or the function scope. Only function scope directives should be issued between #template and #end template lines.

Tip

There may be arbitrary amount of whitespace between the prefix and the command. In addition, the parser allows for extra characters after the directive for most of the commands, so you can do:

<!--# template name -->

In the example above, the prefix is “<!–#”, and anything after “name” is ignored, so you can close out the line with an xml comment end.

Since the parser does not handle execution statements itself, remember to add a ‘#’ if you want to do the same trick:

<!--#! print("foo") #-->

Comment

Module and function scope:

comment ::=  "*" <comment>

Comments are ignored at the module scope, and emitted as Python # comments at the function scope to aid debugging.

Example:

#* just a comment left by the author

Import

Module scope:

import_stmt ::=  "import" <module> "as" <alias>

Import a Python module under <alias>. The alias is required. Once imported, the alias will be available for all definitions in the module.

Example:

#import string as str
#import os.path as path

Load

Module scope:

load_stmt ::=  "load" <path> "as" <alias> ["(" <attributes> ")"]

Load another template module in the file specified by <alias>. <path> is resolved relative to the directory of the current file and the list of include paths added by engine.add_includes.

The attributes indicate how the parser will load and process the file. The following attributes are available:

  • abs - do not use the path of the current file to resolve <path>. Off by default.
  • directive_token = <token> - load the file with <token> as the directive token. Same as the current file by default.
  • placeholder = <char> - use <char> as the character prefix for placeholders. Same as the current file by default.

Example:

#load "lib/file.tmpl" as lib
#load "lib/file.tmpl.html" as html_lib (directive_token = "<!--")
#load "lib/file.tmpl" as lib (abs)

Attribute

Module scope:

attribute_stmt ::=  "attribute" <name> "=" <expression>

Declare a module level variable <name> and assign the result of <expression>.

Within the same module, an attribute can be read by a template function without any qualification. To assign a new value, however, you must go through the builtin variable _module:

#! temp_name = name # ok, if name has been defined as an attribute
#! _module.name = "new name" # assignment requires _module access

More details.

Example:

#attribute author = "anonymous"

Template Function

Module scope:

template_stmt ::=  "template" <name> ["(" <parameters> ")"]
end_stmt      ::=  "end" "template"

Define a template function within the current module. Anything between #template and #end template are evaluated in function scope.

An optional list of identifiers can be provided as parameters. Without the parameter list, the template is considered to be a variable argument function. The arguments will then be available as builtins _args and _kwargs. More details.

Example:

#template join_tokens
joined: ${", ".join(args)}
#end template

#template header(author, title)
$title by author $author
#end template

Single Statement

Function scope:

single_exec ::=  "!" <python_statement>

The entire <python_statement> is copied over to the generated template function code (with indentation stripped). Write the statement as if it were inside a normal Python function.

Example:

#! temp = "temporary value"
#! break
#! continue
#! _output.write(another_template())

For builtin variables available to the execution statement, refer here.

Compound Statement

Function scope:

compound_begin ::=  "{" <python_statement>
compound_end   ::=  "}"

Compound statements are useful for Python control logic such as if, for, and while. Text lines and other execution statements between #{ and #} are considered part of the compound block.

Example:

#{ for i in range(0, 5):
line $i
#}

#{  if setting:
setting is true!
#}
#{  else:
setting is false!
#}

Text Line

Text lines are appended to the evaluation output after placeholder substitution is performed. All placeholders begin with the placeholder character (by default ‘$’). Placeholders can be escaped by repeating the character twice (“$$”).

Tip

Text lines are ignored at the module scope. Take advantage of that to ‘trick’ editors into highlighting your template file correctly.

For example, consider this c++ template file, with “//#” as the directive prefix, and ‘%’ as the placeholder:

void initialize(int* values)
{
//# template set_values(count)
//#     {for i in (0, count):
    values[%i] = %i
//#     }
//# end template
}

In the template above, only value assignment is part of the generated text. The function signature and brackets are ignored, but they tell your editor how to highlight the entire file, and give context to your template.

Identifier Placeholder

$<identifier>

For simple identifier substitution, just prepend the placeholder character to the name.

Example:

$author is responsible for this document

Expression Placeholder

${<python_expression>}

For complex substituion, surround a Python expression with brackets. <python_expression> will be evaluated using Python eval, and the result is used for the substitution.

‘\’ can be used within the expression to escape the ending bracket ‘}’,

Example:

the sum of the values is ${a + b}
lookup result: ${values[key]}

Slurp Placeholders

$<
$>

Two special place holders, $< and $> , are used to discard parts of the text line; they are useful for whitespace control.

Anything in the text line before $< is discarded.

Example:

text line 0
    $<text line 1
        $<text line 2

will generate the following output:

text line 0
text line 1
text line 2

This allows you some flexibility in indenting the source template lines, making them more readable.

Anything in the text line after $> is discarded.

Example:

this is the end of the file
#end template

will generate a newline after “file” since it’s considered part of the textline, whereas:

this is the end of the file$>
#end template

will ensure that no newline occurrs at the end of the output.

Template Execution

When a template function executes, it has a set of variables defined on entry. Here they are in definition order:

The builtin locals are:
  • _module - the module containing the executing function
  • _output - an output stream that goes directly towards the generated string. Write anything you like to the stream and it’ll be added to the function’s return.
  • _args - positional arguments for variable argument functions. Use like Python *args.
  • _kwargs - keyword arguments for variable argument functions. Use like Python **kwargs.
  • _textdb - reserved for Texthon

Template Modules

Template functions in the same file are grouped into a template module. After a load, you can access a module’s functions through the alias:

#load "lib/lib.tmpl" as lib
${lib.template_func()}

All paths that resolve to the same file share the same module. If you modify a module attribute, it’ll affect all template code that has the same module loaded.

Modules can be combined to form new modules. This usage is called mixins, which is Texthon’s solution to template polymorphism:

#load "mod1.tmpl" as mod1
#load "mod2.tmpl" as mod2
#load "mod2.tmpl" as mod3
#!new_module = mod1(mod2, mod3)
${new_module.attr}

The example above creates a new module that contains all definitions and aliases within mod1, mod2, and mod3. new_module is formed by first cloning mod1 with a shallow copy, and then inserting mod2 and mod3 as mixins. The final lookup for “attr” is valid as long as “attr” is available in any of the three modules.

See the html sample for example usage.

Utility Classes

Texthon provides some utility classes/functions to aid template formatting. They are automatically imported under the _utils module alias.

See the nest sample for templates that rely on the Indent class to dynamically control whitespace.

More details.

«  Introduction   ::   Contents   ::   Python API  »