View source for Templatest: Template Engine - Technical Documentation

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.