dyntpl

package module
v1.1.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2025 License: MIT Imports: 22 Imported by: 2

README

Dynamic templates

Dynamic replacement for quicktemplate template engine.

Retrospective

We've used for a long time quicktemplate for building JSON to interchange data between microservices on high-load project, and we were happy. Ерут we need to be able to change existing templates or add new templates on the fly. Unfortunately quicktemplates doesn't support this and this package was developed as a replacement.

It reproduces many of qtpl features and syntax.

How it works

Working in templates is divided into two phases - parsing and templating. The parsing phase builds from template a tree (like AST) and registers it in templates registry by unique name afterward. This phase isn't intended to be used in highload conditions due to high pressure to cpu/mem. The second phase - templating, against intended to use in highload.

Templating phase required a preparation to pass data to the template. There is a special object Ctx, that collects variables to use in template. Each variable must have three params:

  • unique name
  • data - anything you need to use in template
  • inspector type

Inspector type must be explained.

The biggest problem during development was how to get data from an arbitrary structure without using reflection, since reflect package produces a lot of allocations by design and is extremely slowly in general.

To solve that problem was developed a code-generation framework inspector framework. It provides primitive methods to read data from struct's fields, iterating, etc. without using reflection and makes it fast. Framework generates for each required struct a special type with such methods. It isn't a pure dynamic like reflect provided, but works so fast and makes zero allocations.

You may check example of inspectors in package testobj_ins that represent testing structures in testobj.

Usage

The typical usage of dyntpl looks like this:

package main

import (
	"bytes"

	"github.com/koykov/dyntpl"
	"path/to/inspector_lib_ins"
	"path/to/test_struct"
)

var (
	// Fill up test struct with data.
	data = &test_struct.Data{
		// ...
	}

	// Template code.
	tplData = []byte(`{"id":"{%=data.Id%}","hist":[{%for _,v:=range data.History separator ,%}"{%=v.Datetime%}"{%endfor%}]}`)
)

func init() {
	// Parse the template and register it.
	tree, _ := dyntpl.Parse(tplData, false)
	dyntpl.RegisterTplKey("tplData", tree)
}

func main() {
	// Prepare output buffer
	buf := bytes.Buffer{}
	// Prepare dyntpl context.
	ctx := dyntpl.AcquireCtx()
	ctx.Set("data", data, inspector_lib_ins.DataInspector{})
	// Execute the template and write result to buf.
	_ = dyntpl.Write(&buf, "tplData", ctx)
	// Release context.
	dyntpl.ReleaseCtx(ctx)
	// buf.Bytes() or buf.String() contains the result.
}

Content of init() function should be executed once (or periodically on the fly from some source, eg DB).

Content of main() function is how to use dyntpl in a general way in highload. Of course, byte buffer should take from the pool.

Benchmarks

See bench.md for result of internal benchmarks.

Highly recommend to check *_test.go files in the project, since they contains a lot of typical language constructions that supports this engine.

See versus/dyntpl for comparison benchmarks with quicktemplate and native marshaler/template.

As you can see, dyntpl in ~3-4 times slower than quicktemplates. That is a cost for dynamics. There is no way to write template engine that will be faster than native Go code.

Syntax

Print

The most general syntax construction is printing a variable or structure field:

This is a simple statis variable: {%= var0 %}
This is a field of struct: {%= obj.Parent.Name %}

Construction {%= ... %} prints data as is without any type check, escaping or modification.

There are special escaping modifiers. They should use before =:

  • h - HTML escape.
  • a - HTML attribute escape.
  • j - JSON escape.
  • q - JSON quote.
  • J - JS escape.
  • u - URL encode.
  • l - Link escape.
  • c - CSS escape.
  • f.<num> - float with precision, example: {%f.3= 3.1415 %} will output 3.141.
  • F.<num> - ceil rounded float with precision, example: {%F.3= 3.1415 %} will output 3.142.

Note, that none of these directives doesn't apply by default. It's your responsibility to controls what and where you print.

All directives (except of f and F) supports multipliers, like {%jj= ... %}, {%uu= ... %}, {%uuu= ... %}, ...

