├── .gitignore ├── README.md └── v0 ├── block_test.go ├── content_test.go ├── doc.go ├── escape ├── attr.go ├── content.go ├── context.go ├── css.go ├── css_test.go ├── error.go ├── escape.go ├── escape_test.go ├── funcs.go ├── html.go ├── html_test.go ├── js.go ├── js_test.go ├── pipeline.go ├── pipeline_test.go ├── transition.go ├── url.go └── url_test.go ├── escape_test.go ├── example_test.go ├── examplefiles_test.go ├── examplefunc_test.go ├── exec.go ├── exec_test.go ├── funcs.go ├── inline.go ├── multi_test.go ├── parse ├── lex.go ├── lex_test.go ├── node.go ├── parse.go └── parse_test.go ├── template.go └── testdata ├── file1.tmpl ├── file2.tmpl ├── tmpl1.tmpl └── tmpl2.tmpl /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gorilla/template 2 | ================ 3 | 4 | *Warning:* This is a work in progress, and the v0 API is subject of 5 | changes. 6 | 7 | Go templates are a great foundation but suffer from some design 8 | flaws, and this is a pity. They could be really awesome. 9 | gorilla/template is yet another attempt to fix them. 10 | 11 | Goals 12 | ----- 13 | - Better composition options. 14 | - Improved HTML support. 15 | - Client-side support. 16 | - Auto-documentation. 17 | - Simpler spec and API. 18 | 19 | These goals will be achieved through several phases. 20 | 21 | ### Phase 1 - Dec.2012 22 | The initial work makes templates self-contained, removing root 23 | templates. This greatly simplifies internals, spec and API without 24 | losing any functionality. Highlights: 25 | 26 | - Removed New("name") API: templates are self-contained and must 27 | be named using {{define}}. 28 | - Templates are always executed passing the template name, so there's 29 | no need for two Execute methods. 30 | - The Set type replaces the Template type: a Set is a group 31 | of parsed templates. 32 | - Parse tree: added DefineNode and Tree types. 33 | - Node.String() methods result in templates that reproduce the 34 | original template exactly. 35 | 36 | ### Phase 2 - Dec.2012 37 | In this step the contextual escaping mechanism from html/template 38 | becomes part of the gorilla/template package, effectively making it 39 | a combination of text/template and html/template. Highlights: 40 | 41 | - Same functionality of text/template and html/template but: 42 | - 1119 less lines of code. 43 | - 33 less types, functions and methods. 44 | - HTML contextual escaping is set explicitly calling Escape(). 45 | - Types to encapsulate safe strings are placed in the template/escape 46 | package: CSS, JS, JSStr, HTML, HTMLAttr. 47 | 48 | ### Phase 3 - Jan.2013 49 | We finally add template inheritance and introduce two new actions: 50 | {{slot}} and {{fill}}. Highlights: 51 | 52 | - A {{slot}} defines placeholders in a base template, and a {{fill}} 53 | fills a placeholder in a parent template. 54 | - An inherited template is defined passing the parent template name 55 | in the {{define}} action, as in {{define "child" "parent"}}. 56 | - New templates can't be added to a Set after execution: the set 57 | is "compiled" and locked, as in html/template. 58 | - Inheritance and the new actions are inlined, so contextual escaping 59 | and execution didn't require any changes. 60 | -------------------------------------------------------------------------------- /v0/block_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package template 6 | 7 | import ( 8 | "bytes" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestSlot(t *testing.T) { 14 | // Some deep inheritance. 15 | tpl1 := ` 16 | {{define "tpl1"}} 17 | A 18 | {{slot "header"}} 19 | -h1- 20 | {{end}} 21 | B 22 | {{slot "footer"}} 23 | -f1- 24 | {{end}} 25 | C 26 | {{end}} 27 | 28 | {{define "tpl2" "tpl1"}} 29 | xxx 30 | {{end}} 31 | 32 | {{define "tpl3" "tpl2"}} 33 | xxx 34 | {{fill "header"}} 35 | -h3- 36 | {{end}} 37 | xxx 38 | {{end}} 39 | 40 | {{define "tpl4" "tpl3"}} 41 | xxx 42 | {{fill "header"}} 43 | -h4- 44 | {{end}} 45 | xxx 46 | {{fill "footer"}} 47 | -f4- 48 | {{end}} 49 | xxx 50 | {{end}} 51 | 52 | {{define "tpl5" "tpl4"}} 53 | xxx 54 | {{fill "footer"}} 55 | -f5- 56 | {{end}} 57 | xxx 58 | {{end}}` 59 | // Recursive inheritance. 60 | tpl2 := ` 61 | {{define "tpl1" "tpl2"}} 62 | {{fill "header"}} 63 | -h1- 64 | {{end}} 65 | {{end}} 66 | 67 | {{define "tpl2" "tpl3"}} 68 | {{fill "header"}} 69 | -h2- 70 | {{end}} 71 | {{end}} 72 | 73 | {{define "tpl3" "tpl1"}} 74 | {{fill "header"}} 75 | -h3- 76 | {{end}} 77 | {{end}}` 78 | 79 | tests := []execTest{ 80 | // the base template itself 81 | {"tpl1", tpl1, "A-h1-B-f1-C", nil, true}, 82 | // default slot value 83 | {"tpl2", tpl1, "A-h1-B-f1-C", nil, true}, 84 | // override only one slot 85 | {"tpl3", tpl1, "A-h3-B-f1-C", nil, true}, 86 | // override both slots 87 | {"tpl4", tpl1, "A-h4-B-f4-C", nil, true}, 88 | // override only one slot, higher level override both 89 | {"tpl5", tpl1, "A-h4-B-f5-C", nil, true}, 90 | // impossible recursion 91 | {"tpl1", tpl2, "impossible recursion", nil, false}, 92 | } 93 | for _, test := range tests { 94 | set, err := new(Set).Parse(test.input) 95 | if err != nil { 96 | t.Errorf("%s: unexpected parse error: %s", test.name, err) 97 | continue 98 | } 99 | b := new(bytes.Buffer) 100 | err = set.Execute(b, test.name, test.data) 101 | if test.ok { 102 | if err != nil { 103 | t.Errorf("%s: unexpected exec error: %s", test.name, err) 104 | continue 105 | } 106 | output := b.String() 107 | output = strings.Replace(output, " ", "", -1) 108 | output = strings.Replace(output, "\n", "", -1) 109 | output = strings.Replace(output, "\t", "", -1) 110 | if test.output != output { 111 | t.Errorf("%s: expected %q, got %q", test.name, test.output, output) 112 | } 113 | } else { 114 | if err == nil { 115 | t.Errorf("%s: expected exec error", test.name) 116 | continue 117 | } 118 | if !strings.Contains(err.Error(), test.output) { 119 | t.Errorf("%s: expected exec error %q, got %q", test.name, test.output, err.Error()) 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /v0/content_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package template 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/gorilla/template/v0/escape" 14 | ) 15 | 16 | func TestTypedContent(t *testing.T) { 17 | data := []interface{}{ 18 | ` "foo%" O'Reilly &bar;`, 19 | escape.CSS(`a[href =~ "//example.com"]#foo`), 20 | escape.HTML(`Hello, World &tc!`), 21 | escape.HTMLAttr(` dir="ltr"`), 22 | escape.JS(`c && alert("Hello, World!");`), 23 | escape.JSStr(`Hello, World & O'Reilly\x21`), 24 | escape.URL(`greeting=H%69&addressee=(World)`), 25 | } 26 | 27 | // For each content sensitive escaper, see how it does on 28 | // each of the typed strings above. 29 | tests := []struct { 30 | // A template containing a single {{.}}. 31 | input string 32 | want []string 33 | }{ 34 | { 35 | ``, 36 | []string{ 37 | `ZgotmplZ`, 38 | // Allowed but not escaped. 39 | `a[href =~ "//example.com"]#foo`, 40 | `ZgotmplZ`, 41 | `ZgotmplZ`, 42 | `ZgotmplZ`, 43 | `ZgotmplZ`, 44 | `ZgotmplZ`, 45 | }, 46 | }, 47 | { 48 | `
`, 49 | []string{ 50 | `ZgotmplZ`, 51 | // Allowed and HTML escaped. 52 | `a[href =~ "//example.com"]#foo`, 53 | `ZgotmplZ`, 54 | `ZgotmplZ`, 55 | `ZgotmplZ`, 56 | `ZgotmplZ`, 57 | `ZgotmplZ`, 58 | }, 59 | }, 60 | { 61 | `{{.}}`, 62 | []string{ 63 | `<b> "foo%" O'Reilly &bar;`, 64 | `a[href =~ "//example.com"]#foo`, 65 | // Not escaped. 66 | `Hello, World &tc!`, 67 | ` dir="ltr"`, 68 | `c && alert("Hello, World!");`, 69 | `Hello, World & O'Reilly\x21`, 70 | `greeting=H%69&addressee=(World)`, 71 | }, 72 | }, 73 | { 74 | ``, 75 | []string{ 76 | `ZgotmplZ`, 77 | `ZgotmplZ`, 78 | `ZgotmplZ`, 79 | // Allowed and HTML escaped. 80 | ` dir="ltr"`, 81 | `ZgotmplZ`, 82 | `ZgotmplZ`, 83 | `ZgotmplZ`, 84 | }, 85 | }, 86 | { 87 | ``, 88 | []string{ 89 | `<b> "foo%" O'Reilly &bar;`, 90 | `a[href =~ "//example.com"]#foo`, 91 | // Tags stripped, spaces escaped, entity not re-escaped. 92 | `Hello, World &tc!`, 93 | ` dir="ltr"`, 94 | `c && alert("Hello, World!");`, 95 | `Hello, World & O'Reilly\x21`, 96 | `greeting=H%69&addressee=(World)`, 97 | }, 98 | }, 99 | { 100 | ``, 101 | []string{ 102 | `<b> "foo%" O'Reilly &bar;`, 103 | `a[href =~ "//example.com"]#foo`, 104 | // Tags stripped, entity not re-escaped. 105 | `Hello, World &tc!`, 106 | ` dir="ltr"`, 107 | `c && alert("Hello, World!");`, 108 | `Hello, World & O'Reilly\x21`, 109 | `greeting=H%69&addressee=(World)`, 110 | }, 111 | }, 112 | { 113 | ``, 114 | []string{ 115 | `<b> "foo%" O'Reilly &bar;`, 116 | `a[href =~ "//example.com"]#foo`, 117 | // Angle brackets escaped to prevent injection of close tags, entity not re-escaped. 118 | `Hello, <b>World</b> &tc!`, 119 | ` dir="ltr"`, 120 | `c && alert("Hello, World!");`, 121 | `Hello, World & O'Reilly\x21`, 122 | `greeting=H%69&addressee=(World)`, 123 | }, 124 | }, 125 | { 126 | ``, 127 | []string{ 128 | `"\u003cb\u003e \"foo%\" O'Reilly &bar;"`, 129 | `"a[href =~ \"//example.com\"]#foo"`, 130 | `"Hello, \u003cb\u003eWorld\u003c/b\u003e &tc!"`, 131 | `" dir=\"ltr\""`, 132 | // Not escaped. 133 | `c && alert("Hello, World!");`, 134 | // Escape sequence not over-escaped. 135 | `"Hello, World & O'Reilly\x21"`, 136 | `"greeting=H%69&addressee=(World)"`, 137 | }, 138 | }, 139 | { 140 | `