Giter VIP home page Giter VIP logo

quicktemplate's Introduction

Build Status GoDoc Go Report Card

quicktemplate

A fast, powerful, yet easy to use template engine for Go. Inspired by the Mako templates philosophy.

Features

  • Extremely fast. Templates are converted into Go code and then compiled.
  • Quicktemplate syntax is very close to Go - there is no need to learn yet another template language before starting to use quicktemplate.
  • Almost all bugs are caught during template compilation, so production suffers less from template-related bugs.
  • Easy to use. See quickstart and examples for details.
  • Powerful. Arbitrary Go code may be embedded into and mixed with templates. Be careful with this power - do not query the database and/or external resources from templates unless you miss the PHP way in Go :) This power is mostly for arbitrary data transformations.
  • Easy to use template inheritance powered by Go interfaces. See this example for details.
  • Templates are compiled into a single binary, so there is no need to copy template files to the server.

Drawbacks

  • Templates cannot be updated on the fly on the server, since they are compiled into a single binary. Take a look at fasttemplate if you need a fast template engine for simple dynamically updated templates. There are ways to dynamically update the templates during development.

Performance comparison with html/template

Quicktemplate is more than 20x faster than html/template. The following simple template is used in the benchmark:

Benchmark results:

$ go test -bench='Benchmark(Quick|HTML)Template' -benchmem github.com/valyala/quicktemplate/tests
BenchmarkQuickTemplate1-4                 	10000000	       120 ns/op	       0 B/op	       0 allocs/op
BenchmarkQuickTemplate10-4                	 3000000	       441 ns/op	       0 B/op	       0 allocs/op
BenchmarkQuickTemplate100-4               	  300000	      3945 ns/op	       0 B/op	       0 allocs/op
BenchmarkHTMLTemplate1-4                  	  500000	      2501 ns/op	     752 B/op	      23 allocs/op
BenchmarkHTMLTemplate10-4                 	  100000	     12442 ns/op	    3521 B/op	     117 allocs/op
BenchmarkHTMLTemplate100-4                	   10000	    123392 ns/op	   34498 B/op	    1152 allocs/op

goTemplateBenchmark compares QuickTemplate with numerous Go templating packages. QuickTemplate performs favorably.

Security

  • All template placeholders are HTML-escaped by default.
  • Template placeholders for JSON strings prevent from </script>-based XSS attacks:
{% func FailedXSS() %}
<script>
    var s = {%q= "</script><script>alert('you pwned!')" %};
</script>
{% endfunc %}

Examples

See examples.

Quick start

First of all, install the quicktemplate package and quicktemplate compiler (qtc):

go get -u github.com/valyala/quicktemplate
go get -u github.com/valyala/quicktemplate/qtc

If you using go generate, you just need put following into your main.go

Important: please specify your own folder (-dir) to generate template file

//go:generate go get -u github.com/valyala/quicktemplate/qtc
//go:generate qtc -dir=app/views  

Let's start with a minimal template example:

All text outside function templates is treated as comments,
i.e. it is just ignored by quicktemplate compiler (`qtc`). It is for humans.

Hello is a simple template function.
{% func Hello(name string) %}
	Hello, {%s name %}!
{% endfunc %}

Save this file into a templates folder under the name hello.qtpl and run qtc inside this folder.

If everything went OK, hello.qtpl.go file should appear in the templates folder. This file contains Go code for hello.qtpl. Let's use it!

Create a file main.go outside templates folder and put the following code there:

package main

import (
	"fmt"

	"./templates"
)

func main() {
	fmt.Printf("%s\n", templates.Hello("Foo"))
	fmt.Printf("%s\n", templates.Hello("Bar"))
}

Then issue go run. If everything went OK, you'll see something like this:


	Hello, Foo!


	Hello, Bar!

Let's create more a complex template which calls other template functions, contains loops, conditions, breaks, continues and returns. Put the following template into templates/greetings.qtpl:


Greetings greets up to 42 names.
It also greets John differently comparing to others.
{% func Greetings(names []string) %}
	{% if len(names) == 0 %}
		Nobody to greet :(
		{% return %}
	{% endif %}

	{% for i, name := range names %}
		{% if i == 42 %}
			I'm tired to greet so many people...
			{% break %}
		{% elseif name == "John" %}
			{%= sayHi("Mr. " + name) %}
			{% continue %}
		{% else %}
			{%= Hello(name) %}
		{% endif %}
	{% endfor %}
{% endfunc %}

sayHi is unexported, since it starts with lowercase letter.
{% func sayHi(name string) %}
	Hi, {%s name %}
{% endfunc %}

Note that every template file may contain an arbitrary number
of template functions. For instance, this file contains Greetings and sayHi
functions.

Run qtc inside templates folder. Now the folder should contain two files with Go code: hello.qtpl.go and greetings.qtpl.go. These files form a single templates Go package. Template functions and other template stuff is shared between template files located in the same folder. So Hello template function may be used inside greetings.qtpl while it is defined in hello.qtpl. Moreover, the folder may contain ordinary Go files, so its contents may be used inside templates and vice versa. The package name inside template files may be overriden with {% package packageName %}.

Now put the following code into main.go:

package main

import (
	"bytes"
	"fmt"

	"./templates"
)

func main() {
	names := []string{"Kate", "Go", "John", "Brad"}

	// qtc creates Write* function for each template function.
	// Such functions accept io.Writer as first parameter:
	var buf bytes.Buffer
	templates.WriteGreetings(&buf, names)

	fmt.Printf("buf=\n%s", buf.Bytes())
}

Careful readers may notice different output tags were used in these templates: {%s name %} and {%= Hello(name) %}. What's the difference? The {%s x %} is used for printing HTML-safe strings, while {%= F() %} is used for embedding template function calls. Quicktemplate supports also other output tags:

  • {%d int %} and {%dl int64 %} {%dul uint64 %} for integers.
  • {%f float %} for float64. Floating point precision may be set via {%f.precision float %}. For example, {%f.2 1.2345 %} outputs 1.23.
  • {%z bytes %} for byte slices.
  • {%q str %} and {%qz bytes %} for JSON-compatible quoted strings.
  • {%j str %} and {%jz bytes %} for embedding str into a JSON string. Unlike {%q str %}, it doesn't quote the string.
  • {%u str %} and {%uz bytes %} for URL encoding the given str.
  • {%v anything %} is equivalent to %v in printf-like functions.

All the output tags except {%= F() %} produce HTML-safe output, i.e. they escape < to &lt;, > to &gt;, etc. If you don't want HTML-safe output, then just put = after the tag. For example: {%s= "<h1>This h1 won't be escaped</h1>" %}.

As you may notice {%= F() %} and {%s= F() %} produce the same output for {% func F() %}. But the first one is optimized for speed - it avoids memory allocations and copies. It is therefore recommended to stick to it when embedding template function calls.

Additionally, the following extensions are supported for {%= F() %}:

  • {%=h F() %} produces html-escaped output.
  • {%=u F() %} produces URL-encoded output.
  • {%=q F() %} produces quoted json string.
  • {%=j F() %} produces json string without quotes.
  • {%=uh F() %} produces html-safe URL-encoded output.
  • {%=qh F() %} produces html-safe quoted json string.
  • {%=jh F() %} produces html-safe json string without quotes.

All output tags except {%= F() %} family may contain arbitrary valid Go expressions instead of just an identifier. For example:

Import fmt for fmt.Sprintf()
{% import "fmt" %}

FmtFunc uses fmt.Sprintf() inside output tag
{% func FmtFunc(s string) %}
	{%s fmt.Sprintf("FmtFunc accepted %q string", s) %}
{% endfunc %}

There are other useful tags supported by quicktemplate:

  • {% comment %}

    {% comment %}
        This is a comment. It won't trap into the output.
        It may contain {% arbitrary tags %}. They are just ignored.
    {% endcomment %}
    
  • {% plain %}

    {% plain %}
        Tags will {% trap into %} the output {% unmodified %}.
        Plain block may contain invalid and {% incomplete tags.
    {% endplain %}
    
  • {% collapsespace %}

    {% collapsespace %}
        <div>
            <div>space between lines</div>
               and {%s "tags" %}
             <div>is collapsed into a single space
             unless{% newline %}or{% space %}is used</div>
        </div>
    {% endcollapsespace %}
    

    Is converted into:

    <div> <div>space between lines</div> and tags <div>is collapsed into a single space unless
    or is used</div> </div>
    
  • {% stripspace %}

    {% stripspace %}
         <div>
             <div>space between lines</div>
                and {%s " tags" %}
             <div>is removed unless{% newline %}or{% space %}is used</div>
         </div>
    {% endstripspace %}
    

    Is converted into:

    <div><div>space between lines</div>and tags<div>is removed unless
    or is used</div></div>
    
  • It is possible removing whitespace before and after the tag by adding - after {% or prepending %} with -. For example:

    var sum int
    {%- for i := 1; i <= 3; i++ -%}
    sum += {%d i %}
    {%- endfor -%}
    return sum
    

    Is converted into:

    var sum int
    sum += 1
    sum += 2
    sum += 3
    return sum
    
  • {% switch %}, {% case %} and {% default %}:

    1 + 1 =
    {% switch 1+1 %}
    {% case 2 %}
    2?
    {% case 42 %}
    42!
    {% default %}
        I don't know :(
    {% endswitch %}
    
  • {% code %}:

    {% code
    // arbitrary Go code may be embedded here!
    type FooArg struct {
        Name string
        Age int
    }
    %}
    
  • {% package %}:

    Override default package name with the custom name
    {% package customPackageName %}
    
  • {% import %}:

    Import external packages.
    {% import "foo/bar" %}
    {% import (
        "foo"
        bar "baz/baa"
    ) %}
    
  • {% cat "/path/to/file" %}:

    Cat emits the given file contents as a plaintext:
    {% func passwords() %}
        /etc/passwd contents:
        {% cat "/etc/passwd" %}
    {% endfunc %}
    
  • {% interface %}:

    Interfaces allow powerful templates' inheritance
    {%
    interface Page {
        Title()
        Body(s string, n int)
        Footer()
    }
    %}
    
    PrintPage prints Page
    {% func PrintPage(p Page) %}
        <html>
            <head><title>{%= p.Title() %}</title></head>
            <body>
                <div>{%= p.Body("foo", 42) %}</div>
                <div>{%= p.Footer() %}</div>
            </body>
        </html>
    {% endfunc %}
    
    Base page implementation
    {% code
    type BasePage struct {
        TitleStr string
        FooterStr string
    }
    %}
    {% func (bp *BasePage) Title() %}{%s bp.TitleStr %}{% endfunc %}
    {% func (bp *BasePage) Body(s string, n int) %}
        <b>s={%q s %}, n={%d n %}</b>
    {% endfunc %}
    {% func (bp *BasePage) Footer() %}{%s bp.FooterStr %}{% endfunc %}
    
    Main page implementation
    {% code
    type MainPage struct {
        // inherit from BasePage
        BasePage
    
        // real body for main page
        BodyStr string
    }
    %}
    
    Override only Body
    Title and Footer are used from BasePage.
    {% func (mp *MainPage) Body(s string, n int) %}
        <div>
            main body: {%s mp.BodyStr %}
        </div>
        <div>
            base body: {%= mp.BasePage.Body(s, n) %}
        </div>
    {% endfunc %}
    

    See basicserver example for more details.

Performance optimization tips

  • Prefer calling WriteFoo instead of Foo when generating template output for {% func Foo() %}. This avoids unnesessary memory allocation and a copy for a string returned from Foo().

  • Prefer {%= Foo() %} instead of {%s= Foo() %} when embedding a function template {% func Foo() %}. Though both approaches generate identical output, the first approach is optimized for speed.

  • Prefer using existing output tags instead of passing fmt.Sprintf to {%s %}. For instance, use {%d num %} instead of {%s fmt.Sprintf("%d", num) %}, because the first approach is optimized for speed.

  • Prefer using specific output tags instead of generic output tag {%v %}. For example, use {%s str %} instead of {%v str %}, since specific output tags are optimized for speed.

  • Prefer creating custom function templates instead of composing complex strings by hands before passing them to {%s %}. For instance, the first approach is slower than the second one:

    {% func Foo(n int) %}
        {% code
        // construct complex string
        complexStr := ""
        for i := 0; i < n; i++ {
            complexStr += fmt.Sprintf("num %d,", i)
        }
        %}
        complex string = {%s= complexStr %}
    {% endfunc %}
    
    {% func Foo(n int) %}
        complex string = {%= complexStr(n) %}
    {% endfunc %}
    
    // Wrap complexStr func into stripspace for stripping unnesessary space
    // between tags and lines.
    {% stripspace %}
    {% func complexStr(n int) %}
        {% for i := 0; i < n; i++ %}
            num{% space %}{%d i %}{% newline %}
        {% endfor %}
    {% endfunc %}
    {% endstripspace %}
    
  • Make sure that the io.Writer passed to Write* functions is buffered. This will minimize the number of write syscalls, which may be quite expensive.

    Note: There is no need to wrap fasthttp.RequestCtx into bufio.Writer, since it is already buffered.

  • Profile your programs for memory allocations and fix the most demanding functions based on the output of go tool pprof --alloc_objects.

Use cases

While the main quicktemplate purpose is generating HTML, it may be used for generating other data too. For example, JSON and XML marshalling may be easily implemented with quicktemplate:

{% code
type MarshalRow struct {
	Msg string
	N int
}

type MarshalData struct {
	Foo int
	Bar string
	Rows []MarshalRow
}
%}

// JSON marshaling
{% stripspace %}
{% func (d *MarshalData) JSON() %}
{
	"Foo": {%d d.Foo %},
	"Bar": {%q= d.Bar %},
	"Rows":[
		{% for i, r := range d.Rows %}
			{
				"Msg": {%q= r.Msg %},
				"N": {%d r.N %}
			}
			{% if i + 1 < len(d.Rows) %},{% endif %}
		{% endfor %}
	]
}
{% endfunc %}
{% endstripspace %}

// XML marshalling
{% stripspace %}
{% func (d *MarshalData) XML() %}
<MarshalData>
	<Foo>{%d d.Foo %}</Foo>
	<Bar>{%s d.Bar %}</Bar>
	<Rows>
	{% for _, r := range d.Rows %}
		<Row>
			<Msg>{%s r.Msg %}</Msg>
			<N>{%d r.N %}</N>
		</Row>
	{% endfor %}
	</Rows>
</MarshalData>
{% endfunc %}
{% endstripspace %}

Usually, marshalling built with quicktemplate works faster than the marshalling implemented via standard encoding/json and encoding/xml. See the corresponding benchmark results:

go test -bench=Marshal -benchmem github.com/valyala/quicktemplate/tests
BenchmarkMarshalJSONStd1-4                	 3000000	       480 ns/op	       8 B/op	       1 allocs/op
BenchmarkMarshalJSONStd10-4               	 1000000	      1842 ns/op	       8 B/op	       1 allocs/op
BenchmarkMarshalJSONStd100-4              	  100000	     15820 ns/op	       8 B/op	       1 allocs/op
BenchmarkMarshalJSONStd1000-4             	   10000	    159327 ns/op	      59 B/op	       1 allocs/op
BenchmarkMarshalJSONQuickTemplate1-4      	10000000	       162 ns/op	       0 B/op	       0 allocs/op
BenchmarkMarshalJSONQuickTemplate10-4     	 2000000	       748 ns/op	       0 B/op	       0 allocs/op
BenchmarkMarshalJSONQuickTemplate100-4    	  200000	      6572 ns/op	       0 B/op	       0 allocs/op
BenchmarkMarshalJSONQuickTemplate1000-4   	   20000	     66784 ns/op	      29 B/op	       0 allocs/op
BenchmarkMarshalXMLStd1-4                 	 1000000	      1652 ns/op	       2 B/op	       2 allocs/op
BenchmarkMarshalXMLStd10-4                	  200000	      7533 ns/op	      11 B/op	      11 allocs/op
BenchmarkMarshalXMLStd100-4               	   20000	     65763 ns/op	     195 B/op	     101 allocs/op
BenchmarkMarshalXMLStd1000-4              	    2000	    663373 ns/op	    3522 B/op	    1002 allocs/op
BenchmarkMarshalXMLQuickTemplate1-4       	10000000	       145 ns/op	       0 B/op	       0 allocs/op
BenchmarkMarshalXMLQuickTemplate10-4      	 3000000	       597 ns/op	       0 B/op	       0 allocs/op
BenchmarkMarshalXMLQuickTemplate100-4     	  300000	      5833 ns/op	       0 B/op	       0 allocs/op
BenchmarkMarshalXMLQuickTemplate1000-4    	   30000	     53000 ns/op	      32 B/op	       0 allocs/op

FAQ

  • Why is the quicktemplate syntax incompatible with html/template?

    Because html/template syntax isn't expressive enough for quicktemplate.

  • What's the difference between quicktemplate and ego?

    Ego is similar to quicktemplate in the sense it converts templates into Go code. But it misses the following stuff, which makes quicktemplate so powerful and easy to use:

    • Defining multiple function templates in a single template file.
    • Embedding function templates inside other function templates.
    • Template interfaces, inheritance and overriding. See this example for details.
    • Top-level comments outside function templates.
    • Template packages.
    • Combining arbitrary Go files with template files in template packages.
    • Performance optimizations.
  • What's the difference between quicktemplate and gorazor?

    Gorazor is similar to quicktemplate in the sense it converts templates into Go code. But it misses the following useful features:

    • Clear syntax instead of hard-to-understand magic stuff related to template arguments, template inheritance and embedding function templates into other templates.
  • Is there a syntax highlighting for qtpl files?

    Yes - see this issue for details. If you are using JetBrains products (syntax highlighting and autocomplete):

  • I didn't find an answer for my question here.

    Try exploring these questions.

quicktemplate's People

Contributors

calinou avatar d3ah avatar dadabird avatar danielparks avatar escholtz avatar ilius avatar netroby avatar olduh29 avatar phuslu avatar quasilyte avatar skariel avatar stereosteve avatar turutcrane avatar valyala avatar wuvist avatar zikaeroh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

quicktemplate's Issues

Exposing `package main` structs/variables to a template

I'm migrating all my https://github.com/flosch/pongo2 Django-based templates to QuickTemplate. It's really amazing how much more performance this gives; thanks so much for the effort!

There's actually one issue I'm not sure how to approach. It's about struct-definitions (or even instantiated variables too) that I would like to access in the package. I'm aware that the ./templates go generate-compiled files are it's own package and can't access the main package, yet my current structure holds for example all database Entities as structs in the package main domain, and not a package I could import into the template.

So I have this layout.qptl

{% interface
Page {
    Title()
    Menu()
    Content()
    Stylesheets()
    Javascripts()
}
%}

{% func PageTemplate(p Page) %}{% stripspace %}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>{%= p.Title() %}</title>
        <link rel="stylesheet" href="/c/m.css">
        {%= p.Stylesheets() %}
    </head>
    <body>{% stripspace %}
        {%= p.Menu() %}
        {%= p.Content() %}
        {%= p.Javascripts() %}
    {% endstripspace %}</body>
</html>
{% endstripspace %}{% endfunc %}

{% code
type BasePage struct {
    IsLoggedIn bool
    LocaleID int
}
%}
{% func (p *BasePage) Title() %}{% endfunc %}
{% func (p *BasePage) Menu() %}{% stripspace %}
<header>
    <a href="/">{%= Translate(p.LocaleID, "Home") %}</a>
    <a href="catalog">{%= Translate(p.LocaleID, "Catalog") %}</a>
    {% if !p.IsLoggedIn %}<a href="login">{%= Translate(p.LocaleID, "Login") %}</a>{% endif %}
    {% if p.IsLoggedIn %}<a href="logout">{%= Translate(p.LocaleID, "Logout") %}</a>{% endif %}
    {% if !p.IsLoggedIn %}<a href="forgot-password">{%= Translate(p.LocaleID, "Forgot password") %}</a>{% endif %}
    {% if p.IsLoggedIn %}<a href="_28c4">{%= Translate(p.LocaleID, "Admin") %}</a>{% endif %}
</header>
{% endstripspace %}{% endfunc %}
{% func (p *BasePage) Content() %}{% endfunc %}
{% func (p *BasePage) Stylesheets() %}{% endfunc %}
{% func (p *BasePage) Javascripts() %}{% endfunc %}

And this login template:

{% import "gopkg.in/kataras/iris.v6" %}
{% import "renkemadd.com/espal/Server/validator" %}

{% code
type LoginPage struct {
    BasePage
    CTX *iris.Context
    Form map[string]validator.FormFieldType
    FormErrors []string
}
%}

{% func (p *LoginPage) Stylesheets() %}<link rel="stylesheet" href="/c/simple-box.css">{% endfunc%}
{% func (p *LoginPage) Title() %}{%= Translate(p.LocaleID, "Login") %}{% endfunc%}
{% func (p *LoginPage) Content() %}{% stripspace %}
<div class="simpleBox">
    {%= FormErrors(p.FormErrors) %}
    <h1>{%= Translate(p.LocaleID, "Login") %}</h1>
    <form method="post">
        {%s= FormHiddenField(p.Form["_uname"]) %}
        {%s= FormHiddenField(p.Form["_t"]) %}
        {%s= FormEmailField(p.Form["email"]) %}<br>
        {%s= FormPasswordField(p.Form["password"]) %}<br>
        <input type="submit" value="{%= Translate(p.LocaleID, "Login") %}">
    </form>
    <p><a href="/forgot-password"><small>{%= Translate(p.LocaleID, "Forgot your password?") %}</small></a></p>
    <p><a href="/register-account"><small>{%= Translate(p.LocaleID, "No account yet?") %}</small></a></p>
</div>
{% endstripspace %}{% endfunc%}

There are actually two things I miss that I would like to achieve:

1.) Accessing package main structs
2.) Accessing instantiated variables