For example, the following instruction {%uu= someUrl %} will print a double url-encoded value of someUrl. It may be helpful to build a chain of redirects:

https://domain.com?redirect0={%u= url0 %}{%uu= url1 %}{%uuu= url2 %}

Also, you may combine directives in any combinations ({%Ja= var1 %}, {%jachh= var1 %}, ...). Modifier will apply consecutive and each modifier will take to input the result of the previous modifier.

Bound tags

To apply escaping to some big block containing both text and printing variables, there are special bound tags:

  • {% jsonquote %}...{% endjsonquote %} apply JSON escape to all contents.
  • {% htmlescape %}...{% endhtmlescape %} apply HTML escape.
  • {% urlencode %}...{% endurlencode %} URL encode all text data.

Example:

{"key": "{% jsonquote %}Lorem ipsum "dolor sit amet", {%= var0 %}.{%endjsonquote%}"}
Prefix/suffix

Print construction supports prefix and suffix attributes, it may be handy when you print HTML or XML:

<ul>
{%= var prefix <li> suffix </li> %}
</ul>

Prefix and suffix will print only if var isn't empty. Prefix/suffix has shorthands pfx and sfx.

Print modifiers

Alongside of short modifiers using before = engine supports user defined modifiers. You may use them after printing variable using | and looks lice function call:

Name: {%= obj.Name|default("anonymous") %}Welcome, {%= testNameOf(user, {"foo": "bar", "id": user.Id}, "qwe") %}
                  ^ simple example                     ^ call modifier without variable like simple function call
Chain of modifiers: {%= dateVariable|default("2022-10-04")|formatDate("%y-%m-%d") %}
                                    ^ first modifier      ^ second modifier

Modifiers may collect in chain with variadic length. In that case, each modifier will take to input the result of previous modifier. Each modifier may take an arbitrary count of arguments.

In general, modifier is a Go function with special signature:

type ModFn func(ctx *Ctx, buf *any, val any, args []any) error

, where:

  • ctx - context of the template
  • buf - pointer to return value
  • val - value to pass to input (eg varName|modifier() value of varName)
  • args - list of all arguments

After writing your function, you need to register it using one of the functions:

  • RegisterModFn(name, alias string, mod ModFn)
  • RegisterModFnNS(namespace, name, alias string, mod ModFn)

They are the same, but NS version allows to specify the namespace of the function. In that case, you should specify namespace in modifiers call:

Print using ns: {%= varName|namespaceName::modifier() %}
Conditions

dyntpl supports classic syntax of conditions:

{% if leftVar [==|!=|>|>=|<|<=] rightVar %}
    true branch
{% else %}
    false branch
{% endif %}

Examples: 1, 2, 3.

dyntpl can't handle complicated conditions containing more than one comparison, like:

{% if user.Id == 0 || user.Finance.Balance == 0 %}You're not able to buy!{% endif %}

In the grim darkness of the far future this problem will be solved, but now you can make nested conditions or use conditions helpers - functions with signature:

type CondFn func(ctx *Ctx, args []any) bool

, where you may pass an arbitrary amount of arguments and these functions will return bool to choose the right execution branch. These function are user-defined, like modifiers, and you may write your own and then register it using one of the functions:

func RegisterCondFn(name string, cond CondFn)
func RegisterCondFnNS(namespace, name string, cond CondFn) // namespace version

Then condition helper will be accessible inside templates and you may use it using the name:

{% if helperName(user.Id, user.Finance.Balance) %}You're not able to buy!{% endif %}

As an exception, there are two functions len() and cap() that works the same as built-in native Go functions. The result of their execution may be compared

{% if len(user.Name) > 0 %}...{% endif %}

, whereas user-defined helpers don't allow comparisons.

Ternary operator

dyntpl supports ternary operator for most primitive cases of printing the data. Conditions like this:

{% if x.a == 123 %}
    {%j= y.c %}
{% else %}
    {%j= z.d %}
{% endif %}

may be shortener using ternary operator:

{%j= x.a == 123 ? y.c : z.d %}

Condition helpers also supported:

{%= myConditionHelper(x.a, x.b, "foobar", 3.1415) ? y.c : z.d %}
switch

