Prebyte is text templating CLI and C++ library with recursive-descent parser, typed AST, compiled template cache, file-aware include resolution, and {{ ... }} syntax.
Contribution guide: see CONTRIBUTING.md.
Implemented now:
{{ variable }}interpolation- object member access like
{{ user.name }} - list index access like
{{ items[0] }} {{ include "file.txt" }}{{ if ... }} ... {{ elseif ... }} ... {{ else }} ... {{ endif }}{{ for item in items }} ... {{ else }} ... {{ endfor }}{{ for key, value in object }} ... {{ endfor }}{{ set name = expression }}- filters with pipes:
trim,upper,lower,default,replace - builtin variables like
__FILE__,__DATE__,__UUID__,__RANDOM__ - explicit Lua via
{{ lua ... }},{{ lua:block }},if lua(...), andif lua:block - user-defined functions via
{{ fn ... }} ... {{ endfn }}and{{ fn ... lua:block }} ... {{ endfn }} - stdin or file input
- stdout or file output
- CLI variables via
-D - structured variable imports from JSON, YAML, TOML, and
.env - settings and profiles
- global and file-specific rules
- unit tests and integration tests
- benchmark runner with persisted history
Not implemented yet:
whileloops- macros separate from functions
- advanced cache controls exposed to users
make is convenience wrapper for Unix-like shells. Cross-platform path is CMake presets.
makeOr with CMake + Ninja:
cmake --preset dev
cmake --build --preset devOn Windows, use CMake preset commands above from Developer PowerShell or terminal with CMake, Ninja, compiler, and Lua installed.
Build current-host ReqPack package on Linux or macOS:
make reqpackOutput is written to dist/, including target .rqp and index.json.
Tagged releases publish versioned binaries to GitHub Releases.
Linux/macOS releases also publish ReqPack .rqp assets plus index.json for ReqPack repository consumption.
Container images are published to GHCR for Linux x86_64 and aarch64:
docker pull ghcr.io/coditary/prebyte:latest
docker run --rm ghcr.io/coditary/prebyte:latest -h
cat input.txt | docker run --rm -i ghcr.io/coditary/prebyte:latestmake testOr with CMake:
ctest --preset devOn Windows, prefer cmake --build --preset dev --target prebyte_tests then ctest --preset dev.
make benchmarkmake benchmark runs:
- internal Prebyte benchmark history update in
tests/benchmarks/history.md - Prebyte vs Go comparison from
tools/benchmark_compare/
Cross-engine comparison prints three modes:
cold: fresh engine and parse path on each renderwarm-execute: parse and prepare once, then execute again without final output memoizationwarm-memoized: repeat same render after final output memoization is primed
Benchmark history is stored in tests/benchmarks/history.md.
Basic usage:
prebyte input.txt
prebyte input.txt -o output.txt
cat input.txt | prebyte
prebyte template.txt arg0 arg1
prebyte -- foo barCommands:
prebyte <input>render file to stdoutprebyte <input> -o <output>render file to fileprebyte -e <topic>explain topicprebyte list rulesprebyte list varsprebyte list profilesprebyte list ignoreprebyte -hprebyte -v
Options:
-o, --output <file>output file-I, --include-path <dir>add include root, first match wins-Dname=valueset variable-Dname=@path/to/fileread file into variable-Dname=@@literalescape leading@-Dpath/to/file.envimport variables from file-r, --rule <rule>set global or file-specific rule-s, --settings <file>load settings file-i, --ignore <name>ignore named variable during render-p, --profile <name>apply profile--benchmarkappend timing output-X, --debugenable debug flag in effective settings
Render args:
- Extra positional values after input are exposed as
ARGS[index] - For stdin mode, use
--before args:prebyte -- foo bar ARGS[0]is first extra value- Bare
ARGSis invalid; use an index
Interpolation:
Hello {{ name }}
{{ user.name }}
{{ items[1] }}Include:
{{ include "partials/header.txt" }}Include lookup order per root:
<include>.pbc<include>.pbt<include><include>/index.pbc<include>/index.pbt<include>/index
Include roots are checked in this order:
- current file directory
- each CLI
-I/--include-pathin order - settings
include_pathsin order - legacy
include_path ~/.local/share/prebyteon Unix-like systems or%LOCALAPPDATA%\Prebyte\shareon Windows
First matching root wins.
Conditionals:
{{ if enabled }}
Enabled
{{ elseif fallback }}
Fallback
{{ else }}
Disabled
{{ endif }}Loops:
{{ for item in items }}{{ item }}{{ else }}empty{{ endfor }}
{{ for key, value in user }}{{ key }}={{ value }};{{ endfor }}Set:
{{ set title = name | trim | upper }}
{{ title }}Whitespace trim:
A {{- name -}} BNative condition truthiness:
false,0,no,off, and empty strings are falsetrue,1, and other non-empty strings are true- string checks are trimmed and case-insensitive
- empty lists and empty objects are false
Supported expression operators:
!&&||==!=<,>,<=,>=in- parentheses
Builtins:
__TIME____LINE____FILE____FILENAME____DIR____EXTENSION____DATE____TIMESTAMP____YEAR____MONTH____DAY____UNIX_EPOCH____USER____HOST____OS____WORKING_DIR____UUID____RANDOM__ARGS[index]
Notes:
__DATE__usesYYYY-MM-DD__TIMESTAMP__usesYYYY-MM-DDTHH:MM:SS__EXTENSION__has no leading dot- dynamic builtins like
__UUID__and__RANDOM__stay constant during one render
Filters:
trimupperlowerdefault(fallback)replace(from, to)
Examples:
{{ name | trim | upper }}
{{ missing | default("fallback") }}
{{ title | replace("_", "-") }}Native template function:
{{ fn greet(name) }}Hello {{ name }}{{ endfn }}
{{ greet("Ada") }}Lua-backed function:
{{ fn users() lua:block }}
return { { name = "Ada" }, { name = "Grace" } }
{{ endfn }}
{{ for user in users() }}{{ user.name }};{{ endfor }}Function rules:
- functions are available only after their definition
- function definitions do not produce output
- native template functions return rendered text as string
- Lua functions can return scalars, lists, objects, booleans, numbers, or null
- functions defined in a file are visible in later includes from that point
- functions defined inside an include stay local to that include
{{ lua "return upper(name)" }}
{{ lua:block }}
return "Hello " .. name
{{ endlua }}
{{ if lua("return enabled == 'true'") }}
Enabled
{{ endif }}
{{ if lua:block }}
return enabled == 'true'
{{ endlua }}
Enabled
{{ endif }}Built-in Lua helpers:
upper(value)lower(value)trim(value)starts_with(value, prefix)ends_with(value, suffix)
Default Lua limits:
lua_instruction_limit=100000lua_memory_limit_bytes=4194304
Lua sandbox blocks os, io, debug, package, require, dofile, and loadfile.
Examples:
prebyte input.txt --rule strict_variables=true
prebyte input.txt --rule .md::default_variable_value=Fallback
prebyte input.txt --rule README.md::strict_variables=falseSupported rules in current implementation:
strict_variablescase_sensitive_variablesdefault_variable_valuevariable_prefixvariable_suffixmax_variable_lengthreplace_tabstab_sizetrimallow_includesinclude_pathoutput_encodingallow_envforbidden_env_varserror_on_false_inputlua_instruction_limitlua_memory_limit_bytesmax_include_depthmax_render_time_msmax_output_size_bytesmax_loop_iterationdebug
Rule notes:
output_encodingsupportsutf-8andutf-16output_encodingonly affects file output via-o/--outputandPrebyte::process(..., output_path)/process_file(..., output_path)- returned strings and stdout output stay UTF-8
error_on_false_input=falsekeeps normalif/elseiffallback behaviorerror_on_false_input=trueraises a runtime error when anif/elseifcondition is falsey
Example:
variables:
greeting: Hello
rules:
strict_variables: false
lua_memory_limit_bytes: 1048576
file_rules:
.md:
default_variable_value: Fallback
profiles:
friendly:
variables:
greeting: HiSupported top-level keys:
variablesinclude_pathsrulesfile_rulesprofilesignore
Compiled templates:
.pbt= source template.pbc= compiled template cache- Prebyte prefers
.pbcfor includes when cache is still fresh
Useful explain topics:
prebyte -e ruleprebyte -e ignoreprebyte -e profileprebyte -e truthinessprebyte -e luaprebyte -e ARGS
Convenience wrapper:
#include "PrebyteEngine.h"
int main() {
prebyte::Prebyte prebyte;
prebyte.set_variable("name", "Ada");
prebyte.add_argument("first-extra");
std::string output = prebyte.process("Hello {{ name }}\n");
}Embed API:
#include "Engine.h"
#include <iostream>
int main() {
prebyte::Engine engine;
prebyte::CompiledTemplate tpl = engine.compile("Hello {{ name }}\n");
prebyte::RenderContext ctx;
ctx.set("name", "Ada");
std::string output = engine.render(tpl, ctx);
engine.render_to(tpl, [](std::string_view chunk) {
std::cout.write(chunk.data(), static_cast<std::streamsize>(chunk.size()));
}, ctx);
}Top-level compiled template:
#include "Engine.h"
int main() {
prebyte::Engine engine;
prebyte::CompiledTemplate tpl = engine.load_compiled_file("template.pbc");
prebyte::RenderContext ctx;
ctx.set("name", "Ada");
std::string output = engine.render(tpl, ctx);
}Embed API notes:
render()is collecting wrapper overrender_to()RenderContextaccepts scalar and structuredValuescompile_file(path)usespathfor both source and logical path defaults- sink chunk lifetime is only callback duration
- stream render may emit partial output before
DiagnosticError - use
load_compiled_file(path)for top-level.pbctemplates - public API does not expose
.pbcserialization yet; current.pbcfiles come from CLI/cache/internal serializer paths
Current implementation is split into focused modules:
cli/command parsingconfig/settings, profiles, rules, variable importsio/input and outputtemplate/lexer/tokenstemplate/parser/recursive-descent parsertemplate/ast/typed AST nodesruntime/renderer, compiled executor, include resolver, value resolution, Lua runtimesupport/diagnostics and spans
Tests live in:
tests/unit/tests/integration/tests/fixtures/
MIT. See LICENSE.