{"id":23328870,"url":"https://github.com/tkrajina/ftmpl","last_synced_at":"2025-06-23T00:33:43.537Z","repository":{"id":57485666,"uuid":"49422472","full_name":"tkrajina/ftmpl","owner":"tkrajina","description":"Fast typesafe templating for golang","archived":false,"fork":false,"pushed_at":"2018-05-14T06:48:12.000Z","size":125,"stargazers_count":63,"open_issues_count":1,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-09T14:50:30.737Z","etag":null,"topics":["golang","html","template","template-engine"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tkrajina.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-01-11T11:43:22.000Z","updated_at":"2025-02-02T14:29:52.000Z","dependencies_parsed_at":"2022-09-11T15:03:05.189Z","dependency_job_id":null,"html_url":"https://github.com/tkrajina/ftmpl","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/tkrajina/ftmpl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkrajina%2Fftmpl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkrajina%2Fftmpl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkrajina%2Fftmpl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkrajina%2Fftmpl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tkrajina","download_url":"https://codeload.github.com/tkrajina/ftmpl/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkrajina%2Fftmpl/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261389858,"owners_count":23151431,"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","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":["golang","html","template","template-engine"],"created_at":"2024-12-20T21:28:54.269Z","updated_at":"2025-06-23T00:33:38.524Z","avatar_url":"https://github.com/tkrajina.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://api.travis-ci.org/tkrajina/ftmpl.svg)](https://travis-ci.org/tkrajina/ftmpl)\n\n# FTMPL: fast (and typesafe) templating for Golang\n\nftmpl is a fast/compiled/typesafe templating \"language\" for golang.\n\n## Why ftmpl?\n\n * It's faster (run `go test -v . -run=TestComparisonWithGolangTemplates` for a comparison). The builtin templates involve a lot of reflection, ftmpl \"compiles\" to simple Golang functions.\n * No need to learn another templating \"language\", ftmpl is just plain Go\n * Type safety (why have a typesafe language and a nontypesafe templating engine?)\n * Base/extended templates\n\n## Installation\n\n    go get github.com/tkrajina/ftmpl\n\n## Templating files and functions\n\nEvery template must be saved in a file with extension `.tmpl`.\nThe template file can be \"compiled\" in Go code (and this is typically done in the build procedure).\n\nIf the file is named `MyTemplate.tmpl` then the `ftmpl` utility will produce a function `TMPLMyTemplate()`.\n\nCompiling files is done my invoking:\n\n    ftmpl -targetgo target_dir/templates_generated.go source_dir\n\nThe resulting code is pretty straightforward, and you will probably need to check (but not edit!) it in case of errors.\n\nFor template examples see [here](example/) (`.tmpl` files).\nFor the \"compiled\" golang code from those examples [see here](example/compiled.go). The resulting code is always formatted using `go fmt`.\n\nIf you prefer using `go generate` add a comment...\n\n    //go:generate ftmpl -targetgo target_dir/templates_generated.go source_dir\n\n...anywhere in your code.\n\nThe prefix `TMPL` in the function name can be changed by a cmd line switch.\n\n### Watch and recompile\n\n`ftmpl` can \"watch\" for changes in your source directory. When any of the `*.tmpl` files change there, it will \"recompile\" all the templates. Just add `-watch` when calling it:\n\n    ftmpl -watch -targetgo target_dir/templates_generated.go source_dir\n\nIf you start multiple `ftmpl` \"watch\" processes, you can stop them all with:\n\n    ftmpl -unwatchall\n\n## Template files\n\nAn example template `ShowTime.tmpl`\n\n    !#import \"time\"\n    \u003chtml\u003e\n        \u003cbody\u003e\n            It's {{s time.Now().String() }}\n        \u003c/body\u003e\n    \u003c/html\u003e\n\nIt will be compiled to the function `func TMPLShowTime()`\n\n## Templates with arguments\n\nIf you want your template function to have arguments:\n\n    !#import \"time\"\n    !#arg t time.Time\n    \u003chtml\u003e\n        \u003cbody\u003e\n            It's {{s t.String() }}\n        \u003c/body\u003e\n    \u003c/html\u003e\n\nNow the compiled function will be `func TMPLShowTime(time.Time)`\n\n## Placeholders\n\nPlaceholders are expressions to be executed and written when calling the template. \nSince ftmpl is a typesafe templating it needs to know of which type is the expression, and based on that to properly format it:\n\nUse:\n\n * `{{s expression }}` for string expressions\n * `{{d expression }}` for number expressions\n * `{{t expression }}` for bool expressions\n * ...\n\nIt's simple, when compiled `{{s expression }}` will end up as `fmt.Sprintf(\"%s\", expression)`, `{{v expression }}` as `fmt.Sprintf(\"%v\", expression)`, etc.\n\n`{{ expression }}` is the same as `{{v expression }}`.\n\nFor more complex formatting, prefix with `%`:\n\n * `{{%5.5f 1.222222222 }}` is equivalent to `fmt.Sprintf(\"%5.5f\", 1.222222222)`\n * `{{% 15f \"padded\" }}` is equivalent to `fmt.Sprintf(\"% 15f\", \"padded\")`\n\n### Escape and unsecape\n\nIf you use `{{s expresssion }}` the result will be escaped using `html.EscapeString()`. If you want to write *the exact same string* (without escaping), use `{{=s expression }}`.\n\n**Important** `{{v expression }}` *is not escaped* even when the resulting expression is a string!\n\n## Code\n\nThere are two ways to write go code in templates:\n\n### Lines starting with \"!\"\n\n    !#arg n int\n    \u003chtml\u003e\n        \u003cbody\u003e\n    !for i := 0; i \u003c n; i++ {\n            i is now {{d i }}\n    !}\n        \u003c/body\u003e\n    \u003c/html\u003e\n\nBasically every line starting with `!` (but not `!#`) will be translated as go code in the resulting function.\n\nIf you (really?) need a non-golang line starting with `!` start it with `!!`.\n\n### Embedded code\n\n    !#arg n int\n    \u003chtml\u003e\n        \u003cbody\u003e\n            {{! for i := 0; i \u003c n; i++ { }}i is now {{d i }}{{! } }}\n        \u003c/body\u003e\n    \u003c/html\u003e\n\n...so by using {{! code }} you embed a go command in that place.\n\nNote that (unlike some other templating languages) the `{{! code }}` notation *is not multiline*. It must contain only one go command/line.\n\nFor `for` and `if` control structures you can ommit `{`, `}` or `} else {` (open and close block of code).\nUse `end` to close and `else` (with `if`) instead:\n\nFor example `if`:\n\n    {{! if n \u003e 0 }}{{d n}} biger than 0{{! end }}\n    {{! if n \u003e 5 }}{{d n}} biger than 5{{! else }}{{d n}} smaller than 5{{! end }}\n\nWith `for`...\n\n    {{! for i := 0; i \u003c n; i++ }} i={{d i }} {{! end }}\n\n...or as `!` lines:\n\n    !for i:=0; i \u003c 5; i++\n        i is now {{d i }}\n    !end\n\n## Directives\n\nLike Go code, special \"directive\" commands like `!#arg` or `!#import` can also be used as lines starting with `!#` or embedded in the code: `{{!#import \"time\" }}`, `{{!#return }}`, ...\n\n## Exit from template\n\nTo prematurely end the execution with an error (depending on an expression), you can use:\n\n    !#errif \u003cexpression\u003e, \"\u003cerror_message\u003e\"\n\n...or...\n\n    !#return\n\n## Base templates and extensions\n\nWhen you need a base website template, and pages \"extending it\":\n\n    !#arg title string\n    \u003chtml\u003e\n        \u003chead\u003e\n            \u003ctitle\u003e{{s title }}\u003c/title\u003e\n    !#include head\n        \u003c/head\u003e\n        \u003cbody\u003e\n    !#include body\n    !#include footer\n        \u003c/body\u003e\n    \u003c/html\u003e\n\nAnd then a typical page will be something like:\n\n    !#extends base\n    !#arg something int\n\n    !#sub head\n    \u003cscript\u003e\n    alert(\"included\")\n    \u003c/script\u003e\n\n    !#sub body\n    \u003ch1\u003eBody!\u003c/h1\u003e\n\nIf the name of the extended template is `MyPage.tmpl` then the resulting template function will have both the arguments from *that template* and arguments from the *base template*: `func TMPLMyPage(title string, something int)`.\n\nTypically you will have many template arguments, so the best way to deal with them is to pack them all into one \"base page structure\" and another struct for every page.\nThe function will then be `func TMPLMyPage(baseParams BasePageParams, pageParams MyPageParams)`\n\nIf a `!#sub` chunk is declared in the extended template, but not in the base -- `ftmpl` will show you a warning during compilation.\n\n## Inserting (sub)templates\n\nSometimes you need some templates just \"copied\" in other templates. You can do that with `!#insert`. For example:\n\n    !#arg a int\n    Will insert something here: {{!#insert \"insertion.tmpl\" }}\n\nThe `insertion.tmpl` template file:\n\n    !#nocompile\n    a={{d a }}\n\nIs equivalent to:\n\n    !#arg a int\n    Will insert something here: a={{d a }}\n\nNote that the `insertion.tmpl` alone is an **invalid** template because it contains a previously undeclared variable `a`. But, when inserted, then the variable `a` is OK. That template makes sense only when included in another template. That's why it has `!#nocompile` in it.\n\nAnother way to do inserts is to just call the generated template function:\n\n    !#arg a int\n    Will insert something here: {{=s TMPLinsertion(a) }}\n\nIn this case `insertion.tmpl` must **not** have `!#nocompile`, and the variable `a` must be declared with `!#arg a int`.\n\n## No compile\n\nSome template files make no sense alone. For example, templates used only in `!#insert` directives or base templates (you never call them alone, but their extended templates).\n\nIf you don't want to compile them into golang functions, add the `!#nocompile` directive.\n\n## Invalid code\n\nSome errors in your templates will be reported immediately:\n\n    $ ftmpl example/invalid/\n    invalid.tmpl    -\u003e example/invalid/invalid.go\n    go fmt example/invalid/invalid.go\n    Cannot format example/invalid/invalid.go: exit status 1\n\n    example/invalid/invalid.go:42:18: unknown escape sequence\n           38:    result.WriteString(`    \u003cbody\u003e\n           39:    `)\n           40:    //invalid.tmpl:         {{s \"\\ \" }}\n           41:    result.WriteString(fmt.Sprintf(`        %s\n    --\u003e    42:    `, _escape( \"\\ \")))\n                                   ^\n           43:    //invalid.tmpl:     \u003c/body\u003e\n           44:    result.WriteString(`    \u003c/body\u003e\n           45:    `)\n           46:    //invalid.tmpl: \u003c/html\u003e\n           47:    result.WriteString(`\u003c/html\u003e\n    exit status 2\n\nFor others, you'll need to debug the Go code (every Go line is preceded with a Go comment with the original .tmpl file and tmpl code).\n\n## Returning errors\n\nEvery template will result in *two* template functions. Both will execute the same code, but:\n\n * Use `func TMPLERRMyTemplate(args) (string, error)` if you expect your template to return errors, use this one. Use `!return \"\", err` to prematurely exit from the template with a proper error value.\n * With `func TMPLMyTemplate(args) string` if the template returns an error, the result is an empty string, and the error will be written in `os.Syserr`.\n\n## Global declarations\n\nIn case you need a global declaration (outside of the template function), it can be done with `!#global`:\n\n    !#global type Argument struct { Aaa string; Bbb int }\n    !#arg arg Argument\n    Aaa is {{s arg.Aaa }}, and Bbb is {{d arg.Bbb }}\n\nThe `Argument` struct will be declared outside the function body, and the function is now `TMPLMyTemplate(Argument)`.\n\n## Special variables\n\n`_template` is defined in the function and contains the current template name.\n\n`_ftmpl` is a `bytes.Buffer` which contains the result of the template. `_w` is an alias to `_ftmpl.WriteString`. It means that `{{! _ftmpl.WriteString(\"something\") }}` is equivalent to `{{! _w(\"something\") }}` which in turn is the same as `{{=s \"something\" }}`.\n\n## Careful with Go code\n\nFtmpl allows you to write go code (instead of templating \"metalanguages\", like other templating engines), but be careful to not write too much of it. \nIdeally, the data should be prepared before the template function, and the only code in the template would be some basic formatting, a couple of `if`s and `for`loops.\n\n\n# License\n\nftmpl is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftkrajina%2Fftmpl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftkrajina%2Fftmpl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftkrajina%2Fftmpl/lists"}