So for 1.):

{% import "gopkg.in/kataras/iris.v6" %}
{% import "renkemadd.com/espal/Server/validator" %}

{% code
type LoginPage struct {
    BasePage
    CTX *iris.Context
    Form map[string]validator.FormFieldType
    FormErrors []string
    User User <<<<<<< THIS would be the database User struct in `package main`, but can't import
}
%}

So for 2.):

In the layout.qtpl you can see a part in the menu:

{% if p.IsLoggedIn %}<a href="_28c4">{%= Translate(p.LocaleID, "Admin") %}</a>{% endif %}

_28c4 is actually a global variable I used in the Django templates that would be the AdminURL that deployed systems can use for dynamically defining their admin url for extra safety:

_AdminURL = mustGetStringConfig("urls.admin") // YAML fetch

I could massively move all the DB entities and certain variables to sub-packages in my project, but it would imply that I have to mass rename all struct-calls, also bloating a lot of code (&UserAddress would become something like &dbentities.UserAddress and I wouldn't really favor that).

I hope I'm not being too vague :). Please ask if things aren't clear enough.

Thank you!

A way to get template as []byte?

By now i can only see a way to retrive generated template as a string (or write it directly to io.Writer). It would be nice to also have a possibility to retrive it as []byte, without overhead of []byte-string-[]byte conversion.

it leaks memory

p := &templates.MainPage{
User: user,
Users: users,
Flags: flags,
Levels: levels,
}
templates.WritePageTemplate(w, p)

Add feature to strip HTML comments

Hi,

it would be very nice to have something like "{% stripcomments %}..." to strip all static HTML comments from templates.

{% stripcomments %}
<!-- Icons -->
<link rel="shortcut icon" href="assets/img/favicons/favicon.png">
{% endstripcomments %}

would become

<link rel="shortcut icon" href="assets/img/favicons/favicon.png">

Error

I am following instructions in the Quickstart. I do everything as it is written. When I write go run I get following errors.

C:\Go\pkg\tool\windows_amd64\link.exe: cannot open file C:\Go\pkg\windows_amd64/github.com/valyala/quicktemplate.a: open C:\Go\pkg\windows_amd64/github.com/valyala/quicktemplate.a: The system cannot find the path specified.

Hash in files name but not in development

Coming from javascript frontend world I'm wondering how to use hash when importing or referencing files in templates.

I wanna develop with this code:

{% func Page(p Page) %}
  <html>
  <head>
    <link rel="stylesheet" href="/static/css/base.css"/>
  </head>
  <body>
    {%= p.Body() %}
  </body>
  </html>
{% endfunc %}

and deploy with this code:

{% func Page(p Page) %}
  <html>
  <head>
    <link rel="stylesheet" href="/static/css/base-4ab5f2de4.css"/>
  </head>
  <body>
    {%= p.Body() %}
  </body>
  </html>
{% endfunc %}
  • before: base.css
  • after: base-4ab5f2de4.css

Is it possible today?

  • What do you think about using an ENV VAR at compile time to switch behaviour (DEVELOP/PRODUCTION) and calculate every hash with a function?

  • What do you use today for cache invalidation?

This is useful also for adding CDN addresses in future, right?

Something similar (abandoned): https://github.com/defrankland/hasherator.

Optional block end with simply 'end'

It would be nice to have the syntactic option to end a block with a simple end. It saves typing for those who prefer to be succinct. Please consider. Thanks.

  • Go, JavaScript, Java and many others end all blocks with a single character '}' (very succinct)
  • Python infers the end of a block with just whitespace (too succinct, IMHO)
  • Ruby ends all blocks with 'end' (a nice balance)

I hope one day, a JetBrains plugin can be created for this (jealous of the Jet Template engine https://github.com/jhsx/GoJetPlugin).

Dynamically load pages based on string parameter

First of all thanks a lot for quicktemplate, I'm newbie and this is great: I'm learning a lot from your code. Thanks again!

(Maybe) a silly question: I'm trying to use authboss with quicktemplate but I don't know if I'm doing it well.

Authboss has just one interface (https://godoc.org/github.com/volatiletech/authboss/#Renderer) for its rendering system.
As stated in its README (https://github.com/volatiletech/authboss/blob/master/README.md#rendering-views):

The renderer knows how to load templates, and how to render them with some data and that's it.

...

When your app is a traditional web application and is generating it's HTML serverside using templates this becomes a small wrapper on top of your rendering setup. For example if you're using html/template then you could just use template.New() inside the Load() method and store that somewhere and call template.Execute() in the Render() method.

There is also a very basic renderer: Authboss Renderer which has some very ugly built in views and the ability to override them with your own if you don't want to integrate your own rendering system into that interface.

If you read the code for html.go you can see Load() and Render() methods.

I started copying that code and if I understand correctly:

  • the Load() method: I think I don't need it (for my basic work). Am I wrong? The original authboss one is here with some comments of mine:
func (h *HTML) Load(names ...string) error {
    if h.layout == nil {
        b, err := loadWithOverride(h.overridePath, "html-templates/layout.tpl") // I use an interface for layout page with quicktemplate
        if err != nil {
            return err
        }
        h.layout, err = template.New("").Funcs(h.funcMap).Parse(string(b)) // I don't need parsing anymore
        if err != nil {
            return errors.Wrap(err, "failed to load layout template")
        }
    }
    for _, n := range names {
        filename := fmt.Sprintf("html-templates/%s.tpl", n) // this is already an object in my code, right?
        b, err := loadWithOverride(h.overridePath, filename)
        if err != nil {
            return err
        }
        clone, err := h.layout.Clone() // this is already an object in my code, right?
        if err != nil {
            return err
        }
        _, err = clone.New("authboss").Funcs(h.funcMap).Parse(string(b)) // this is already an object in my code, right?
        if err != nil {
            return errors.Wrapf(err, "failed to load template for page %s", n)
        }
        h.templates[n] = clone // maybe something like this with functions?
    }
    return nil
}
  • for the Render() method I can use something like this:
func (h *HTML) Render(ctx context.Context, page string, data authboss.HTMLData) (output []byte, contentType string, err error) {
	// -------> Original authboss one, commented now
	// buf := &bytes.Buffer{}
	// tpl, ok := h.templates[page]
	// if !ok {
	// return nil, "", errors.Errorf("template for page %s not found", page)
	// }
	// err = tpl.Execute(buf, data)
	// if err != nil {
	// return nil, "", errors.Wrapf(err, "failed to render template for page %s", page)
	// }

	// -------> Mine
	buf := &bytes.Buffer{}
	templates.WritePage(buf, &templates.LoginPage{
		Data: data,
	})
	return buf.Bytes(), "text/html", nil
}

which has the problem I cannot dynamically load pages in templates.WritePage() method based on page parameter.

LoginPage is coming from a template like this:

{% import "github.com/volatiletech/authboss" %}

{% code
    type LoginPage struct { Data authboss.HTMLData } 
%}

{% func (p *LoginPage) Title() %}
    Login
{% endfunc %}

{% func (p *LoginPage) Body() %}
    <b>Data: {%v p.Data.Something %}</b>
    <form action="/login" method="POST">
        <input type="email">
        <input type="password">
        <button>Login</button>
        {% if p.Data["modules"] != nil %} 
            Something else with modules...
        {% endif %}
    </form>
{% endfunc %}

Maybe with reflection? Really? I read everywhere reflection is really slow and I need to use something else if possible.

I tried also with something like this:

{% import "github.com/volatiletech/authboss" %}

{% code var ALL_TEMPLATES map[string]*LoginPage %}

{% code
    func init() {
        ALL_TEMPLATES = make(map[string]*LoginPage)
        ALL_TEMPLATES["login"] = &LoginPage{}
    }
%}

{% code
    type LoginPage struct { Data authboss.HTMLData } 
%}

{% func (p *LoginPage) Title() %}
    Login
{% endfunc %}

{% func (p *LoginPage) Body() %}
    <b>Data: {%v p.Data.Something %}</b>
    <form action="/login" method="POST">
        <input type="email">
        <input type="password">
        <button>Login</button>
        {% if p.Data["modules"] != nil %} 
            Something else with modules...
        {% endif %}
    </form>
{% endfunc %}

but I think something is wrong here. I don't like ALL_TEMPLATES slice way of doing this.

What do you suggest?

I can write a Wiki (for newbies like me) in this project and in the authboss one.


I already opened an issue on authboss repo: volatiletech/authboss#239.

Feature request: strip newline

Title says everything. we have strip/collapse space.

Is there any way to collapse newline too?
the resulting html will be similar to minified html and that would be awesome!

cheers

Suggestion for executing templates with location/path as a variable

I'm trying to think of a way to execute templates based on router parameters. If I have a large collection of templates arranged in a folder structure, I'd like to be able execute one via the path without explicitly wiring everything up before hand. I'd love to see an option on qtc to generate a map of function pointers where I could look for a template at a particular path, execute it if it exists, or choose a template in a parent location if it doesn't. (hope that makes sense, and I'm assuming that the template all need to take the same args) Eventually, I may actually store the templates in a database and write them out to the file system as part of the build process before invoking qtc. This would give me back some of the advantages of dynamic templates while enjoying the speed and efficiency of quicktemplates.

1

1

cat in func with parameters

I'm trying to understand cat.

In readme I can read:

{% cat "/path/to/file" %}:

Cat emits the given file contents as a plaintext:
{% func passwords() %}
    /etc/passwd contents:
    {% cat "/etc/passwd" %}
{% endfunc %}

I'm trying to use something like this but with a parameter:

{% func passwords(user string) %}
    /etc/passwd contents for user:
    {% cat "/etc/" + user + "/passwd" %}
{% endfunc %}

But the compiler returns an error: invalid syntax.

How can I do?
Is it possible?

Suggestion qtc: Add a watcher qtc -w

Add a watcher subcommand/flag for example qtc -w ./ -d ./templates -> watch and compile on file changes in the current directory, serve the compiled files in the ./templates directory.

You can use fsnotify package, I will do that on iris but it will be nice to have it build'n with qtc :)

P.S: I don't know if go generate has a watch command but I doubt

Line numbers in panic are incorrect

The line numbers generated in panic messages from go are incorrect. For example:

ERRO[0002] panic: runtime error: invalid memory address or nil pointer dereference web/templates.(*EventsShow).StreamBodyHTML
	web/templates/events-show.qtpl:56

The actual error is on a much earlier line.

The issue can be resolved by removing the tabs before each //line comment.

Add stripcomments

We have cool stripspace feature already, and maybe it's time to add stripcomments to remove html comments?

Disable HTML escaping

I really like QTPL but I rarely use it for HTML generation but rather go code generation. So I often write code like this to avoid the automatic HTML escaping which is really inconvenient:

func writeunesc(iow io.Writer, s string) {
	qw := qt.AcquireWriter(iow)
	streamunesc(qw, s)
	qt.ReleaseWriter(qw)
}

func unesc(s string) string {
	qb := qt.AcquireByteBuffer()
	writeunesc(qb, s)
	qs := string(qb.B)
	qt.ReleaseByteBuffer(qb)
	return qs
}

func streamunesc(qw *qt.Writer, s string) {
	qw.N().S(s)
}

to use it like this:

{% func Foo(param *ParamType) %}
Some
{%= unesc(param.Text) %}
stuff
{% endfunc %}

So I wanted to ask if there is already a way to deal with this and if not how one could implement this maybe as some thing like {%r param.Text %}

collapsespace strips space after quicktemplate tag

I have the following template:

{% collapsespace %}
{% func Foo(str string) %}
  Foo {%s str %} bar.
{% endfunc %}
{% endcollapsespace %}

I call this template templates.WriteFoo(out, "string").

I get the following output:

Foo stringbar.

I think this is a bug. It should leave one space after the output tag (now it strips all) when collapsespace is in effect.

Run qtc on a folder

Is it possible to set a folder where my templates are located that I want to be compiled?

Something like this

qtc --folder=/my/long/path/templates

Thanks!

Ability to specify precision for floats

It would be nice to be able to set precision for floats (without slow {%s fmt.Sprintf("%.2f", 3.1415926535) %}).
E.g. {%f.2 3.1415926535 %} outputs 3.14.

I can make PR for this.

Feature Request: Change package name

First off, thanks for the great template library. Coming from mako, I feel right at home with quicktemplate.

Currently quicktemplate only outputs the name of the directory as the package name in the generated go code. Would it be possible to allow the package name to be changed from a {% package %} directive at the top of the template?

My use case is that I have a few templates that I want to drop into the main package alongside other code. I'd like to be able to specify {% package "main" %}

I surveyed the code to take a look at how to implement it. I guess the packageName would have to be exposed by the scanner somehow to get it back up to the parser.

Gentle suggestions improvements in documentation

this package is such a great find. thanks so much for your work. Just a few suggestions

  1. notwithstanding the assumption that folks are experienced go developers, it might be a good idea to mention that function names in the template must be capitalized to be accessible.

  2. the use of ./template doesn't work if you are using go build .. I have a directory that I keep both the source and executable, which is necessary for this development. build wants an absolute path names for imports. It might be worth mentioning that the location of the template folder wants to be in your source tree and referenced with an absolute path.

  3. the readme.md is a great reference on the template language, but did not strike me as the "definitive reference" Perhaps it is just a change in headings, or perhaps setting up a separate document as the reference would help to quell the uneasy feeling that there is more than mentioned there.

  4. the basicserver example could really use some additional comments to describe what is really going on. The use of function pointers could be discussed in more detail. It really deserves more TLC

  5. finally, our intended use requires the building of templates in parallel using multiple goroutines. It would be helpful to know if the rendering part of this system can handle that. the streaming to output we handle separately.

make it be a sql template

hi,I want make it be a best sql template like this:
step1: define a sql template text
select *
from table
where name=#name
@if age>0 {
and age=#age
or name=#name
@}

step2: when I give one param "name" with value "John",after call your quilktemplate,return a preparedstatment string sql, and a paramslist
select * from table where name=? , [ John]

when I give two params "name=John" and "age=12",it should be like this:

select * from table where name=? and age=? or name=?, [John, 12, John]

I think it should be very usefull.
thanks

Injection of content by string

Hi,

I'm trying to inject some metatags in the template without scape. This tags was generate by a library in Go that returns a output in string. But the output always is scaped.
Code:

{% func LinkingDataLocal(datalist jsonld.StructuredDataList) %}
        {% for _, tag := range datalist %}
            <script type="application/ld+json">{%s tag.JSON() %}</script>
        {% endfor %}
{% endfunc %}

Output:

<script type="application/ld+json">{&quot;@context&quot;:&quot;http://schema.org/&quot;,&quot;@type&quot;:&quot;WebSite&quot;,&quot;name&quot;:&quot;Some Description&quot;,&quot;url&quot;:&quot;https://www.example.com&quot;}</script>

I tried other scapes {%=h..., {%=jh`, etc... but don't work too.

Best Practice: Code organization

Hi @valyala,

thank you for the great project. What is your best practice organizing the files? Do you usually commit the templates and compiled templates in your projects or just the templates and compile e.g. during CI just before test/build?

Thanks for your advice.

Sven

[Feature Request] ignoring new line and tab indents in special tags

I am using QTPL for go code generation.
i see that it is intended for html template. But it can be very useful for all kind of text generation.

issue #27 does not cover my case

below is a sample template func to explain my use case

{% func GenTestFunc() %}
	func TestFunc(a int, b int) int {
		var sum int
		{% for i := 0; i < 10; i++ %}
			sum += {%d i %}
		{% endfor %}
		return sum
	}
{% endfunc %}

I'm getting:

	func TestFunc(a int, b int) int {
		var sum int
		
			sum += 0
		
			sum += 1
		
			sum += 2
		
			sum += 3
		
			sum += 4
		
			sum += 5
		
			sum += 6
		
			sum += 7
		
			sum += 8
		
			sum += 9

		return sum
		
	}

expect something like this:

func TestFunc(a int, b int) int {
	var sum int
	sum += 0
	sum += 1
	sum += 2
	sum += 3
	sum += 4
	sum += 5
	sum += 6
	sum += 7
	sum += 8
	sum += 9
	return sum
}

i tried {% stripspace %} and {% collapsespace %} but they are not sufficient

  1. {% func %}, {% for %}, {% if %} and other tags, take string between as it is, to improve readability, these blocks can ignore first tab/space indent, or special tag to ignore them
  2. also each the block, create a new line before them

i had to write this to get the intended output

{% func GenTestFunc() %}
func TestFunc(a int, b int) int {
	var sum int{% for i := 0; i < 10; i++ %}
	sum += {%d i %}{% endfor %}
	return sum
}
{% endfunc %}

cat without HTML-escaping?

Is it possible to use {% cat "myfile.svg" %} without having the result escaped?

Haven't been able to get any of these to work:
{%= cat ...
{% cat= ...
{%s= cat ...

Quick start issue!

Hi,
I followed the quick start steps but running the main.go (go run main.go) here is the error message showed:

../github.com/valyala/quicktemplate/bytebuffer.go:4:2: cannot find package "github.com/valyala/bytebufferpool" in any of:
/usr/lib/go-1.6/src/github.com/valyala/bytebufferpool (from $GOROOT)
/home/danilo/godev/src/github.com/valyala/bytebufferpool (from $GOPATH)

Could you please fix it?
Thanks,
Danilo

Include template

Thank you very much for the great job, Aliaksandr. I just was not able to find how to include another template into the current one (I have some standard repetitive parts in my templates). E.g. in the html/template package it is {{ template "FILENAME" }} construction.

Accept uint64 values for %d

Hi.

Right now it's only possible to use ints with {%d โ€ฆ %} output tag, when I trying to pass uint64 I get the following error:

test.qtpl:8[/home/user/test.qtpl.go:49]: cannot use id (type uint64) as type int in argument to qw422016.N().D

Could you please add support for uint64 too? Thanks.

Avoid always repeating {% stripspace %} everywhere

I'm using

{% stripspace %}
...
{% endstripspace %}

everywhere.

Is there a way to avoid always repeating this code and taking it for granted?

Maybe during compilation with qtc? Maybe as a parameter?

Possible to import html files?

Hello,

is it possible to import html files and QTC still interpreting quicktemplate syntax? Cat will import the html as a string and therefore ignore quicktemplate syntax for variables etc.

Reason is: working with .qtpl files without html autocomplete etc. is somewhat tiresome.....

Did i miss something?

Regards

Add flag to not have the comments in the qtpl.go file

Is it possible to add a flag that will not add the comments in the qtpl.go file?
Because at the moment for some reason the comment lines are bugging with my golintci-lint.
Another option would be to add a space between the // and the first letter of the comment.

so instead of
//line internal/.....qtpl:1 it would become
// line internal/....qtpl:1

command line flag for collapsewhitespace?

Not sure if this is a good idea or not, but maybe it would be cool to have a global CLI flag to collapse whitespace everywhere?

Would be better than putting collapsewhitespace in many places, could be helpful for dev vs. prod builds, might also make your binary slightly smaller.

Anyway I'd be happy to work on this, unless you think it's infeasible or a bad idea.


update 1: I see now that you can put {% collapsewhitespace %} at top of file and {% endcollapsewhitespace %} at end, and it will be applied to everything in that file.

update 2: When collapsing all the whitespace, if you have something like:

class="row {% if idx % 2 == 0 %}even{% else %}odd{% endif %} {% if isSpecial %}special{% endif %}"

the class names will get mushed together, since the space between the tags gets stripped out. Therefore you have to do something like:

class="row {% if idx % 2 == 0 %}even {% else %}odd {% endif %} {% if isSpecial %}special {% endif %}"

to ensure spaces between your class names.

Anyway, I can see how the explicit use of collapsewhitespace is probably a good idea. I'd still be happy to add CLI flag though...

[Question] Inject content after template render

Currently I am embedding SVG icons by calling a function {%= p.SVGSymbol("svg/icon/close.svg") %} that creates a SVG <use xlink> tag and remembers the symbol being used. And at the end of the template I embed the automatically generated SVG definitions (so that only icons that are actually used are embedded).

I know need to insert the sprite at the beginning of the content - before I know what icons I need to embed.

What would be the best way to achieve that without having to render all templates twice for every call?

Syntax Highlighting?

Do you know if anyone has started adding syntax highlighting support for various text editors? My personal interest is for Sublime Text.

Different slash for different OS

I'm using unix, mac, windows with many projects and when I regenerate code on different OS qtc writes different slash in each commented line:

Example on mac:

//line project/templates/include_custom.qtpl:3

Example on Windows:

//line project\templates\include_custom.qtpl:3

Can we use the same slash every time please?

What do you think about it?

is there any multi inheritance support

Hi there
I have template like this:

//base.twigLike
<!DOCTYPE html>
<html>
    <head>
        {% block head %}
            <link rel="stylesheet" href="style.css" />
            <title>{% block title %}{% endblock %} - My Webpage</title>
        {% endblock %}
    </head>
    <body>
        {% block body%}{% endblock %}
    </body>
</html>

//admin.twigLike
{% extends "base.twigLike" %}
 {% block body%}
    <div>{% block menu%}{% endblock %} </div>
    <div>{% block conent%}{% endblock %} </div>
{% endblock %}

//page.twigLike
{% extends "admin.twigLike" %}
//some other stuff

in quicktemplate is there any way to multi inheritance?

Doesn't work in Google App Engine environment

Google App Engine Go Standard Environment has no unsafe package, so running the simple Hello example gives the following error:

2016/07/03 22:23:53 go-app-builder: Failed parsing input: parser: bad import "unsafe" in github.com/valyala/quicktemplate/bytebuffer.go from GOPATH

Is it possible to have an option to run it without "unsafe" package (even with price of lower performance)?

{% flush %} tag for progressive rendering of HTML?

Hello valyala -

Was interested in doing "progressive rendering" where the first part of a page is flushed before slower content is loaded and rendered. This is possible by decomposing page into chunks and rendering them in order, but requires that you split any existing templates / layouts in half.

It seems like it would be possible to add a {% flush %} tag that would emit this in the middle of the template:

        if f, ok := w.(http.Flusher); ok {
		f.Flush()
	}

I was able to verify this works in practice by adding this code to a .qtpl.go:

        qw422016.N().S(`<h1>Before flush...</h1>`)
	if f, ok := qw422016.W().(http.Flusher); ok {
		f.Flush()
	}
	time.Sleep(5 * time.Second)
	qw422016.N().S(`<h1>After flush...</h1>`)

I started to implement here but the use of http in the middle of the emitted file would require that net/http be imported at the top, and by that point the emitImportsUse function has already run.

One option is to just require user to do {% import "net/http" %} if they plan to use {% flush %}. The hero project relies on goimports to solve this problem.

If the import issue could be fixed, would you be interested in adding a tag like this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.