For multiple conditions, you can use switch statement, examples:

Loops

Dyntpl supports both types of loops:

  • counter loops, like {% for i:=0; i<5; i++ %}...{% endfor %}
  • range-loop, like {% for k, v := range obj.Items %}...{% endfor %}

Edge cases like for k < 2000 {...} or for ; i < 10 ; {...} isn't supported. Also, you can't make an infinite loop by using for {...}.

Separators

When separator between iterations is required, there is a special attribute separator that made special to build JSON output. Example of use:

[
  {% for _, a := range user.History separator , %}
    {
      "id": {%q= a.Id %},
      "date": {%q= a.Date %},
      "comment": {%q= a.Note %}
    }
  {% endfor %}
]

The output that will be produced:

[
  {"id":1, "date": "2020-01-01", "comment": "success"},
  {"id":2, "date": "2020-01-01", "comment": "failed"},
  {"id":3, "date": "2020-01-01", "comment": "rejected"}
]

As you see, commas between 2nd and last elements were added by dyntpl without any additional handling, like ...{% if i>0 %},{% endif %}{% endfor %}. Separator has shorthand variant sep.

loop-else

Separator isn't the last exclusive feature of loops. For loops allows else branch like for conditions:

<select name="type">
  {% for k, v := range user.historyTags %}
    <option value="{%= k %}">{%= v %}</option>
  {% else %}
    <option>N/D</option>
  {% endfor %}
</select>

If loop's source is empty and there aren't data to iterate, then else branch will execute without manual handling. In the example above, if user.historyTags is empty, the empty <option> will display.

Loop breaking

dyntpl supports default instructions break and continue to break loop/iteration, example:

{% for _, v := list %}
  {% if v.ID == 0 %}
    {% continue %}
  {% endif %}
  {% if v.Status == -1 %}
    {% break %}
  {% endif %}
{% endfor %}

These instructions works as intended, but they required condition wrapper and that's bulky. Therefore, dyntpl provide combined break if and continue if that works the same:

{% for _, v := list %}
  {% continue if v.ID == 0 %}
  {% break if v.Status == -1 %}
{% endfor %}

The both examples are equal, but the second is more compact.

Lazy breaks

Imagine the case - you've decided in the middle of iteration that loop requires a break, but the iteration must finish its work the end. Eg template printing some XML element and break inside it will produce an unclosed tag:

<?xml version="1.0" encoding="UTF-8"?>
<users>
  {% for _, u := range users %}
    <user>
        <name>{%= u.Name %}</name>
        {% if u.Blocked == 1 %}
          {% break %} {# <-- unclosed tag reson #}
        {% endif %}
        <balance>{%= u.Balance }</balance>
    </user>
  {% endfor %}
</users>

Obviously, an invalid XML document will build. For that case, dyntpl supports special instruction lazybreak. It breaks the loop but allows current iteration works till the end.

Nested loops break

In native Go to break nested loops you must use one of the instructions:

  • goto <label>
  • break <label>
  • continue <label>

Well, supporting these things is too strange for template engine, isn't it? There is more handy break, provided by php. It allows to specify after a keyword how many loops must be touched by the instruction. dyntpl implements this case and provides instructions:

  • break N
  • lazybreak N
{% for i:=0; i<10; i++ %}
  bar
  {% for j:=0; i<10; i++ %}
    foo
    {% if j == 8 %}
      {% break 2 %}
    {% endif %}
    {% if j == 7 %}
      {% lazybreak 2 %}
    {% endif %}
    {%= j %}
  {% endfor %}
  {%= i %}
{% endfor %}

break/lazybreak N instructions supports conditional versions:

  • break N if
  • lazybreak N if

The example above may be changed to:

    ...
    {% break 2 if j == 8 %}
    {% lazybreak 2 if j == 7 %}
    ...

and you will give the same output.

Include sub-templates

To reuse templates exists instruction include that may be included directly from the template. Just call {% include subTplID %} (example {% include sidebar/right %}) to render and include output of that template inside current template. Sub-template will use the parent template's context to access the data.

Also, you may include sub-templates in bash-style using ..

Extensions

Dyntpl's features may be extended by including extension modules in the project. It's a Go packages that calls dyntpl's modifier registration functions (RegisterModFn, RegisterModFnNS) or condition registration functions (RegisterCondFn, RegisterCondFnNS) or any extending functions from dyntpl API. As result, inside templates will appear new modifiers and other helper functions.

Currently supported modules:

To enable necessary module just import it to the project, eg:

import (
	_ "https://github.com/koykov/dyntpl_vector"
)

and vector's features will be available inside templates.

Feel free to develop your own extensions. Strongly recommend to register new modifiers using namespaces, like this.

Conclusion

Due to two phases (parsing and templating) in using dyntpl it isn't handy to use in simple cases, especially outside highload. The good condition to use it is a highload project and dynamic support requirement. Use dyntpl in proper conditions and it will solve your performance problems.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrUnexpectedEOF = errors.New("unexpected end of file: control structure couldn't be closed")
	ErrUnbalancedCtl = errors.New("unbalanced control structures found")
	ErrUnknownCtl    = errors.New("unknown ctl")

	ErrSenselessCond   = errors.New("comparison of two static args")
	ErrCondHlpNotFound = errors.New("condition helper not found")

	ErrTplNotFound = errors.New("template not found")
	ErrInterrupt   = errors.New("tpl processing interrupted")
	ErrModNoArgs   = errors.New("empty arguments list")
	ErrModPoorArgs = errors.New("arguments list is too small")
	ErrModNoStr    = errors.New("argument is not string or bytes")

	ErrWrongLoopLim  = errors.New("wrong count loop limit argument")
	ErrWrongLoopCond = errors.New("wrong loop condition operation")
	ErrWrongLoopOp   = errors.New("wrong loop operation")
	ErrBreakLoop     = errors.New("break loop")
	ErrLBreakLoop    = errors.New("lazybreak loop")
	ErrContLoop      = errors.New("continue loop")

	ErrUnknownPool = errors.New("unknown pool")
)

Functions

func ConvBool

func ConvBool(val any) (b bool, ok bool)

ConvBool tries to convert value ti boolean.

func ConvBytes

func ConvBytes(val any) (b []byte, ok bool)

ConvBytes tries to convert value to bytes.

func ConvBytesSlice

func ConvBytesSlice(val any) (b [][]byte, ok bool)

ConvBytesSlice tries to convert value to slice of bytes.

func ConvFloat

func ConvFloat(val any) (f float64, ok bool)

ConvFloat tries to convert value to float.

func ConvInt

func ConvInt(val any) (i int64, ok bool)

ConvInt tries to convert value to integer.

func ConvStr

func ConvStr(val any) (s string, ok bool)

ConvStr tries to convert value to string.

func ConvStrSlice

func ConvStrSlice(val any) (s []string, ok bool)

ConvStrSlice tries to convert value to string slice.

func ConvUint

func ConvUint(val any) (u uint64, ok bool)

ConvUint tries to convert value to uint.

func Docgen added in v1.1.9

func Docgen(format DocgenFormat) ([]byte, error)

func EmptyCheck

func EmptyCheck(ctx *Ctx, val any) bool

EmptyCheck tries to apply all known helpers over the val.

First acceptable helper will break next attempts.

func EmptyCheckBool

func EmptyCheckBool(_ *Ctx, val any) bool

EmptyCheckBool checks is val is an empty bool.

func EmptyCheckBytes

func EmptyCheckBytes(_ *Ctx, val any) bool

EmptyCheckBytes checks is val is an empty bytes array.

func EmptyCheckBytesSlice

func EmptyCheckBytesSlice(_ *Ctx, val any) bool

EmptyCheckBytesSlice checks is val is an empty slice of bytes.

func EmptyCheckFloat

func EmptyCheckFloat(_ *Ctx, val any) bool

EmptyCheckFloat checks is val is an empty float number.

func EmptyCheckInt

func EmptyCheckInt(_ *Ctx, val any) bool

EmptyCheckInt checks is val is an empty integer.

func EmptyCheckStr

func EmptyCheckStr(_ *Ctx, val any) bool

EmptyCheckStr checks is val is an empty string.

func EmptyCheckStrSlice

