Comprehensive technical documentation for the Templatest template engine:
{{toc numerate=1}}
=== Introduction ===
**Templatest** is a sophisticated, transition-style template engine for WackoWiki that combines:
- **Push-style architecture** with pull methods (for CSRF tokens, i18n, etc.)
- **Lazy evaluation** and topologically-ordered compilation
- **Incremental building** freed from traditional von Neumann applicative constraints
- **Efficient caching** with automatic cache invalidation
The engine prioritizes performance through compilation-time optimization while maintaining flexibility for runtime customization.
==== Key Characteristics ====
- **Compile-time focused**: Templates are parsed, compiled, and cached for optimal performance
- **Modular filter system**: Extensible filtering pipeline with escape handling
- **Static analysis**: Identifies and optimizes static content at compile time
- **Push/Pull duality**: Supports both data pushing (pull functions) and variable setting
- **Hierarchical processing**: Nested pattern support with automatic indentation
=== Architecture Overview ===
==== Class Hierarchy ====
%%
Templatest (Compiler & Factory)
↓
TemplatestSetter (Setting & Patching)
↓
TemplatestFilters (Filter Pipeline)
↓
TemplatestEscaper (Security & Encoding)
↓
TemplatestUser (Public API & Rendering)
%%
==== Processing Pipeline ====
%%
Template File
↓
[Parsing] → parse_file()
↓
[Definition Extraction] → inline_definitions()
↓
[Compilation] → compile()
↓
[Static Optimization] → inline_static_subs()
↓
[Default Application] → inline_defaults()
↓
[Caching] → serialize to cache file
↓
TemplatestUser Instance (Ready for use)
%%
=== Core Concepts ===
==== 1. Patterns ====
**Patterns** are named template blocks that serve as reusable components. They're the fundamental building unit of Templatest.
%%
[ ==== patternname ==== ]
template content here
[ ==== otherpattern ==== ]
other content
%%
- **Main pattern**: The first pattern in a file (stored as ##$store[0]##)
- **Named patterns**: Can be called by name as subpatterns or variables
- **Anonymous patterns**: Temporary patterns with autogenerated names (when using ##_## as name)
==== 2. Variables ====
**Variables** are placeholders within patterns that receive data at runtime.
%%
[ ==== mypattern ==== ]
Hello, [ ' name ' ]!
%%
Variables are:
- Marked with quotes: ##[ ' varname ' ]##
- Assigned data through the ##set()## method
- Subject to filtering and escaping
- Can use dot notation for array access: ##[ ' user.profile.name ' ]##
==== 3. Subpatterns ====
**Subpatterns** are pattern references within other patterns, enabling composition.
%%
[ ==== layout ==== ]
<div class="header">[ ' header ' ]</div>
<div class="content">[ ' content ' ]</div>
[ ==== page ==== ]
This is the page content
%%
When ##layout## is rendered with ##set('content', 'page')##, the ##page## pattern is inserted into the content placeholder.
==== 4. Pull Actions ====
**Pull functions** are runtime callbacks for dynamic content generation (CSRF tokens, internationalization, etc.).
%%
[ ==== form ==== ]
<form>
[ ' csrf: ' ]
[ ' form_fields: ' ]
</form>
%%
Format: ##[ ' actionname: args ' ]##
=== Template Syntax ===
==== Tag Structure ====
Tags use a quoted-content pattern with optional pipes:
%%
[ ' tag_content | filter1 arg1 | filter2 arg1 arg2 ' ]
%%
==== Quote Levels ====
The number of quotes determines tag recognition:
%%
[ ' content ' ] - single quotes (standard)
[ '' content '' ] - double quotes (requires double quotes to open)
[ ''' content ''' ] - triple quotes (requires triple quotes to open)
%%
This prevents premature tag matching when quotes appear in content.
==== Comments ====
%%
[ ==== // this is a comment ==== ]
[ ==== # this is also a comment ==== ]
%%
==== Directives (Outside Patterns) ====
Directives appear before any pattern definition:
===== ##.escape## - Set default escaping mode =====
%%
.escape html # Apply HTML escaping to all patterns
.escape js mypattern # Apply JavaScript escaping only to mypattern
%%
Valid modes:
- ##raw## - No escaping
- ##html## - HTML entity escaping
- ##js## - JavaScript string escaping
- ##css## - CSS escaping
- ##url## - URL encoding
- ##html_attr## or ##attr## - HTML attribute escaping
===== ##.patch## - Set default values =====
%%
.patch mypattern varname defaultvalue
%%
===== ##.include## - Include external templates =====
%%
.include path/to/template.tpl
%%
Maximum inclusion depth: 5 levels (circular includes prevented)
=== Patterns and Variables ===
==== Pattern Definition ====
Patterns are defined with equals signs indicating hierarchy:
%%
[ ==== main ==== ] # Main pattern
[ === section === ] # Section header (not used for separation)
[ == subsection == ] # Subsection (flexible)
%%
All are equivalent; the number of ##=## signs is cosmetic. The pattern name must:
- Start with a letter (a-z, A-Z)
- Contain only alphanumeric characters and underscores
- Be unique within the template file
==== Variable Assignment ====
Variables are declared and referenced with quotes:
%%
Template Definition:
[ ' username ' ]
[ ' user.email ' ]
[ ' status | default "pending" ' ]
PHP Usage:
$tpl->set('username', 'John');
$tpl->set('user', ['email' => 'john@example.com']);
$tpl->set('status', null); // Uses default
%%
==== Block vs. Inline Usage ====
Templatest automatically detects usage context:
**Block Usage** (tag alone on line with optional indentation):
%%
<div class="content">
[ ' message ' ]
</div>
%%
Result: Automatic indentation applied
%%html
<div class="content">
Rendered content here
</div>
%%
**Inline Usage** (tag within line content):
%%
<p>Hello [ ' name ' ]!</p>
%%
Result: No indentation, whitespace trimmed
%%html
<p>Hello John!</p>
%%
==== Nested Pattern Usage (Subpatterns) ====
Reference patterns within patterns:
%%
[ ==== header ==== ]
<h1>[ ' title ' ]</h1>
[ ==== page ==== ]
[ ' header ' ]
<p>[ ' content ' ]</p>
%%
Usage:
%%php
$page = Templatest::read('template.tpl');
$page->set('header', ['title' => 'Welcome']); // Header pattern
$page->set('content', 'Page content'); // Page pattern var
%%
==== Context Navigation ====
For complex hierarchies, use context helpers:
%%php
$tpl = Templatest::read('template.tpl');
$i = $tpl->enter('user'); // Enter user context
$tpl->set('name', 'John'); // Sets user_name
$tpl->set('profile', 'Developer'); // Sets user_profile
$tpl->leave(); // Exit context
// Equivalent to:
$tpl->set('user_name', 'John');
$tpl->set('user_profile', 'Developer');
%%
=== Tag System ===
==== Tag Components ====
A complete tag has the structure:
%%
[ ' primary_action | filter1 args | filter2 args ' ]
%%
==== Primary Actions ====
===== 1. Variable Reference =====
%%
[ ' varname ' ]
[ ' varname | filter1 | filter2 ' ]
%%
===== 2. Pattern Reference =====
%%
[ ' patternname ' ] # Call pattern with its own variables
[ ' varname patternname ' ] # Assign pattern to variable
%%
===== 3. Pull Action =====
%%
[ ' action: arg1 arg2 ' ]
%%
Registers a callback that will be invoked during rendering.
===== 4. Dot Notation (Sugar) =====
%%
[ ' user.name ' ] → [ ' user | .name ' ]
%%
Automatically converted to index filter application.
==== Pipe System ====
Pipes connect filters in sequence:
%%
[ ' value | filter1 arg1 | filter2 arg2 | filter3 ' ]
%%
Each filter's output becomes the next filter's input.
=== Filters and Processing ===
==== Filter Execution ====
Filters are applied in order during the ##assign()## phase:
%%
1. Input value
2. Apply filter1(value, arg1) → intermediate
3. Apply filter2(intermediate, arg2) → result
4. Apply filter3(result) → final
5. Apply default escaping
6. Return final value
%%
==== Built-in Filters ====
===== String Transformation =====
%%php
filter_lower($value) // Lowercase
filter_upper($value) // Uppercase
filter_trim($value, $character_mask) // Trim whitespace
filter_replace(args...) // Multiple replacements
filter_regex($value, $re, $to, $limit, $strict) // Regex replacement
%%
===== Formatting =====
%%php
filter_format($value, $fmt) // sprintf()
filter_date($value, $fmt) // date()
filter_number($decimals, $dec_pt, $thousands_sep) // number_format()
filter_join($value, $glue) // implode()
filter_stringify($value) // Convert to string
%%
===== HTML/Content =====
%%php
filter_nl2br($value) // Newlines to <br>
filter_spaceless($value) // Collapse whitespace (preserve <pre>, <textarea>)
filter_striptags($value, $allowable_tags) // strip_tags()
filter_truncate($value, $limit, $ellipsis) // Truncate with ellipsis
%%
===== Data Processing =====
%%php
filter_index($value, $path) // Array/object access
filter_split($value, $delimiter, $limit) // Explode/str_split
filter_json_encode($value, ...options) // JSON encode
filter_json_decode($value) // JSON decode
filter_list($index, ...items) // Select from list
filter_default($value, $default) // Provide default
filter_void($value) // Return null
%%
===== URL/Encoding =====
%%php
filter_escape_also_e($value, $mode) // Escape: html, js, css, url, attr, raw
filter_url_encode($value) // URL encoding (handles arrays)
%%
===== Utility =====
%%php
filter_check($value, $on) // Checkbox value/checked helper
filter_checkbox($value) // Checkbox checked helper
filter_select($value, $on) // Select option selected helper
filter_enclose($value, $pref, $post) // Wrap with prefix/postfix
filter_sp2nbsp($value) // Spaces to
filter_dbg($value) // Debug output (returns value)
filter_pre($value) // Suppress indentation for value
%%
==== Escaping ====
===== Default Escaping =====
Values are automatically escaped based on pattern configuration:
%%php
// At compile time
.escape html // HTML escaping
.escape js // JavaScript escaping
// At runtime
filter_escape_also_e($value, 'html') // Explicit escaping
%%
===== Escape Modes =====
%%
html - <script> → HTML entities
js - "string" → \x22string\x22 (Unicode escaping)
css - #id { → \23 id\20 \7b\20 (hex escaping)
url - spaces → %20 (percent encoding)
attr - comprehensive HTML attribute safety
raw - No escaping (use with caution)
%%
==== Custom Filters ====
Register custom filter functions:
%%php
$tpl = Templatest::read('template.tpl');
// Add a custom filter
$tpl->filter('customfilter', function($value, $arg1, $arg2) {
// Process and return value
return $value;
});
// Now usable in templates
[ ' myvar | customfilter arg1 arg2 ' ]
%%
=== Advanced Features ===
==== 1. Static Optimization ====
Templatest identifies static content at compile time and optimizes it:
%%
Pattern with static subpattern:
[ ==== box ==== ]
<div>[ ' content ' ]</div>
[ ==== footer ==== ]
<footer>© 2026</footer> ← Static (no variables)
Optimization:
- footer pattern marked as static
- Inlined into parent pattern during compilation
- Reduces rendering overhead
%%
==== 2. Auto-Indentation ====
Block-level variables automatically inherit parent indentation:
%%
Template:
<ul>
[ ' items ' ]
</ul>
Setting:
$tpl->set('items', "
<li>Item 1</li>
<li>Item 2</li>
");
Result:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
%%
The 4-space indent is automatically applied to each line.
==== 3. Caching ====
Templates are automatically cached for performance:
%%php
// Cache operations
$tpl = Templatest::read('template.tpl', '/path/to/cache/dir');
// Cache features:
// - Serialized compiled template stored
// - Automatically invalidated if source changes
// - Write-bit control: chmod 000 disables caching for that file
// - CODE_VERSION check prevents version mismatch
%%
Cache file naming:
%%
template.tpl → template@tpl (slashes converted to @)
%%
==== 4. Conditional Defaults ====
The ##default## filter with static assignment:
%%
Template:
[ ' status | default "pending" ' ]
Setting with null:
$tpl->set('status', null); # Uses "pending"
$tpl->set('status', false); # Uses "pending"
$tpl->set('status', 'active'); # Uses "active"
%%
==== 5. Pattern Cloning ====
Access different patterns through magic methods:
%%php
$main_tpl = Templatest::read('template.tpl'); // Main pattern
$header = $main_tpl->header(); // Clone with header pattern
$footer = $main_tpl->footer(); // Clone with footer pattern
// Each is independently renderable
echo $header->set('title', 'Welcome');
echo $footer->set('year', 2026);
%%
=== Developer Guide ===
==== Loading and Initialization ====
%%php
// Basic loading
$tpl = Templatest::read('/path/to/template.tpl');
// With caching
$tpl = Templatest::read('/path/to/template.tpl', '/path/to/cache');
// Clone for specific pattern
$header = $tpl->header();
$page = $tpl->page();
%%
==== Setting Data ====
%%php
// Simple variable
$tpl->set('name', 'John');
$tpl->set('age', 30);
// Array (will use default filter)
$tpl->set('items', ['Apple', 'Banana', 'Orange']);
// Nested variables (underscore syntax)
$tpl->set('user_name', 'John');
$tpl->set('user_email', 'john@example.com');
// Equivalent with context
$i = $tpl->enter('user');
$tpl->set('name', 'John');
$tpl->set('email', 'john@example.com');
$tpl->leave();
// Array-style setting
$tpl->set(['user' => ['name' => 'John', 'email' => 'john@example.com']]);
// Multiple values
$tpl->set('header', 'Welcome', 'content', 'Body text', 'footer', 'Footer');
%%
==== Adding Pull Functions ====
%%php
// Define a pull handler
$tpl->pull('csrf', function($is_block, $location) {
return htmlspecialchars($_SESSION['csrf_token']);
});
$tpl->pull('i18n', function($is_block, $location, $key, $lang = 'en') {
return get_translation($key, $lang);
});
// Usage in template:
// [ ' csrf: ' ]
// [ ' i18n: "welcome.message" "fr" ' ]
%%
==== Adding Custom Filters ====
%%php
// Single filter
$tpl->filter('md5', function($value) {
return md5($value);
});
// Filter with parameters
$tpl->filter('truncate_custom', function($value, $length, $suffix = '...') {
return substr($value, 0, $length) . $suffix;
});
// Usage in template:
// [ ' email | md5 ' ]
// [ ' description | truncate_custom 50 "..." ' ]
%%
==== Character Encoding ====
%%php
// Set encoding (defaults to UTF-8)
$tpl->setEncoding('utf-8');
$tpl->setEncoding('iso-8859-1');
// Get current encoding
$encoding = $tpl->getEncoding();
// Supported encodings (per htmlspecialchars):
// iso-8859-1, iso-8859-5, iso-8859-15
// cp866, cp1251, cp1252
// koi8-r, koi8-ru
// big5, gb2312
// shift_jis, euc-jp, macroman, etc.
%%
==== Rendering ====
%%php
// Direct rendering (uses __toString)
echo $tpl;
// Explicit render
echo (string) $tpl;
// Get as string
$html = $tpl->__toString();
%%
==== Error Handling ====
Errors are triggered with ##E_USER_WARNING##:
%%php
// Configure error handler
set_error_handler(function($errno, $errstr) {
if (strpos($errstr, 'templatest') !== false) {
// Handle template error
error_log($errstr);
}
});
// Template errors include:
// - Unknown filters
// - Invalid tags
// - Undefined variables
// - Pattern redefinition
// - File not found
%%
=== Examples ===
==== Example 1: Basic Layout ====
**Template File** (##layout.tpl##):
%%
[ ==== layout ==== ]
<!DOCTYPE html>
<html>
<head>
<title>[ ' title ' ]</title>
</head>
<body>
<header>
[ ' header ' ]
</header>
<main>
[ ' content ' ]
</main>
<footer>
[ ' footer ' ]
</footer>
</body>
</html>
[ ==== simple_header ==== ]
<h1>[ ' h1_text ' ]</h1>
[ ==== simple_footer ==== ]
<p>© [ ' year | default "2026" ' ]</p>
%%
**PHP Code**:
%%php
$tpl = Templatest::read('layout.tpl', '/cache');
$tpl->set('title', 'My Website');
$tpl->set('header', 'simple_header');
$tpl->set('h1_text', 'Welcome!');
$tpl->set('content', '<p>Main content here</p>');
$tpl->set('footer', 'simple_footer');
$tpl->set('year', 2026);
echo $tpl;
%%
==== Example 2: Form with CSRF Protection ====
**Template File** (##form.tpl##):
%%
.escape html
[ ==== form ==== ]
<form method="POST">
[ ' csrf: ' ]
[ ' form_fields ' ]
<button type="submit">[ ' button_text | default "Submit" ' ]</button>
</form>
[ ==== text_field ==== ]
<div class="form-group">
<label>[ ' label ' ]</label>
<input type="text" name="[ ' name ' ]" value="[ ' value | default "" ' ]">
</div>
[ ==== textarea_field ==== ]
<div class="form-group">
<label>[ ' label ' ]</label>
<textarea name="[ ' name ' ]">[ ' value | default "" ' ]</textarea>
</div>
%%
**PHP Code**:
%%php
$tpl = Templatest::read('form.tpl', '/cache');
// Add CSRF pull handler
$tpl->pull('csrf', function($is_block, $location) {
$token = htmlspecialchars($_SESSION['csrf_token']);
return '<input type="hidden" name="csrf_token" value="' . $token . '">';
});
// Build form fields
$fields_html = '';
// Text field
$field = $tpl->text_field();
$field->set('label', 'Email');
$field->set('name', 'email');
$field->set('value', 'user@example.com');
$fields_html .= $field;
// Textarea field
$field = $tpl->textarea_field();
$field->set('label', 'Message');
$field->set('name', 'message');
$fields_html .= $field;
$tpl->set('form_fields', $fields_html);
$tpl->set('button_text', 'Send');
echo $tpl;
%%
==== Example 3: Data List with Filters ====
**Template File** (##list.tpl##):
%%
[ ==== product_list ==== ]
<ul class="products">
[ ' items ' ]
</ul>
[ ==== product_item ==== ]
<li class="product">
<h3>[ ' name | upper ' ]</h3>
<p>[ ' description | truncate 100 ' ]</p>
<span class="price">[ ' price | number 2 "," "." ' ]</span>
<span class="status">[ ' active | select 1 ' ]Status: In Stock</span>
</li>
%%
**PHP Code**:
%%php
$tpl = Templatest::read('list.tpl', '/cache');
$items_html = '';
$products = [
['name' => 'laptop', 'description' => 'High-performance computing device...', 'price' => 999.99, 'active' => 1],
['name' => 'mouse', 'description' => 'Wireless mouse for productivity...', 'price' => 29.99, 'active' => 1],
['name' => 'keyboard', 'description' => 'Mechanical keyboard...', 'price' => 149.99, 'active' => 0],
];
foreach ($products as $product) {
$item = $tpl->product_item();
$item->set('name', $product['name']);
$item->set('description', $product['description']);
$item->set('price', $product['price']);
$item->set('active', $product['active']);
$items_html .= $item;
}
$tpl->set('items', $items_html);
echo $tpl;
%%
==== Example 4: Internationalization with Pull ====
**Template File** (##i18n.tpl##):
%%
[ ==== page ==== ]
<h1>[ ' title: "page.title" ' ]</h1>
<p>[ ' description: "page.description" ' ]</p>
<button>[ ' button: "actions.submit" ' ]</button>
%%
**PHP Code**:
%%php
$translations = [
'en' => [
'page.title' => 'Welcome',
'page.description' => 'This is a test page',
'actions.submit' => 'Submit',
],
'fr' => [
'page.title' => 'Bienvenue',
'page.description' => 'Ceci est une page de test',
'actions.submit' => 'Soumettre',
],
];
$current_lang = 'fr';
$tpl = Templatest::read('i18n.tpl', '/cache');
$tpl->pull('title', function($is_block, $location, $key) use ($translations, $current_lang) {
return htmlspecialchars($translations[$current_lang][$key] ?? $key);
});
$tpl->pull('description', function($is_block, $location, $key) use ($translations, $current_lang) {
return htmlspecialchars($translations[$current_lang][$key] ?? $key);
});
$tpl->pull('button', function($is_block, $location, $key) use ($translations, $current_lang) {
return htmlspecialchars($translations[$current_lang][$key] ?? $key);
});
echo $tpl;
%%
=== Performance Considerations ===
==== Caching ====
- **Always enable caching** in production: ##Templatest::read($path, $cache_dir)##
- Cache invalidation is automatic when source files change
- Disable caching per-file: ##chmod 000 template.tpl##
==== Static Optimization ====
The engine automatically optimizes:
- Static patterns are inlined
- Non-dynamic content is compiled into chunks
- Reduces runtime interpretation
==== Large Templates ====
- Break templates into included files (max 5 levels)
- Use patterns for reusable components
- Profile with ##filter_dbg## if needed
=== Troubleshooting ===
==== Template Not Found ====
%%
Error: template file /path/to/template.tpl not found
%%
**Solution**: Verify file path and readable permissions
==== No Main Template Found ====
%%
Error: no main template found in /path/to/template.tpl
%%
**Solution**: Define at least one pattern with ##[ ==== name ==== ]##
==== Unknown Filter ====
%%
Warning: unknown filter 'myfilter' at /path/to/template.tpl:15
%%
**Solution**: Register filter with ##$tpl->filter('myfilter', $callback)##
==== Too Deep Template Inclusion ====
%%
Error: too deep template inclusion
%%
**Solution**: Maximum inclusion depth is 5 levels. Reorganize template includes.
==== Pattern Redefinition ====
%%
Error: attempt of redefining pattern 'name' at /path/to/template.tpl:10
%%
**Solution**: Pattern names must be unique within a file.
=== Summary ===
Templatest provides a powerful, efficient template engine optimized for WackoWiki. By understanding:
1. **Patterns** as reusable components
2. **Variables** for dynamic content
3. **Filters** for data transformation
4. **Pull/Push** duality for flexible rendering
5. **Caching** for performance
Developers can create maintainable, high-performance templates with strong security defaults (HTML escaping) and extensibility through custom filters and pull handlers.