Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support an optional custom escape #25

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func main() {
}

// parse template
tpl, err := raymond.Parse(source)
tpl, err := raymond.Parse(source, nil)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -179,7 +179,7 @@ You can use `MustParse()` and `MustExec()` functions if you don't want to deal w

```go
// parse template
tpl := raymond.MustParse(source)
tpl := raymond.MustParse(source, nil)

// render template
result := tpl.MustExec(ctx)
Expand Down Expand Up @@ -281,7 +281,7 @@ ctx := map[string]string{
"body": "<p>This is a post about &lt;p&gt; tags</p>",
}

tpl := raymond.MustParse(source)
tpl := raymond.MustParse(source, nil)
result := tpl.MustExec(ctx)

fmt.Print(result)
Expand All @@ -305,7 +305,7 @@ raymond.RegisterHelper("link", func(url, text string) raymond.SafeString {
return raymond.SafeString("<a href='" + raymond.Escape(url) + "'>" + raymond.Escape(text) + "</a>")
})

tpl := raymond.MustParse("{{link url text}}")
tpl := raymond.MustParse("{{link url text}}", nil)

ctx := map[string]string{
"url": "http://www.aymerick.com/",
Expand All @@ -322,6 +322,29 @@ Output:
<a href='http://www.aymerick.com/'>This is a &lt;em&gt;cool&lt;/em&gt; website</a>
```

### Custom Escaping

If you are rendering something which is not HTML, the default escaping may not be what you want. In that case, you may specify your own escaper:

```go
type NoopEscaper struct {}
func (n NoopEscaper) Escape(s string) string {
return s
}


opts := &raymond.TemplateOptions{
Escaper: &NoopEscaper{},
}
tpl := raymond.MustParse("Hey {{ text }}", opts)

ctx := map[string]string{
"text": "<<< cool >>>",
}

result := tpl.MustExec(ctx)
fmt.Print(result) // would print "Hey <<< cool >>>"
```

## Helpers