func EmptyCheckStrSlice(_ *Ctx, val any) bool

EmptyCheckStrSlice checks is val is an empty slice of strings.

func EmptyCheckUint

func EmptyCheckUint(_ *Ctx, val any) bool

EmptyCheckUint checks is val is an empty unsigned integer.

func GetInsByVarName

func GetInsByVarName(varName string) (inspector.Inspector, bool)

GetInsByVarName gets inspector by variable name.

func GetInspector

func GetInspector(varName, name string) (ins inspector.Inspector, err error)

GetInspector gets inspector by both variable name or inspector name.

func RegisterPool added in v1.1.6

func RegisterPool(key string, pool Pool) error

RegisterPool adds new internal pool to the registry by given key.

func RegisterTpl

func RegisterTpl(id int, key string, tree *Tree)

RegisterTpl saves template by ID and key in the registry.

You may use to access to the template both ID or key. This function can be used in any time to register new templates or overwrite existing to provide dynamics.

func RegisterTplID

func RegisterTplID(id int, tree *Tree)

RegisterTplID saves template using only ID.

See RegisterTpl().

func RegisterTplKey

func RegisterTplKey(key string, tree *Tree)

RegisterTplKey saves template using only key.

See RegisterTpl().

func ReleaseCtx

func ReleaseCtx(ctx *Ctx)

ReleaseCtx puts object back to default pool.

func Render

func Render(key string, ctx *Ctx) ([]byte, error)

Render template with given key according given context.

See Write(). Recommend to use Write() together with byte buffer pool to avoid redundant allocations.

func RenderByID

func RenderByID(id int, ctx *Ctx) ([]byte, error)

RenderByID renders template with given ID according context.

See WriteByID(). Recommend to use WriteByID() together with byte buffer pool to avoid redundant allocations.

func RenderFallback

func RenderFallback(key, fbKey string, ctx *Ctx) ([]byte, error)

RenderFallback renders template using one of keys: key or fallback key.

See WriteFallback(). Using this func you can handle cases when some objects have custom templates and all other should use default templates. Example: template registry: * tplUser * tplUser-15 user object with id 15 Call of dyntpl.RenderFallback("tplUser-15", "tplUser", ctx) will take template tplUser-15 from registry. In other case, for user #4: call of dyntpl.WriteFallback("tplUser-4", "tplUser", ctx) will take default template tplUser from registry. Recommend to user WriteFallback().

func Write

func Write(w io.Writer, key string, ctx *Ctx) (err error)

Write template with given key to given writer object.

Using this function together with byte buffer pool reduces allocations.

func WriteByID

func WriteByID(w io.Writer, id int, ctx *Ctx) (err error)

WriteByID writes template with given ID to given writer object.

Using this function together with byte buffer pool reduces allocations.

func WriteDocgen added in v1.1.9

func WriteDocgen(w io.Writer, format DocgenFormat) error

func WriteFallback

func WriteFallback(w io.Writer, key, fbKey string, ctx *Ctx) (err error)

WriteFallback writes template using fallback key logic and write result to writer object.

See RenderFallback(). Use this function together with byte buffer pool to reduce allocations.

Types

type CondFn

type CondFn func(ctx *Ctx, args []any) bool

CondFn describes helper func signature.

func GetCondFn

func GetCondFn(name string) CondFn

GetCondFn returns condition helper from the registry.

type CondFnTuple added in v1.1.9

type CondFnTuple struct {
	// contains filtered or unexported fields
}

func RegisterCondFn

func RegisterCondFn(name string, cond CondFn) *CondFnTuple

RegisterCondFn registers new condition helper.

func RegisterCondFnNS added in v1.1.8

func RegisterCondFnNS(namespace, name string, cond CondFn) *CondFnTuple

RegisterCondFnNS registers new condition helper in given namespace.

func (*CondFnTuple) WithDescription added in v1.1.9

func (t *CondFnTuple) WithDescription(desc string) *docgen

func (*CondFnTuple) WithExample added in v1.1.9

func (t *CondFnTuple) WithExample(example string) *docgen

func (*CondFnTuple) WithNote added in v1.1.9

