Changelog¶
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
[v1.0.0] - 2026-07-08¶
First stable release. From v1.0.0 the exported API follows semantic versioning:
no exported symbol in the root package or the pkg/ packages changes
incompatibly within the v1 series. This release makes the pre-1.0 breaking
changes that shape that frozen surface, so it carries an unusually large
BREAKING set; after it, compatibility is the rule. See the migration
guide for an
old-to-new API mapping of every change below.
Changed¶
- BREAKING:
context.Contextis now the first argument of every render and load entry point (Render,RenderTo,RenderString,RenderStringTo,RenderValues,RenderToValues,RenderStringValues,LoadTemplate,CompileString, and the newRenderPrepared), of the host callable signatures (ext.Filter/Function/Test), and of the compiledRenderFuncABI. A long render or a host filter doing I/O can now be cancelled; passcontext.Background()when you do not need it. Uncancelled renders are byte-identical to before. - BREAKING: the engine internals moved under
internal/and are no longer importable: the lexer (lex), the tree-walking interpreter (interp), and the compile-to-Go backend (compile). Hosts use the root facade and, for AOT compilation, thequill compilecommand. - BREAKING:
runtime.Valueis now opaque. Its payload fields are unexported; read them withKind(),AsBool(),AsInt(),AsFloat(),AsStr(),AsArray(),AsObject(). Construct values with the existing constructors (runtime.Int,Str, ...). The struct's size and copyability are unchanged, so it remains valid inside the compiledRenderFuncvars map. - BREAKING:
LoadTemplate/CompileStringreturn an opaque*quill.Template(methodsName,BlockNames,HasBlock,HasMacro) rather than the internal interpreter template; render a prepared handle withRenderPrepared. - BREAKING: opaque configuration and result types. A
sandbox.Policyis now built withsandbox.NewPolicy(sandbox.AllowTags(...), sandbox.AllowFilters(...), sandbox.Strict(), ...)instead of a struct literal;check.Type/ObjectType/Signatureandcompiled.Manifest/Fingerprintare opaque and constructor-built. - BREAKING:
extrenames:ext.ExtensionSet->ext.Set,ext.Extension->ext.Bundle,NewExtensionSet->NewSet. TheWithExtensions/WithExtensionoption names are unchanged. - BREAKING: root-package renames and removals:
NewWithArray->NewFromMap;Environment.Displayremoved (useRenderTo); the renderer-internalEnvironmentgetters (StrictVariables,AutoescapeHTML,Policy,SandboxActive,Coverage,TabWidth,Logger,TemplateExists,RawSource) are no longer part of the public surface. - BREAKING:
errorshardening: the wrapped error insideerrors.Securityis unexported (reach it viaUnwrap); anerrors.Error's position is read throughSrc()/Line()/Col()methods instead of fields; a dedicatederrors.SecUnknownTypeclass distinguishes an unregistered host type from a denied-but-known member. - BREAKING:
cover.Collector's instrumentation hooks (Hit,SeedTemplate,SeedMacro) are removed from the public API. They were engine-internal: driven by the interpreter, never by hosts, which consume coverage through the report methods (Report,Summary,TemplateCoverage,Counts,MergeReports). The instrumentation core moved to an internal package. - The
quill coverCI gate now exits with code 2 when total unit coverage is below-fail-under(distinct from a hard error's exit 1, so CI can tell the two apart); the redundant-thresholdalias is removed (use-fail-under). - Error MESSAGE strings are documented as NOT part of the compatibility contract:
classify a failure by the exported
Kind, witherrors.As/errors.Is, or against a sentinel such asloader.ErrNotFound, never by matching text.
Added¶
loader.ErrNotFound, a sentinel that every loader miss wraps;loader.IsNotFoundis nowerrors.Is(err, loader.ErrNotFound)and no longer matches unrelated errors whose text happens to contain "not found".- Native Go fuzz targets for the lexer (
FuzzLex) and parser (FuzzParse).
Fixed¶
- The lexer honored its single-fault contract only by luck. For input such as
{{$the interpolation, statement-head, and block-close scanners emitted an ERROR token but the scan continued, re-emitting the faulting bytes as text, contradicting the documented "a single ERROR token immediately before EOF". The scan now stops at the first fault. Found by the new fuzz targets. compile.ErrNotCompilablewas a mutable global any importer could corrupt; it is now a non-corruptible sentinel (errors.Isclassification unchanged).
v0.3.0 - 2026-07-07¶
Changed¶
- BREAKING: the library packages moved under
pkg/. All fifteen internal and public packages now live under a singlepkg/directory, leaning the repo root from seventeen top-level directories to eight. Update your imports:.../runtime->.../pkg/runtime,.../loader->.../pkg/loader, and likewise forcheck,compile,compiled,cover,errors,ext, andsandbox(the previously-internallex/ast/parse/source/interp/cachemoved fromcore/topkg/). The module root import (github.com/avmnu-sng/quill-template-engine) andgo install .../cmd/quill@latestare unchanged; most code only imports the root package plusruntime/loader.
Fixed¶
- The documentation site rendered filter-pipe examples as
\|instead of|inside table code spans (a mkdocs/python-markdown escaping quirk; GitHub was unaffected). Those cells now use an HTML entity so the pipe renders correctly.
v0.2.0 - 2026-07-06¶
Security¶
- Parser denial-of-service fixed. A chain of nested parentheses made the
arrow-vs-grouping lookahead (
parenIsArrow) rescan all following tokens per(, so parsing was O(n^2): a ~220 KB template drove peak memory to ~1 GB and ~10 s of CPU, and extreme nesting could crash the process with a goroutine stack overflow. All of this is reachable through the publicRenderAPI. The lookahead is now O(1) via a one-pass bracket-match table, and a parser nesting-depth cap turns pathological input into a positioned syntax error instead. The same 100k-paren input now parses in ~37 ms using tens of MB.
Changed¶
- BREAKING: the internal engine packages moved under
core/.ast,cache,lex,parse,source, andinterpare now imported ascore/ast,core/cache,core/lex,core/parse,core/source, andcore/interp. Update the import path if you referenced any of them directly, or if you use thecompile,check, orcoverAPIs whose signatures name*ast.Node(now*core/ast.Node). The documented public packages (runtime,loader,ext,cover,sandbox,check,compile,compiled,errors, andcmd/quill) keep their import paths unchanged, as doesgo get/go install.
Added¶
- Error columns.
errors.Errornow carries a 1-basedCol, set via the newAtPos(src, line, col)method (Atis preserved and fills a zero column). Syntax errors render asname:line:colwhen a column is known. - Editor support. A VS Code extension with a TextMate grammar (
source.quill) lives ineditors/vscode/, and a.gitattributesrule maps template files to the Twig grammar on GitHub. The recommended template file extension is now.quill, which avoids the CodeQL.qlextension that GitHub Linguist claims.
Fixed¶
- Syntax diagnostics locate and name the fault. Errors now include a column;
an unterminated interpolation or block is reported at its opener rather than at
end-of-input; and a delimiter fault names the literal token (
)) instead of the internal label (RPAREN). @tablevel coercion clamps to the platformintrange, avoiding a wrap on 32-bit targets.
v0.1.0 - 2026-07-04¶
Initial public release of Quill, a general-purpose, gradually-typed, fast template engine for Go.
Added¶
- Language and interpreter. A brace-delimited, keyword-led template language
with an
@-sigil statement form and a bare-brace form (pragma bare), pipe filters, arrow functions, and a Pratt-parsed expression language. Statements include@for(withloopmetadata,@else, fusediffilter, andrecursivedescent),@if/@elseif/@else,@set(with list and map destructuring andcapture),@with,@do,@log,@tab, and the region and directive statements. - Composition.
@extends/@blockwithparent(),@macrowith defaults and variadics,@import/@from,@usetrait reuse,@embed, the statement- and function-form@include,@call/caller(), and@provide/@yieldslots. - Gradual type system. A static checker (package
check) that runs between parse and interpret, consumes in-template annotations (@types,@set/@fortargets,@macro/@blockparams and returns, arrow params), infers types where the spec defines it, and applies the gradualanyfallback elsewhere. It rejects ill-typed templates with a positioned error before any byte renders, and narrows unions and nullables throughistests and null-safe access. Annotations never change runtime behavior. - Compile-to-Go backend. A backend (package
compile) that generates Go for the hot path; generated units install throughWithCompiled, while the default path stays on the tree-walking interpreter. - Standard library. A complete built-in catalogue of
snake_casefilters, functions, and tests: string and text-shaping filters, collection and higher-order filters (arrow-drivenmap/filter/reduce/find,select/reject,group_by, thehas some/has everyquantifiers), number and format helpers, and the scalar-kind, comparison, registry-existence, and type tests. - Whitespace control. Three trim modes (hard, line, and a no-trim close),
Jinja-style
trim_blocks/lstrip_blockscleanup applied by default, aspacelessfilter and region, atrimfilter, and a keep-close-newline pragma. - Escaping and the sandbox. No output escaping by default;
WithAutoescapeHTMLfor global HTML escaping, six escape strategies (html,js,css,html_attr,html_attr_relaxed,url) via theescape/efilter and@escaperegion, with safeness tracking. A policy sandbox (sandbox.Policy) restricts permitted tags, filters, functions, methods, and properties, activated globally, per@sandboxregion, or per sandboxed include, with each violation raising a*errors.Security. - Coverage. Native unit and branch coverage of templates (package
cover) viaWithCoverage, with text, LCOV, and HTML reports and aFailUndergate. Coverage is opt-in and never changes rendered bytes. - Streaming.
RenderTo/RenderStringTostream output to anyio.Writerwithout buffering the whole result. - Go interop.
RenderValues/RenderStringValuesaccept ordinary Go values, marshaled throughruntime.FromGo(scalars, slices, deterministically ordered maps, and structs honoring aquill:"name"orjson:"name"tag). - Loaders and extensions. Composable loaders (
Filesystem,FS,Chain,Prefix,Func) and host-supplied filters, functions, and tests through theextpackage (WithExtensions/WithExtension), with a defined shadow order. - Command-line tool. The
quillcommand renders a template with JSON data (quill) and reports coverage (quill cover) with text, LCOV, or HTML output and a-fail-undergate.