Expand Down Expand Up @@ -433,7 +456,7 @@ RegisterHelper("fullName", func(person Person) string {
You can register a helper on a specific template, and in that case that helper will be available to that template only:

```go
tpl := raymond.MustParse("User: {{fullName user.firstName user.lastName}}")
tpl := raymond.MustParse("User: {{fullName user.firstName user.lastName}}", nil)

tpl.RegisterHelper("fullName", func(firstName, lastName string) string {
return firstName + " " + lastName
Expand Down Expand Up @@ -1116,7 +1139,7 @@ Those context functions behave like helper functions: they can be called with pa
You can register template partials before execution:

```go
tpl := raymond.MustParse("{{> foo}} baz")
tpl := raymond.MustParse("{{> foo}} baz", nil)
tpl.RegisterPartial("foo", "<span>bar</span>")

result := tpl.MustExec(nil)
Expand All @@ -1132,7 +1155,7 @@ Output:
You can register several partials at once:

```go
tpl := raymond.MustParse("{{> foo}} and {{> baz}}")
tpl := raymond.MustParse("{{> foo}} and {{> baz}}", nil)
tpl.RegisterPartials(map[string]string{
"foo": "<span>bar</span>",
"baz": "<span>bat</span>",
Expand All @@ -1156,7 +1179,7 @@ You can registers global partials that will be accessible by all templates:
```go
raymond.RegisterPartial("foo", "<span>bar</span>")

tpl := raymond.MustParse("{{> foo}} baz")
tpl := raymond.MustParse("{{> foo}} baz", nil)
result := tpl.MustExec(nil)
fmt.Print(result)
```
Expand All @@ -1169,7 +1192,7 @@ raymond.RegisterPartials(map[string]string{
"baz": "<span>bat</span>",
})

tpl := raymond.MustParse("{{> foo}} and {{> baz}}")
tpl := raymond.MustParse("{{> foo}} and {{> baz}}", nil)
result := tpl.MustExec(nil)
fmt.Print(result)
```
Expand All @@ -1182,7 +1205,7 @@ It's possible to dynamically select the partial to be executed by using sub expr
For example, that template randomly evaluates the `foo` or `baz` partial:

```go
tpl := raymond.MustParse("{{> (whichPartial) }}")
tpl := raymond.MustParse("{{> (whichPartial) }}", nil)
tpl.RegisterPartials(map[string]string{
"foo": "<span>bar</span>",
"baz": "<span>bat</span>",
Expand All @@ -1209,7 +1232,7 @@ It's possible to execute partials on a custom context by passing in the context
For example:

```go
tpl := raymond.MustParse("User: {{> userDetails user }}")
tpl := raymond.MustParse("User: {{> userDetails user }}", nil)
tpl.RegisterPartial("userDetails", "{{firstname}} {{lastname}}")

ctx := map[string]interface{}{
Expand Down Expand Up @@ -1237,7 +1260,7 @@ Custom data can be passed to partials through hash parameters.
For example:

```go
tpl := raymond.MustParse("{{> myPartial name=hero }}")
tpl := raymond.MustParse("{{> myPartial name=hero }}", nil)
tpl.RegisterPartial("myPartial", "My hero is {{name}}")

ctx := map[string]interface{}{
Expand Down
4 changes: 2 additions & 2 deletions base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func launchTests(t *testing.T, tests []Test) {
var tpl *Template

// parse template
tpl, err = Parse(test.input)
tpl, err = Parse(test.input, nil)
if err != nil {
t.Errorf("Test '%s' failed - Failed to parse template\ninput:\n\t'%s'\nerror:\n\t%s", test.name, test.input, err)
} else {
Expand Down Expand Up @@ -91,7 +91,7 @@ func launchErrorTests(t *testing.T, tests []Test) {
var tpl *Template

// parse template
tpl, err = Parse(test.input)
tpl, err = Parse(test.input, nil)
if err != nil {
t.Errorf("Test '%s' failed - Failed to parse template\ninput:\n\t'%s'\nerror:\n\t%s", test.name, test.input, err)
} else {
Expand Down
34 changes: 17 additions & 17 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func BenchmarkArguments(b *testing.B) {
"bar": true,
}

tpl := MustParse(source)
tpl := MustParse(source, nil)
tpl.RegisterHelper("foo", func(a, b, c, d interface{}) string { return "" })

b.ResetTimer()
Expand All @@ -37,7 +37,7 @@ func BenchmarkArrayEach(b *testing.B) {
},
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand All @@ -57,7 +57,7 @@ func BenchmarkArrayMustache(b *testing.B) {
},
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand Down Expand Up @@ -92,7 +92,7 @@ func BenchmarkComplex(b *testing.B) {
},
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand All @@ -112,7 +112,7 @@ func BenchmarkData(b *testing.B) {
},
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand All @@ -133,7 +133,7 @@ func BenchmarkDepth1(b *testing.B) {
"foo": "bar",
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand All @@ -154,7 +154,7 @@ func BenchmarkDepth2(b *testing.B) {
"foo": "bar",
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand All @@ -172,7 +172,7 @@ func BenchmarkObjectMustache(b *testing.B) {
},
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand All @@ -190,7 +190,7 @@ func BenchmarkObject(b *testing.B) {
},
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand All @@ -216,9 +216,9 @@ func BenchmarkPartialRecursion(b *testing.B) {
},
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

partial := MustParse(`{{name}}{{#each kids}}{{>recursion}}{{/each}}`)
partial := MustParse(`{{name}}{{#each kids}}{{>recursion}}{{/each}}`, nil)
tpl.RegisterPartialTemplate("recursion", partial)

b.ResetTimer()
Expand All @@ -238,9 +238,9 @@ func BenchmarkPartial(b *testing.B) {
},
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

partial := MustParse(`Hello {{name}}! You have {{count}} new messages.`)
partial := MustParse(`Hello {{name}}! You have {{count}} new messages.`, nil)
tpl.RegisterPartialTemplate("variables", partial)

b.ResetTimer()
Expand All @@ -263,7 +263,7 @@ func BenchmarkPath(b *testing.B) {
},
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand All @@ -274,7 +274,7 @@ func BenchmarkPath(b *testing.B) {
func BenchmarkString(b *testing.B) {
source := `Hello world`

tpl := MustParse(source)
tpl := MustParse(source, nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand All @@ -287,7 +287,7 @@ func BenchmarkSubExpression(b *testing.B) {

ctx := map[string]interface{}{}

tpl := MustParse(source)
tpl := MustParse(source, nil)
tpl.RegisterHelpers(map[string]interface{}{
"echo": func(v string) string { return "foo " + v },
"header": func() string { return "Colors" },
Expand All @@ -307,7 +307,7 @@ func BenchmarkVariables(b *testing.B) {
"count": 30,
}

tpl := MustParse(source)
tpl := MustParse(source, nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand Down
23 changes: 19 additions & 4 deletions escape.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,29 @@ func escape(w writer, s string) error {
return err
}

// Escape escapes special HTML characters.
//
// It can be used by helpers that return a SafeString and that need to escape some content by themselves.
func Escape(s string) string {
type escapeFunc func(string) string

// Escaper defines an object which can be used to escape strings.
type Escaper interface {
Escape(string) string
}

// HTMLEscaper is an Escaper which escapes HTML-unsafe characters in strings.
type HTMLEscaper struct{}

// Escape HTML characters in a string
func (h HTMLEscaper) Escape(s string) string {
if strings.IndexAny(s, escapedChars) == -1 {
return s
}
var buf bytes.Buffer
escape(&buf, s)
return buf.String()
}

var defaultEscaper = HTMLEscaper{}

// Escape escapes special HTML characters.
//
// It can be used by helpers that return a SafeString and that need to escape some content by themselves.
var Escape = HTMLEscaper.Escape
41 changes: 35 additions & 6 deletions escape_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package raymond

import "fmt"
import (
"strings"
"testing"
)

func ExampleEscape() {
tpl := MustParse("{{link url text}}")
func TestDefaultEscape(t *testing.T) {
expected := "<a href='http://www.aymerick.com/'>This is a &lt;em&gt;cool&lt;/em&gt; website</a>"
tpl := MustParse("{{link url text}}", nil)
esc := &HTMLEscaper{}

tpl.RegisterHelper("link", func(url string, text string) SafeString {
return SafeString("<a href='" + Escape(url) + "'>" + Escape(text) + "</a>")
return SafeString("<a href='" + esc.Escape(url) + "'>" + esc.Escape(text) + "</a>")
})

ctx := map[string]string{
Expand All @@ -15,6 +20,30 @@ func ExampleEscape() {
}

result := tpl.MustExec(ctx)
fmt.Print(result)
// Output: <a href='http://www.aymerick.com/'>This is a &lt;em&gt;cool&lt;/em&gt; website</a>
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
}

type TestEscaper struct{}

func (t TestEscaper) Escape(s string) string {
return strings.Replace(s, "em", "EM", -1)
}

func TestCustomEscape(t *testing.T) {
expected := "This is a <EM>cool</EM> website"
opts := &TemplateOptions{
Escaper: &TestEscaper{},
}
tpl := MustParse("{{ text }}", opts)

ctx := map[string]string{
"text": "This is a <em>cool</em> website",
}

result := tpl.MustExec(ctx)
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
}
Loading