func (t *CondFnTuple) WithNote(note string) *docgen

func (*CondFnTuple) WithParam added in v1.1.9

func (t *CondFnTuple) WithParam(param, desc string) *docgen

func (*CondFnTuple) WithType added in v1.1.9

func (t *CondFnTuple) WithType(typ string) *docgen

type CondOKFn

type CondOKFn func(ctx *Ctx, v *any, ok *bool, args []any)

CondOKFn describes helper func signature.

func GetCondOKFn

func GetCondOKFn(name string) CondOKFn

GetCondOKFn returns condition-OK helper from the registry.

type CondOKTuple added in v1.1.9

type CondOKTuple struct {
	// contains filtered or unexported fields
}

func RegisterCondOKFn

func RegisterCondOKFn(name string, cond CondOKFn) *CondOKTuple

RegisterCondOKFn registers new condition-OK helper.

func RegisterCondOKFnNS added in v1.1.8

func RegisterCondOKFnNS(namespace, name string, cond CondOKFn) *CondOKTuple

RegisterCondOKFnNS registers new condition-OK helper in given namespace.

func (*CondOKTuple) WithDescription added in v1.1.9

func (t *CondOKTuple) WithDescription(desc string) *docgen

func (*CondOKTuple) WithExample added in v1.1.9

func (t *CondOKTuple) WithExample(example string) *docgen

func (*CondOKTuple) WithNote added in v1.1.9

func (t *CondOKTuple) WithNote(note string) *docgen

func (*CondOKTuple) WithParam added in v1.1.9

func (t *CondOKTuple) WithParam(param, desc string) *docgen

func (*CondOKTuple) WithType added in v1.1.9

func (t *CondOKTuple) WithType(typ string) *docgen

type Ctx

type Ctx struct {

	// External buffers to use in modifier and condition helpers.
	BufAcc bytebuf.Accumulative
	// todo remove as unused later
	// DEPRECATED: use BufAcc instead.
	Buf, Buf1, Buf2 bytebuf.Chain

	BufB bool
	BufI int64
	BufU uint64
	BufF float64
	BufT time.Time
	BufX any

	Err error
	// contains filtered or unexported fields
}

Ctx is a context object. Contains list of variables available to inspect. In addition, has buffers to help develop new helpers without allocations.

func AcquireCtx

func AcquireCtx() *Ctx

AcquireCtx gets object from the default context pool.

func NewCtx

func NewCtx() *Ctx

NewCtx makes new context object.

func (*Ctx) AcquireFrom added in v1.1.6

func (ctx *Ctx) AcquireFrom(pool string) (any, error)

AcquireFrom receives new variable from given pool and register it to return batch after finish template processing.

func (*Ctx) BufModOut

func (ctx *Ctx) BufModOut(buf *any, p []byte)

BufModOut buffers mod output bytes.

func (*Ctx) BufModStrOut

func (ctx *Ctx) BufModStrOut(buf *any, s string)

BufModStrOut buffers mod output string.

func (*Ctx) Defer added in v1.1.6

func (ctx *Ctx) Defer(fn func() error)

Defer registers new deferred function.

Function will call after finishing template. todo: find a way how to avoid closure allocation.

func (*Ctx) Get

func (ctx *Ctx) Get(path string) any

Get arbitrary value from the context by path.

See Ctx.get(). Path syntax: <ctxVrName>[.<Field>[.<NestedField0>[....<NestedFieldN>]]] Examples: * user.Bio.Birthday * staticVar

func (*Ctx) GetCounter

func (ctx *Ctx) GetCounter(key string) int

GetCounter gets int counter value.

func (*Ctx) Reset

func (ctx *Ctx) Reset()

Reset the context.

Made to use together with pools.

func (*Ctx) Set

func (ctx *Ctx) Set(key string, val any, ins inspector.Inspector) *Ctx

Set the variable to context. Inspector ins should be corresponded to variable val.

func (*Ctx) SetBytes

func (ctx *Ctx) SetBytes(key string, val []byte) *Ctx

SetBytes sets bytes as static variable.

See Ctx.Set(). This is a special case to improve speed.

func (*Ctx) SetCounter

