{"id":34514099,"url":"https://github.com/koykov/dyntpl","last_synced_at":"2025-12-24T04:18:11.430Z","repository":{"id":54877499,"uuid":"242588871","full_name":"koykov/dyntpl","owner":"koykov","description":"Dynamic template engine.","archived":false,"fork":false,"pushed_at":"2025-12-12T20:52:19.000Z","size":595,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-14T11:13:00.111Z","etag":null,"topics":["dynamic","highload","template"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/koykov.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-02-23T20:51:48.000Z","updated_at":"2025-11-30T19:22:34.000Z","dependencies_parsed_at":"2023-01-31T01:45:44.381Z","dependency_job_id":"985f8854-3466-457c-9c8c-1cee94e093d8","html_url":"https://github.com/koykov/dyntpl","commit_stats":null,"previous_names":["koykov/cbytetpl"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/koykov/dyntpl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koykov%2Fdyntpl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koykov%2Fdyntpl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koykov%2Fdyntpl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koykov%2Fdyntpl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/koykov","download_url":"https://codeload.github.com/koykov/dyntpl/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koykov%2Fdyntpl/sbom","scorecard":{"id":568750,"data":{"date":"2025-08-11","repo":{"name":"github.com/koykov/dyntpl","commit":"f06969cc4ec0431694547e36525f75c5a3f991bd"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.3,"checks":[{"name":"Maintained","score":10,"reason":"15 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/8 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: license.md:0","Info: FSF or OSI recognized license: MIT License: license.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T15:44:11.645Z","repository_id":54877499,"created_at":"2025-08-20T15:44:11.645Z","updated_at":"2025-08-20T15:44:11.645Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27994561,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-24T02:00:07.193Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dynamic","highload","template"],"created_at":"2025-12-24T04:18:10.514Z","updated_at":"2025-12-24T04:18:11.421Z","avatar_url":"https://github.com/koykov.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Dynamic templates\n\nDynamic replacement for [quicktemplate](https://github.com/valyala/quicktemplate) template engine.\n\n## Retrospective\n\nWe've used for a long time quicktemplate for building JSON to interchange data between microservices on high-load project,\nand we were happy. Ерут we need to be able to change existing templates or add new templates on the fly. Unfortunately\nquicktemplates doesn't support this and this package was developed as a replacement.\n\nIt reproduces many of qtpl features and syntax.\n\n## How it works\n\nWorking in templates is divided into two phases - parsing and templating. The parsing phase builds from template a tree\n(like AST) and registers it in templates registry by unique name afterward. This phase isn't intended to be used in highload\nconditions due to high pressure to cpu/mem. The second phase - templating, against intended to use in highload.\n\nTemplating phase required a preparation to pass data to the template. There is a special object [Ctx](ctx.go), that collects\nvariables to use in template. Each variable must have three params:\n* unique name\n* data - anything you need to use in template\n* inspector type\n\nInspector type must be explained.\n\nThe biggest problem during development was how to get data from an arbitrary structure without using reflection,\nsince `reflect` package produces a lot of allocations by design and is extremely slowly in general.\n\nTo solve that problem was developed a code-generation framework [inspector](https://github.com/koykov/inspector) framework.\nIt provides primitive methods to read data from struct's fields, iterating, etc. without using reflection and makes it\nfast. Framework generates for each required struct a special type with such methods. It isn't a pure dynamic like \n`reflect` provided, but works so [fast](https://github.com/koykov/versus/tree/master/inspector2) and makes zero allocations. \n\nYou may check example of inspectors in package [testobj_ins](./testobj_ins) that represent testing structures\nin [testobj](./testobj).\n\n## Usage\n\nThe typical usage of dyntpl looks like this:\n```go\npackage main\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/koykov/dyntpl\"\n\t\"path/to/inspector_lib_ins\"\n\t\"path/to/test_struct\"\n)\n\nvar (\n\t// Fill up test struct with data.\n\tdata = \u0026test_struct.Data{\n\t\t// ...\n\t}\n\n\t// Template code.\n\ttplData = []byte(`{\"id\":\"{%=data.Id%}\",\"hist\":[{%for _,v:=range data.History separator ,%}\"{%=v.Datetime%}\"{%endfor%}]}`)\n)\n\nfunc init() {\n\t// Parse the template and register it.\n\ttree, _ := dyntpl.Parse(tplData, false)\n\tdyntpl.RegisterTplKey(\"tplData\", tree)\n}\n\nfunc main() {\n\t// Prepare output buffer\n\tbuf := bytes.Buffer{}\n\t// Prepare dyntpl context.\n\tctx := dyntpl.AcquireCtx()\n\tctx.Set(\"data\", data, inspector_lib_ins.DataInspector{})\n\t// Execute the template and write result to buf.\n\t_ = dyntpl.Write(\u0026buf, \"tplData\", ctx)\n\t// Release context.\n\tdyntpl.ReleaseCtx(ctx)\n\t// buf.Bytes() or buf.String() contains the result.\n}\n```\nContent of `init()` function should be executed once (or periodically on the fly from some source, eg DB).\n\nContent of `main()` function is how to use dyntpl in a general way in highload. Of course, byte buffer should take from the pool.\n\n## Benchmarks\n\nSee [bench.md](bench.md) for result of internal benchmarks.\n\nHighly recommend to check `*_test.go` files in the project, since they contains a lot of typical language constructions\nthat supports this engine.\n\nSee [versus/dyntpl](https://github.com/koykov/versus/tree/master/dyntpl) for comparison benchmarks with\n[quicktemplate](https://github.com/valyala/quicktemplate) and native marshaler/template.\n\nAs you can see, dyntpl in ~3-4 times slower than [quicktemplates](https://github.com/valyala/quicktemplate).\nThat is a cost for dynamics. There is no way to write template engine that will be faster than native Go code.\n\n## Syntax\n\n### Print\n\nThe most general syntax construction is printing a variable or structure field:\n```\nThis is a simple statis variable: {%= var0 %}\nThis is a field of struct: {%= obj.Parent.Name %}\n```\nConstruction `{%= ... %}` prints data as is without any type check, escaping or modification.\n\nThere are special escaping modifiers. They should use before `=`:\n* `h` - HTML escape.\n* `a` - HTML attribute escape.\n* `j` - JSON escape.\n* `q` - JSON quote.\n* `J` - JS escape.\n* `u` - URL encode.\n* `l` - Link escape.\n* `c` - CSS escape.\n* `f.\u003cnum\u003e` - float with precision, example: `{%f.3= 3.1415 %}` will output `3.141`.\n* `F.\u003cnum\u003e` - ceil rounded float with precision, example: `{%F.3= 3.1415 %}` will output `3.142`.\n\nNote, that none of these directives doesn't apply by default. It's your responsibility to controls what and where you print.\n\nAll directives (except of `f` and `F`) supports multipliers, like `{%jj= ... %}`, `{%uu= ... %}`, `{%uuu= ... %}`, ...\n\nFor example, the following instruction `{%uu= someUrl %}` will print a double url-encoded value of `someUrl`. It may be\nhelpful to build a chain of redirects:\n```\nhttps://domain.com?redirect0={%u= url0 %}{%uu= url1 %}{%uuu= url2 %}\n```\n\nAlso, you may combine directives in any combinations (`{%Ja= var1 %}`, `{%jachh= var1 %}`, ...). Modifier will apply\nconsecutive and each modifier will take to input the result of the previous modifier.\n\n### Bound tags\n\nTo apply escaping to some big block containing both text and printing variables, there are special bound tags:\n* `{% jsonquote %}...{% endjsonquote %}` apply JSON escape to all contents.\n* `{% htmlescape %}...{% endhtmlescape %}` apply HTML escape.\n* `{% urlencode %}...{% endurlencode %}` URL encode all text data.\n\nExample:\n```\n{\"key\": \"{% jsonquote %}Lorem ipsum \"dolor sit amet\", {%= var0 %}.{%endjsonquote%}\"}\n```\n\n#### Prefix/suffix\n\nPrint construction supports prefix and suffix attributes, it may be handy when you print HTML or XML:\n```html\n\u003cul\u003e\n{%= var prefix \u003cli\u003e suffix \u003c/li\u003e %}\n\u003c/ul\u003e\n```\nPrefix and suffix will print only if `var` isn't empty. Prefix/suffix has shorthands `pfx` and `sfx`.\n\n### Print modifiers\n\nAlongside of short modifiers using before `=` engine supports user defined modifiers. You may use them after printing\nvariable using `|` and looks lice function call:\n```\nName: {%= obj.Name|default(\"anonymous\") %}Welcome, {%= testNameOf(user, {\"foo\": \"bar\", \"id\": user.Id}, \"qwe\") %}\n                  ^ simple example                     ^ call modifier without variable like simple function call\nChain of modifiers: {%= dateVariable|default(\"2022-10-04\")|formatDate(\"%y-%m-%d\") %}\n                                    ^ first modifier      ^ second modifier\n```\nModifiers may collect in chain with variadic length. In that case, each modifier will take to input the result of\nprevious modifier. Each modifier may take an arbitrary count of arguments.\n\nIn general, modifier is a Go function with special signature:\n```go\ntype ModFn func(ctx *Ctx, buf *any, val any, args []any) error\n```\n, where:\n* ctx - context of the template\n* buf - pointer to return value\n* val - value to pass to input (eg `varName|modifier()` value of `varName`)\n* args - list of all arguments\n\nAfter writing your function, you need to register it using one of the functions:\n* `RegisterModFn(name, alias string, mod ModFn)`\n* `RegisterModFnNS(namespace, name, alias string, mod ModFn)`\n\nThey are the same, but NS version allows to specify the namespace of the function. In that case, you should specify namespace\nin modifiers call:\n```\nPrint using ns: {%= varName|namespaceName::modifier() %}\n```\n\n### Conditions\n\ndyntpl supports classic syntax of conditions:\n```\n{% if leftVar [==|!=|\u003e|\u003e=|\u003c|\u003c=] rightVar %}\n    true branch\n{% else %}\n    false branch\n{% endif %}\n```\n\nExamples: [1](testdata/parser/condition.tpl), [2](testdata/parser/conditionNested.tpl), [3](testdata/parser/conditionStr.tpl).\n\ndyntpl can't handle complicated conditions containing more than one comparison, like:\n```\n{% if user.Id == 0 || user.Finance.Balance == 0 %}You're not able to buy!{% endif %}\n```\nIn the grim darkness of the far future this problem will be solved, but now you can make nested conditions or use\nconditions helpers - functions with signature:\n```go\ntype CondFn func(ctx *Ctx, args []any) bool\n```\n, where you may pass an arbitrary amount of arguments and these functions will return bool to choose the right execution branch.\nThese function are user-defined, like modifiers, and you may write your own and then register it using one of the functions:\n```go\nfunc RegisterCondFn(name string, cond CondFn)\nfunc RegisterCondFnNS(namespace, name string, cond CondFn) // namespace version\n```\n\nThen condition helper will be accessible inside templates and you may use it using the name:\n```\n{% if helperName(user.Id, user.Finance.Balance) %}You're not able to buy!{% endif %}\n```\n\nAs an exception, there are two functions `len()` and `cap()` that works the same as built-in native Go functions. The result\nof their execution may be compared\n```\n{% if len(user.Name) \u003e 0 %}...{% endif %}\n```\n, whereas user-defined helpers don't allow comparisons.\n\n#### Ternary operator\n\ndyntpl supports ternary operator for most primitive cases of printing the data. Conditions like this:\n```\n{% if x.a == 123 %}\n    {%j= y.c %}\n{% else %}\n    {%j= z.d %}\n{% endif %}\n```\nmay be shortener using ternary operator:\n```\n{%j= x.a == 123 ? y.c : z.d %}\n```\n\nCondition helpers also supported:\n```\n{%= myConditionHelper(x.a, x.b, \"foobar\", 3.1415) ? y.c : z.d %}\n```\n\n#### switch\n\nFor multiple conditions, you can use `switch` statement, examples:\n* [classic switch](testdata/parser/switch.tpl)\n* [no-condition switch](testdata/parser/switchNoCondition.tpl)\n* [no-condition switch with helpers](testdata/parser/switchNoConditionWithHelper.tpl)\n\n### Loops\n\nDyntpl supports both types of loops:\n* counter loops, like `{% for i:=0; i\u003c5; i++ %}...{% endfor %}`\n* range-loop, like `{% for k, v := range obj.Items %}...{% endfor %}`\n\nEdge cases like `for k \u003c 2000 {...}` or `for ; i \u003c 10 ; {...}` isn't supported.\nAlso, you can't make an infinite loop by using `for {...}`.\n\n#### Separators\n\nWhen separator between iterations is required, there is a special attribute `separator` that made special to build JSON\noutput. Example of use:\n```\n[\n  {% for _, a := range user.History separator , %}\n    {\n      \"id\": {%q= a.Id %},\n      \"date\": {%q= a.Date %},\n      \"comment\": {%q= a.Note %}\n    }\n  {% endfor %}\n]\n```\nThe output that will be produced:\n```json\n[\n  {\"id\":1, \"date\": \"2020-01-01\", \"comment\": \"success\"},\n  {\"id\":2, \"date\": \"2020-01-01\", \"comment\": \"failed\"},\n  {\"id\":3, \"date\": \"2020-01-01\", \"comment\": \"rejected\"}\n]\n```\nAs you see, commas between 2nd and last elements were added by dyntpl without any additional handling, like\n`...{% if i\u003e0 %},{% endif %}{% endfor %}`.\nSeparator has shorthand variant `sep`.\n\n#### loop-else\n\nSeparator isn't the last exclusive feature of loops. For loops allows `else` branch like for conditions:\n```\n\u003cselect name=\"type\"\u003e\n  {% for k, v := range user.historyTags %}\n    \u003coption value=\"{%= k %}\"\u003e{%= v %}\u003c/option\u003e\n  {% else %}\n    \u003coption\u003eN/D\u003c/option\u003e\n  {% endfor %}\n\u003c/select\u003e\n```\nIf loop's source is empty and there aren't data to iterate, then `else` branch will execute without manual handling. In the\nexample above, if `user.historyTags` is empty, the empty `\u003coption\u003e` will display.\n\n#### Loop breaking\n\ndyntpl supports default instructions `break` and `continue` to break loop/iteration, example:\n```\n{% for _, v := list %}\n  {% if v.ID == 0 %}\n    {% continue %}\n  {% endif %}\n  {% if v.Status == -1 %}\n    {% break %}\n  {% endif %}\n{% endfor %}\n```\n\nThese instructions works as intended, but they required condition wrapper and that's bulky. Therefore, dyntpl provide\ncombined `break if` and `continue if` that works the same:\n```\n{% for _, v := list %}\n  {% continue if v.ID == 0 %}\n  {% break if v.Status == -1 %}\n{% endfor %}\n```\n\nThe both examples are equal, but the second is more compact.\n\n#### Lazy breaks\n\nImagine the case - you've decided in the middle of iteration that loop requires a break, but the iteration must finish its\nwork the end. Eg template printing some XML element and break inside it will produce an unclosed tag:\n```\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cusers\u003e\n  {% for _, u := range users %}\n    \u003cuser\u003e\n        \u003cname\u003e{%= u.Name %}\u003c/name\u003e\n        {% if u.Blocked == 1 %}\n          {% break %} {# \u003c-- unclosed tag reson #}\n        {% endif %}\n        \u003cbalance\u003e{%= u.Balance }\u003c/balance\u003e\n    \u003c/user\u003e\n  {% endfor %}\n\u003c/users\u003e\n```\n\nObviously, an invalid XML document will build. For that case, dyntpl supports special instruction `lazybreak`. It breaks the\nloop but allows current iteration works till the end.\n\n#### Nested loops break\n\nIn native Go to break nested loops you must use one of the instructions:\n* `goto \u003clabel\u003e`\n* `break \u003clabel\u003e`\n* `continue \u003clabel\u003e`\n\nWell, supporting these things is too strange for template engine, isn't it? There is more handy [break, provided by php](https://www.php.net/manual/en/control-structures.break.php).\nIt allows to specify after a keyword how many loops must be touched by the instruction. dyntpl implements this case and provides\ninstructions:\n* `break N`\n* `lazybreak N`\n\n```\n{% for i:=0; i\u003c10; i++ %}\n  bar\n  {% for j:=0; i\u003c10; i++ %}\n    foo\n    {% if j == 8 %}\n      {% break 2 %}\n    {% endif %}\n    {% if j == 7 %}\n      {% lazybreak 2 %}\n    {% endif %}\n    {%= j %}\n  {% endfor %}\n  {%= i %}\n{% endfor %}\n```\n\n`break/lazybreak N` instructions supports conditional versions:\n* `break N if`\n* `lazybreak N if`\n\nThe example above may be changed to:\n```\n    ...\n    {% break 2 if j == 8 %}\n    {% lazybreak 2 if j == 7 %}\n    ...\n```\nand you will give the same output.\n\n### Include sub-templates\n\nTo reuse templates exists instruction `include` that may be included directly from the template.\nJust call `{% include subTplID %}` (example `{% include sidebar/right %}`) to render and include output of that template\ninside current template.\nSub-template will use the parent template's context to access the data.\n\nAlso, you may include sub-templates in bash-style using `.`.\n\n### Extensions\n\nDyntpl's features may be extended by including extension modules in the project. It's a Go packages that calls\ndyntpl's modifier registration functions (`RegisterModFn`, `RegisterModFnNS`) or condition registration functions\n(`RegisterCondFn`, `RegisterCondFnNS`) or any extending functions from dyntpl API. As result, inside templates will\nappear new modifiers and other helper functions.\n\nCurrently supported modules:\n* [dyntpl_vector](https://github.com/koykov/dyntpl_vector) provide support of vector parsers inside the templates.\n* [dyntpl_i18n](https://github.com/koykov/dyntpl_i18n) provide support of i18n features.\n\nTo enable necessary module just import it to the project, eg:\n```go\nimport (\n\t_ \"https://github.com/koykov/dyntpl_vector\"\n)\n```\nand vector's [features](https://github.com/koykov/dyntpl_vector) will be available inside templates. \n\nFeel free to develop your own extensions. Strongly recommend to register new modifiers using namespaces, like\n[this](https://github.com/koykov/dyntpl_vector/blob/master/init.go#L12).\n\n### Conclusion\n\nDue to two phases (parsing and templating) in using dyntpl it isn't handy to use in simple cases, especially outside\nhighload. The good condition to use it is a highload project and dynamic support requirement. Use dyntpl in proper\nconditions and it will solve your performance problems.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoykov%2Fdyntpl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkoykov%2Fdyntpl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoykov%2Fdyntpl/lists"}