func (ctx *Ctx) SetCounter(key string, val int) *Ctx

SetCounter sets int counter as static variable.

See Ctx.Set(). This is a special case to support counters.

func (*Ctx) SetStatic

func (ctx *Ctx) SetStatic(key string, val any) *Ctx

SetStatic sets static variable to context.

func (*Ctx) SetString

func (ctx *Ctx) SetString(key, val string) *Ctx

SetString sets string as static variable.

type DocgenFormat added in v1.1.9

type DocgenFormat string
const (
	DocgenFormatMarkdown DocgenFormat = "markdown"
	DocgenFormatHTML     DocgenFormat = "html"
	DocgenFormatJSON     DocgenFormat = "json"
)

type EmptyCheckFn

type EmptyCheckFn func(ctx *Ctx, val any) bool

EmptyCheckFn describes empty check helper func signature.

func GetEmptyCheckFn

func GetEmptyCheckFn(name string) EmptyCheckFn

GetEmptyCheckFn gets empty check helper from the registry.

type EmptyCheckTuple added in v1.1.9

type EmptyCheckTuple struct {
	// contains filtered or unexported fields
}

func RegisterEmptyCheckFn

func RegisterEmptyCheckFn(name string, cond EmptyCheckFn) *EmptyCheckTuple

RegisterEmptyCheckFn registers new empty check helper.

func RegisterEmptyCheckFnNS added in v1.1.8

func RegisterEmptyCheckFnNS(namespace, name string, cond EmptyCheckFn) *EmptyCheckTuple

RegisterEmptyCheckFnNS registers new empty check helper.

func (*EmptyCheckTuple) WithDescription added in v1.1.9

func (t *EmptyCheckTuple) WithDescription(desc string) *docgen

func (*EmptyCheckTuple) WithExample added in v1.1.9

func (t *EmptyCheckTuple) WithExample(example string) *docgen

func (*EmptyCheckTuple) WithNote added in v1.1.9

func (t *EmptyCheckTuple) WithNote(note string) *docgen

func (*EmptyCheckTuple) WithParam added in v1.1.9

func (t *EmptyCheckTuple) WithParam(param, desc string) *docgen

func (*EmptyCheckTuple) WithType added in v1.1.9

func (t *EmptyCheckTuple) WithType(typ string) *docgen

type Global added in v1.1.8

type Global any

Global describes value of global variable.

func GetGlobal added in v1.1.8

func GetGlobal(name string) Global

GetGlobal returns global variable by given name.

type GlobalTuple added in v1.1.9

type GlobalTuple struct {
	// contains filtered or unexported fields
}

func RegisterGlobal added in v1.1.8

func RegisterGlobal(name, alias string, val Global) *GlobalTuple

RegisterGlobal registers new global variable.

Caution! Globals registered after template parsing will take no effect.

func RegisterGlobalNS added in v1.1.8

func RegisterGlobalNS(namespace, name, alias string, val Global) *GlobalTuple

RegisterGlobalNS registers new global variable in given namespace.

func (*GlobalTuple) WithDescription added in v1.1.9

func (t *GlobalTuple) WithDescription(desc string) *docgen

func (*GlobalTuple) WithExample added in v1.1.9

func (t *GlobalTuple) WithExample(example string) *docgen

func (*GlobalTuple) WithNote added in v1.1.9

func (t *GlobalTuple) WithNote(note string) *docgen

func (*GlobalTuple) WithParam added in v1.1.9

func (t *GlobalTuple) WithParam(param, desc string) *docgen

func (*GlobalTuple) WithType added in v1.1.9

func (t *GlobalTuple) WithType(typ string) *docgen

type KV added in v1.1.8

type KV struct {
	K []byte
	V any
}

KV represents key-value pair.

type ModFn

type ModFn func(ctx *Ctx, buf *any, val any, args []any) error

ModFn describes signature of the modifier functions.

Arguments description: * ctx provides access to additional variables and various buffers to reduce allocations. * buf is a storage for final result after finishing modifier work. * val is a left side variable that preceded to call of modifier func, example: {%= val|mod(...) %} * args is a list of all arguments listed on modifier call.

func GetModFn

func GetModFn(name string) ModFn

GetModFn gets modifier from the registry.

type ModFnTuple added in v1.1.9

type ModFnTuple struct {
	// contains filtered or unexported fields
}

func RegisterModFn

func RegisterModFn(name, alias string, mod ModFn) *ModFnTuple

RegisterModFn registers new modifier function.

func RegisterModFnNS added in v1.1.8

func RegisterModFnNS(namespace, name, alias string, mod ModFn) *ModFnTuple

RegisterModFnNS registers new mod function in given namespace.

func (*ModFnTuple) WithDescription added in v1.1.9

func (t *ModFnTuple) WithDescription(desc string) *docgen

func (*ModFnTuple) WithExample added in v1.1.9

func (t *ModFnTuple) WithExample(example string) *docgen

func (*ModFnTuple) WithNote added in v1.1.9

func (t *ModFnTuple) WithNote(note string) *docgen

func (*ModFnTuple) WithParam added in v1.1.9

func (t *ModFnTuple) WithParam(param, desc string) *docgen

func (*ModFnTuple) WithType added in v1.1.9

func (t *ModFnTuple) WithType(typ string) *docgen

type Pool added in v1.1.6

type Pool interface {
	Get() any
	Put(any)
	// Reset cleanups data before putting to the pool.
	Reset(any)
}

Pool represents internal pool. In addition to native sync.Pool requires Reset() method.

type RangeLoop

type RangeLoop struct {
	// contains filtered or unexported fields
}

RangeLoop is a object that injects to inspector to perform range loop execution.

func NewRangeLoop

func NewRangeLoop(node *node, tpl *Tpl, ctx *Ctx, w io.Writer) *RangeLoop

NewRangeLoop makes new RL.

func (*RangeLoop) Iterate

func (rl *RangeLoop) Iterate() inspector.LoopCtl

Iterate performs the iteration.

func (*RangeLoop) RequireKey

func (rl *RangeLoop) RequireKey() bool

RequireKey checks if node requires a key to store in the context.

func (*RangeLoop) Reset

func (rl *RangeLoop) Reset()

Reset clears all data in the list of RL.

func (*RangeLoop) SetKey

func (rl *RangeLoop) SetKey(val any, ins inspector.Inspector)

SetKey saves key to the context.

func (*RangeLoop) SetVal

func (rl *RangeLoop) SetVal(val any, ins inspector.Inspector)

SetVal saves value to the context.

type Tpl

type Tpl struct {
	ID  int
	Key string
	// contains filtered or unexported fields
}

Tpl is a main template object. Template contains only parsed template and evaluation logic. All temporary and intermediate data should be store in context object to make using of templates thread-safe.

type Tree

type Tree struct {
	// contains filtered or unexported fields
}

Tree structure that represents parsed template as list of nodes with childrens.

func Parse

func Parse(tpl []byte, keepFmt bool) (tree *Tree, err error)

Parse initializes parser and parse the template body.

func ParseFile

func ParseFile(fileName string, keepFmt bool) (tree *Tree, err error)

ParseFile initializes parser and parse file contents.

func (*Tree) HumanReadable

func (t *Tree) HumanReadable() []byte

HumanReadable builds human-readable view of the tree (currently in XML format).

type VarInsTuple added in v1.1.9

type VarInsTuple struct {
	// contains filtered or unexported fields
}

func RegisterVarInsPair

func RegisterVarInsPair(varName string, ins inspector.Inspector) *VarInsTuple

RegisterVarInsPair registers new variable-inspector pair.

func (*VarInsTuple) WithDescription added in v1.1.9

func (t *VarInsTuple) WithDescription(desc string) *docgen

func (*VarInsTuple) WithExample added in v1.1.9

func (t *VarInsTuple) WithExample(example string) *docgen

func (*VarInsTuple) WithNote added in v1.1.9

func (t *VarInsTuple) WithNote(note string) *docgen

func (*VarInsTuple) WithParam added in v1.1.9

func (t *VarInsTuple) WithParam(param, desc string) *docgen

func (*VarInsTuple) WithType added in v1.1.9

func (t *VarInsTuple) WithType(typ string) *docgen

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL