tpl: Add docshelper for template funcs

And fix some other minor related issues.

Updates #3418
This commit is contained in:
Bjørn Erik Pedersen 2017-05-01 18:40:34 +02:00
parent e2b067f050
commit 690b0f8ff5
25 changed files with 2064 additions and 347 deletions

File diff suppressed because it is too large Load diff

41
tpl/cast/docshelper.go Normal file
View file

@ -0,0 +1,41 @@
// Copyright 2017 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cast
import (
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/docshelper"
"github.com/spf13/hugo/tpl/internal"
)
// This file provides documentation support and is randomly put into this package.
func init() {
docsProvider := func() map[string]interface{} {
docs := make(map[string]interface{})
d := &deps.Deps{}
var namespaces []*internal.TemplateFuncsNamespace
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
nf := nsf(d)
namespaces = append(namespaces, nf)
}
docs["funcs"] = namespaces
return docs
}
docshelper.AddDocProvider("tpl", docsProvider)
}

View file

@ -24,21 +24,27 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New() ctx := New()
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ "1234" | int | printf "%T" }}`, `int`},
{`{{ 1234 | string | printf "%T" }}`, `string`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"int": ctx.ToInt,
"string": ctx.ToString,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.ToInt,
[]string{"int"},
[][2]string{
{`{{ "1234" | int | printf "%T" }}`, `int`},
},
)
ns.AddMethodMapping(ctx.ToString,
[]string{"string"},
[][2]string{
{`{{ 1234 | string | printf "%T" }}`, `string`},
},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,48 +24,122 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New(d) ctx := New(d)
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ delimit (slice "A" "B" "C") ", " " and " }}`, `A, B and C`},
{`{{ echoParam .Params "langCode" }}`, `en`},
{`{{ if in "this string contains a substring" "substring" }}Substring found!{{ end }}`, `Substring found!`},
{
`{{ (querify "foo" 1 "bar" 2 "baz" "with spaces" "qux" "this&that=those") | safeHTML }}`,
`bar=2&baz=with+spaces&foo=1&qux=this%26that%3Dthose`},
{
`<a href="https://www.google.com?{{ (querify "q" "test" "page" 3) | safeURL }}">Search</a>`,
`<a href="https://www.google.com?page=3&amp;q=test">Search</a>`},
{`{{ slice "B" "C" "A" | sort }}`, `[A B C]`},
{`{{ seq 3 }}`, `[1 2 3]`},
{`{{ union (slice 1 2 3) (slice 3 4 5) }}`, `[1 2 3 4 5]`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"after": ctx.After,
"apply": ctx.Apply,
"delimit": ctx.Delimit,
"dict": ctx.Dictionary,
"echoParam": ctx.EchoParam,
"first": ctx.First,
"in": ctx.In,
"index": ctx.Index,
"intersect": ctx.Intersect,
"isSet": ctx.IsSet,
"isset": ctx.IsSet,
"last": ctx.Last,
"querify": ctx.Querify,
"shuffle": ctx.Shuffle,
"slice": ctx.Slice,
"sort": ctx.Sort,
"union": ctx.Union,
"where": ctx.Where,
"seq": ctx.Seq,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.After,
[]string{"after"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Apply,
[]string{"apply"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Delimit,
[]string{"delimit"},
[][2]string{
{`{{ delimit (slice "A" "B" "C") ", " " and " }}`, `A, B and C`},
},
)
ns.AddMethodMapping(ctx.Dictionary,
[]string{"dict"},
[][2]string{},
)
ns.AddMethodMapping(ctx.EchoParam,
[]string{"echoParam"},
[][2]string{
{`{{ echoParam .Params "langCode" }}`, `en`},
},
)
ns.AddMethodMapping(ctx.First,
[]string{"first"},
[][2]string{},
)
ns.AddMethodMapping(ctx.In,
[]string{"in"},
[][2]string{
{`{{ if in "this string contains a substring" "substring" }}Substring found!{{ end }}`, `Substring found!`},
},
)
ns.AddMethodMapping(ctx.Index,
[]string{"index"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Intersect,
[]string{"intersect"},
[][2]string{},
)
ns.AddMethodMapping(ctx.IsSet,
[]string{"isSet", "isset"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Last,
[]string{"last"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Querify,
[]string{"querify"},
[][2]string{
{
`{{ (querify "foo" 1 "bar" 2 "baz" "with spaces" "qux" "this&that=those") | safeHTML }}`,
`bar=2&baz=with+spaces&foo=1&qux=this%26that%3Dthose`},
{
`<a href="https://www.google.com?{{ (querify "q" "test" "page" 3) | safeURL }}">Search</a>`,
`<a href="https://www.google.com?page=3&amp;q=test">Search</a>`},
},
)
ns.AddMethodMapping(ctx.Shuffle,
[]string{"shuffle"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Slice,
[]string{"slice"},
[][2]string{
{`{{ slice "B" "C" "A" | sort }}`, `[A B C]`},
},
)
ns.AddMethodMapping(ctx.Sort,
[]string{"sort"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Union,
[]string{"union"},
[][2]string{
{`{{ union (slice 1 2 3) (slice 3 4 5) }}`, `[1 2 3 4 5]`},
},
)
ns.AddMethodMapping(ctx.Where,
[]string{"where"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Seq,
[]string{"seq"},
[][2]string{
{`{{ seq 3 }}`, `[1 2 3]`},
},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,27 +24,53 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New() ctx := New()
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ if eq .Section "blog" }}current{{ end }}`, `current`},
{`{{ "Hugo Rocks!" | default "Hugo Rules!" }}`, `Hugo Rocks!`},
{`{{ "" | default "Hugo Rules!" }}`, `Hugo Rules!`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"default": ctx.Default,
"eq": ctx.Eq,
"ge": ctx.Ge,
"gt": ctx.Gt,
"le": ctx.Le,
"lt": ctx.Lt,
"ne": ctx.Ne,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.Default,
[]string{"default"},
[][2]string{
{`{{ "Hugo Rocks!" | default "Hugo Rules!" }}`, `Hugo Rocks!`},
{`{{ "" | default "Hugo Rules!" }}`, `Hugo Rules!`},
},
)
ns.AddMethodMapping(ctx.Eq,
[]string{"eq"},
[][2]string{
{`{{ if eq .Section "blog" }}current{{ end }}`, `current`},
},
)
ns.AddMethodMapping(ctx.Ge,
[]string{"ge"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Gt,
[]string{"gt"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Le,
[]string{"le"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Lt,
[]string{"lt"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Ne,
[]string{"ne"},
[][2]string{},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,24 +24,35 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New() ctx := New()
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ md5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`},
{`{{ crypto.MD5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`},
{`{{ sha1 "Hello world, gophers!" }}`, `c8b5b0e33d408246e30f53e32b8f7627a7a649d4`},
{`{{ sha256 "Hello world, gophers!" }}`, `6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"md5": ctx.MD5,
"sha1": ctx.SHA1,
"sha256": ctx.SHA256,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.MD5,
[]string{"md5"},
[][2]string{
{`{{ md5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`},
{`{{ crypto.MD5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`},
},
)
ns.AddMethodMapping(ctx.SHA1,
[]string{"sha1"},
[][2]string{
{`{{ sha1 "Hello world, gophers!" }}`, `c8b5b0e33d408246e30f53e32b8f7627a7a649d4`},
},
)
ns.AddMethodMapping(ctx.SHA256,
[]string{"sha256"},
[][2]string{
{`{{ sha256 "Hello world, gophers!" }}`, `6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46`},
},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,19 +24,21 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New(d) ctx := New(d)
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"getCSV": ctx.GetCSV,
"getJSON": ctx.GetJSON,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.GetCSV,
[]string{"getCSV"},
[][2]string{},
)
ns.AddMethodMapping(ctx.GetJSON,
[]string{"getJSON"},
[][2]string{},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,24 +24,35 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New() ctx := New()
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ (slice "A" "B" "C") | jsonify }}`, `["A","B","C"]`},
{`{{ "SGVsbG8gd29ybGQ=" | base64Decode }}`, `Hello world`},
{`{{ 42 | base64Encode | base64Decode }}`, `42`},
{`{{ "Hello world" | base64Encode }}`, `SGVsbG8gd29ybGQ=`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"base64Decode": ctx.Base64Decode,
"base64Encode": ctx.Base64Encode,
"jsonify": ctx.Jsonify,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.Base64Decode,
[]string{"base64Decode"},
[][2]string{
{`{{ "SGVsbG8gd29ybGQ=" | base64Decode }}`, `Hello world`},
{`{{ 42 | base64Encode | base64Decode }}`, `42`},
},
)
ns.AddMethodMapping(ctx.Base64Encode,
[]string{"base64Encode"},
[][2]string{
{`{{ "Hello world" | base64Encode }}`, `SGVsbG8gd29ybGQ=`},
},
)
ns.AddMethodMapping(ctx.Jsonify,
[]string{"jsonify"},
[][2]string{
{`{{ (slice "A" "B" "C") | jsonify }}`, `["A","B","C"]`},
},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -26,14 +26,15 @@ func New() *Namespace {
type Namespace struct { type Namespace struct {
} }
func (ns *Namespace) Print(a ...interface{}) (n int, err error) { func (ns *Namespace) Print(a ...interface{}) string {
return _fmt.Print(a...) return _fmt.Sprint(a...)
} }
func (ns *Namespace) Printf(format string, a ...interface{}) (n int, err error) { func (ns *Namespace) Printf(format string, a ...interface{}) string {
return _fmt.Printf(format, a...) return _fmt.Sprintf(format, a...)
} }
func (ns *Namespace) Println(a ...interface{}) (n int, err error) { func (ns *Namespace) Println(a ...interface{}) string {
return _fmt.Println(a...) return _fmt.Sprintln(a...)
} }

View file

@ -24,18 +24,33 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New() ctx := New()
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ print "works!" }}`, `works!`}, Name: name,
{`{{ printf "%s!" "works" }}`, `works!`}, Context: func() interface{} { return ctx },
{`{{ println "works!" }}`, "works!\n"},
} }
return &internal.TemplateFuncsNamespace{ ns.AddMethodMapping(ctx.Print,
Name: name, []string{"print"},
Context: func() interface{} { return ctx }, [][2]string{
Aliases: map[string]interface{}{}, {`{{ print "works!" }}`, `works!`},
Examples: examples, },
} )
ns.AddMethodMapping(ctx.Println,
[]string{"println"},
[][2]string{
{`{{ println "works!" }}`, "works!\n"},
},
)
ns.AddMethodMapping(ctx.Printf,
[]string{"printf"},
[][2]string{
{`{{ printf "%s!" "works" }}`, `works!`},
},
)
return ns
} }

View file

@ -24,19 +24,18 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New(d) ctx := New(d)
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"imageConfig": ctx.Config,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.Config,
[]string{"imageConfig"},
[][2]string{},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,26 +24,37 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New() ctx := New()
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ humanize "my-first-post" }}`, `My first post`},
{`{{ humanize "myCamelPost" }}`, `My camel post`},
{`{{ humanize "52" }}`, `52nd`},
{`{{ humanize 103 }}`, `103rd`},
{`{{ "cat" | pluralize }}`, `cats`},
{`{{ "cats" | singularize }}`, `cat`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"humanize": ctx.Humanize,
"pluralize": ctx.Pluralize,
"singularize": ctx.Singularize,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.Humanize,
[]string{"humanize"},
[][2]string{
{`{{ humanize "my-first-post" }}`, `My first post`},
{`{{ humanize "myCamelPost" }}`, `My camel post`},
{`{{ humanize "52" }}`, `52nd`},
{`{{ humanize 103 }}`, `103rd`},
},
)
ns.AddMethodMapping(ctx.Pluralize,
[]string{"pluralize"},
[][2]string{
{`{{ "cat" | pluralize }}`, `cats`},
},
)
ns.AddMethodMapping(ctx.Singularize,
[]string{"singularize"},
[][2]string{
{`{{ "cats" | singularize }}`, `cat`},
},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -0,0 +1,33 @@
// Copyright 2017 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
import (
"testing"
"github.com/stretchr/testify/require"
)
type Test struct {
}
func (t *Test) MyTestMethod() string {
return "abcde"
}
func TestMethodToName(t *testing.T) {
test := &Test{}
require.Equal(t, "MyTestMethod", methodToName(test.MyTestMethod))
}

View file

@ -16,6 +16,12 @@
package internal package internal
import ( import (
"encoding/json"
"path/filepath"
"reflect"
"runtime"
"strings"
"github.com/spf13/hugo/deps" "github.com/spf13/hugo/deps"
) )
@ -32,12 +38,46 @@ type TemplateFuncsNamespace struct {
// This is the method receiver. // This is the method receiver.
Context interface{} Context interface{}
// Additional info, aliases and examples, per method name.
MethodMappings map[string]TemplateFuncMethodMapping
}
func (t *TemplateFuncsNamespace) AddMethodMapping(m interface{}, aliases []string, examples [][2]string) {
if t.MethodMappings == nil {
t.MethodMappings = make(map[string]TemplateFuncMethodMapping)
}
name := methodToName(m)
// sanity check
for _, e := range examples {
if e[0] == "" {
panic(t.Name + ": Empty example for " + name)
}
}
for _, a := range aliases {
if a == "" {
panic(t.Name + ": Empty alias for " + name)
}
}
t.MethodMappings[name] = TemplateFuncMethodMapping{
Method: m,
Aliases: aliases,
Examples: examples,
}
}
type TemplateFuncMethodMapping struct {
Method interface{}
// Any template funcs aliases. This is mainly motivated by keeping // Any template funcs aliases. This is mainly motivated by keeping
// backwards compability, but some new template funcs may also make // backwards compability, but some new template funcs may also make
// sense to give short and snappy aliases. // sense to give short and snappy aliases.
// Note that these aliases are global and will be merged, so the last // Note that these aliases are global and will be merged, so the last
// key will win. // key will win.
Aliases map[string]interface{} Aliases []string
// A slice of input/expected examples. // A slice of input/expected examples.
// We keep it a the namespace level for now, but may find a way to keep track // We keep it a the namespace level for now, but may find a way to keep track
@ -45,3 +85,44 @@ type TemplateFuncsNamespace struct {
// Some of these, hopefully just a few, may depend on some test data to run. // Some of these, hopefully just a few, may depend on some test data to run.
Examples [][2]string Examples [][2]string
} }
func methodToName(m interface{}) string {
name := runtime.FuncForPC(reflect.ValueOf(m).Pointer()).Name()
name = filepath.Ext(name)
name = strings.TrimPrefix(name, ".")
name = strings.TrimSuffix(name, "-fm")
return name
}
func (t *TemplateFuncsNamespace) MarshalJSON() ([]byte, error) {
type Func struct {
Name string
Description string // TODO(bep)
Aliases []string
Examples [][2]string
}
// TODO(bep) map/lookup from docs template Namespace + Func name.
var funcs []Func
ctx := t.Context.(func() interface{})()
ctxType := reflect.TypeOf(ctx)
for i := 0; i < ctxType.NumMethod(); i++ {
method := ctxType.Method(i)
f := Func{
Name: method.Name,
}
if mapping, ok := t.MethodMappings[method.Name]; ok {
f.Aliases = mapping.Aliases
f.Examples = mapping.Examples
}
funcs = append(funcs, f)
}
return json.Marshal(&struct {
Name string
Funcs []Func
}{
Name: t.Name,
Funcs: funcs,
})
}

View file

@ -24,20 +24,18 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New(d) ctx := New(d)
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"i18n": ctx.Translate,
"T": ctx.Translate,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.Translate,
[]string{"i18n", "T"},
[][2]string{},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,29 +24,55 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New() ctx := New()
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{"{{add 1 2}}", "3"},
{"{{div 6 3}}", "2"},
{"{{mod 15 3}}", "0"},
{"{{modBool 15 3}}", "true"},
{"{{mul 2 3}}", "6"},
{"{{sub 3 2}}", "1"},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"add": ctx.Add,
"div": ctx.Div,
"mod": ctx.Mod,
"modBool": ctx.ModBool,
"mul": ctx.Mul,
"sub": ctx.Sub,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.Add,
[]string{"add"},
[][2]string{
{"{{add 1 2}}", "3"},
},
)
ns.AddMethodMapping(ctx.Div,
[]string{"div"},
[][2]string{
{"{{div 6 3}}", "2"},
},
)
ns.AddMethodMapping(ctx.Mod,
[]string{"mod"},
[][2]string{
{"{{mod 15 3}}", "0"},
},
)
ns.AddMethodMapping(ctx.ModBool,
[]string{"modBool"},
[][2]string{
{"{{modBool 15 3}}", "true"},
},
)
ns.AddMethodMapping(ctx.Mul,
[]string{"mul"},
[][2]string{
{"{{mul 2 3}}", "6"},
},
)
ns.AddMethodMapping(ctx.Sub,
[]string{"sub"},
[][2]string{
{"{{sub 3 2}}", "1"},
},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,22 +24,32 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New(d) ctx := New(d)
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ range (readDir ".") }}{{ .Name }}{{ end }}`, "README.txt"},
{`{{ readFile "README.txt" }}`, `Hugo Rocks!`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"getenv": ctx.Getenv,
"readDir": ctx.ReadDir,
"readFile": ctx.ReadFile,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.Getenv,
[]string{"getenv"},
[][2]string{},
)
ns.AddMethodMapping(ctx.ReadDir,
[]string{"readDir"},
[][2]string{
{`{{ range (readDir ".") }}{{ .Name }}{{ end }}`, "README.txt"},
},
)
ns.AddMethodMapping(ctx.ReadFile,
[]string{"readFile"},
[][2]string{
{`{{ readFile "README.txt" }}`, `Hugo Rocks!`},
},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,20 +24,25 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New(d) ctx := New(d)
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ partial "header.html" . }}`, `<title>Hugo Rocks!</title>`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"partial": ctx.Include,
"partialCached": ctx.getCached,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.Include,
[]string{"partial"},
[][2]string{
{`{{ partial "header.html" . }}`, `<title>Hugo Rocks!</title>`},
},
)
ns.AddMethodMapping(ctx.getCached,
[]string{"partialCached"},
[][2]string{},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,30 +24,57 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New() ctx := New()
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ "Bat&Man" | safeCSS | safeCSS }}`, `Bat&amp;Man`},
{`{{ "Bat&Man" | safeHTML | safeHTML }}`, `Bat&Man`},
{`{{ "Bat&Man" | safeHTML }}`, `Bat&Man`},
{`{{ "(1*2)" | safeJS | safeJS }}`, `(1*2)`},
{`{{ "http://gohugo.io" | safeURL | safeURL }}`, `http://gohugo.io`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"safeCSS": ctx.CSS,
"safeHTML": ctx.HTML,
"safeHTMLAttr": ctx.HTMLAttr,
"safeJS": ctx.JS,
"safeJSStr": ctx.JSStr,
"safeURL": ctx.URL,
"sanitizeURL": ctx.SanitizeURL,
"sanitizeurl": ctx.SanitizeURL,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.CSS,
[]string{"safeCSS"},
[][2]string{
{`{{ "Bat&Man" | safeCSS | safeCSS }}`, `Bat&amp;Man`},
},
)
ns.AddMethodMapping(ctx.HTML,
[]string{"safeHTML"},
[][2]string{
{`{{ "Bat&Man" | safeHTML | safeHTML }}`, `Bat&Man`},
{`{{ "Bat&Man" | safeHTML }}`, `Bat&Man`},
},
)
ns.AddMethodMapping(ctx.HTMLAttr,
[]string{"safeHTMLAttr"},
[][2]string{},
)
ns.AddMethodMapping(ctx.JS,
[]string{"safeJS"},
[][2]string{
{`{{ "(1*2)" | safeJS | safeJS }}`, `(1*2)`},
},
)
ns.AddMethodMapping(ctx.JSStr,
[]string{"safeJSStr"},
[][2]string{},
)
ns.AddMethodMapping(ctx.URL,
[]string{"safeURL"},
[][2]string{
{`{{ "http://gohugo.io" | safeURL | safeURL }}`, `http://gohugo.io`},
},
)
ns.AddMethodMapping(ctx.SanitizeURL,
[]string{"sanitizeURL", "sanitizeurl"},
[][2]string{},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,54 +24,118 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New(d) ctx := New(d)
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{chomp "<p>Blockhead</p>\n" }}`, `<p>Blockhead</p>`},
{
`{{ findRE "[G|g]o" "Hugo is a static side generator written in Go." "1" }}`,
`[go]`},
{`{{ hasPrefix "Hugo" "Hu" }}`, `true`},
{`{{ hasPrefix "Hugo" "Fu" }}`, `false`},
{`{{lower "BatMan"}}`, `batman`},
{
`{{ replace "Batman and Robin" "Robin" "Catwoman" }}`,
`Batman and Catwoman`},
{
`{{ "http://gohugo.io/docs" | replaceRE "^https?://([^/]+).*" "$1" }}`,
`gohugo.io`},
{`{{slicestr "BatMan" 0 3}}`, `Bat`},
{`{{slicestr "BatMan" 3}}`, `Man`},
{`{{substr "BatMan" 0 -3}}`, `Bat`},
{`{{substr "BatMan" 3 3}}`, `Man`},
{`{{title "Bat man"}}`, `Bat Man`},
{`{{ trim "++Batman--" "+-" }}`, `Batman`},
{`{{ "this is a very long text" | truncate 10 " ..." }}`, `this is a ...`},
{`{{ "With [Markdown](/markdown) inside." | markdownify | truncate 14 }}`, `With <a href="/markdown">Markdown …</a>`},
{`{{upper "BatMan"}}`, `BATMAN`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"chomp": ctx.Chomp,
"countrunes": ctx.CountRunes,
"countwords": ctx.CountWords,
"findRE": ctx.FindRE,
"hasPrefix": ctx.HasPrefix,
"lower": ctx.ToLower,
"replace": ctx.Replace,
"replaceRE": ctx.ReplaceRE,
"slicestr": ctx.SliceString,
"split": ctx.Split,
"substr": ctx.Substr,
"title": ctx.Title,
"trim": ctx.Trim,
"truncate": ctx.Truncate,
"upper": ctx.ToUpper,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.Chomp,
[]string{"chomp"},
[][2]string{
{`{{chomp "<p>Blockhead</p>\n" }}`, `<p>Blockhead</p>`},
},
)
ns.AddMethodMapping(ctx.CountRunes,
[]string{"countrunes"},
[][2]string{},
)
ns.AddMethodMapping(ctx.CountWords,
[]string{"countwords"},
[][2]string{},
)
ns.AddMethodMapping(ctx.FindRE,
[]string{"findRE"},
[][2]string{
{
`{{ findRE "[G|g]o" "Hugo is a static side generator written in Go." "1" }}`,
`[go]`},
},
)
ns.AddMethodMapping(ctx.HasPrefix,
[]string{"hasPrefix"},
[][2]string{
{`{{ hasPrefix "Hugo" "Hu" }}`, `true`},
{`{{ hasPrefix "Hugo" "Fu" }}`, `false`},
},
)
ns.AddMethodMapping(ctx.ToLower,
[]string{"lower"},
[][2]string{
{`{{lower "BatMan"}}`, `batman`},
},
)
ns.AddMethodMapping(ctx.Replace,
[]string{"replace"},
[][2]string{
{
`{{ replace "Batman and Robin" "Robin" "Catwoman" }}`,
`Batman and Catwoman`},
},
)
ns.AddMethodMapping(ctx.ReplaceRE,
[]string{"replaceRE"},
[][2]string{},
)
ns.AddMethodMapping(ctx.SliceString,
[]string{"slicestr"},
[][2]string{
{`{{slicestr "BatMan" 0 3}}`, `Bat`},
{`{{slicestr "BatMan" 3}}`, `Man`},
},
)
ns.AddMethodMapping(ctx.Split,
[]string{"split"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Substr,
[]string{"substr"},
[][2]string{
{`{{substr "BatMan" 0 -3}}`, `Bat`},
{`{{substr "BatMan" 3 3}}`, `Man`},
},
)
ns.AddMethodMapping(ctx.Trim,
[]string{"trim"},
[][2]string{
{`{{ trim "++Batman--" "+-" }}`, `Batman`},
},
)
ns.AddMethodMapping(ctx.Title,
[]string{"title"},
[][2]string{
{`{{title "Bat man"}}`, `Bat Man`},
},
)
ns.AddMethodMapping(ctx.Truncate,
[]string{"truncate"},
[][2]string{
{`{{ "this is a very long text" | truncate 10 " ..." }}`, `this is a ...`},
{`{{ "With [Markdown](/markdown) inside." | markdownify | truncate 14 }}`, `With <a href="/markdown">Markdown …</a>`},
},
)
ns.AddMethodMapping(ctx.ToUpper,
[]string{"upper"},
[][2]string{
{`{{upper "BatMan"}}`, `BATMAN`},
},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,22 +24,32 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New() ctx := New()
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ (time "2015-01-21").Year }}`, `2015`},
{`dateFormat: {{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }}`, `dateFormat: Wednesday, Jan 21, 2015`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"dateFormat": ctx.Format,
"now": ctx.Now,
"time": ctx.AsTime,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.Format,
[]string{"dateFormat"},
[][2]string{
{`dateFormat: {{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }}`, `dateFormat: Wednesday, Jan 21, 2015`},
},
)
ns.AddMethodMapping(ctx.Now,
[]string{"now"},
[][2]string{},
)
ns.AddMethodMapping(ctx.AsTime,
[]string{"asTime"}, // TODO(bep) handle duplicate
[][2]string{
{`{{ (asTime "2015-01-21").Year }}`, `2015`},
},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -51,8 +51,14 @@ func (t *templateFuncster) initFuncMap() {
panic(ns.Name + " is a duplicate template func") panic(ns.Name + " is a duplicate template func")
} }
funcMap[ns.Name] = ns.Context funcMap[ns.Name] = ns.Context
for k, v := range ns.Aliases { for _, mm := range ns.MethodMappings {
funcMap[k] = v for _, alias := range mm.Aliases {
if _, exists := funcMap[alias]; exists {
panic(alias + " is a duplicate template func")
}
funcMap[alias] = mm.Method
}
} }
} }

View file

@ -87,19 +87,21 @@ func TestTemplateFuncsExamples(t *testing.T) {
for _, nsf := range internal.TemplateFuncsNamespaceRegistry { for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
ns := nsf(d) ns := nsf(d)
for i, example := range ns.Examples { for _, mm := range ns.MethodMappings {
in, expected := example[0], example[1] for i, example := range mm.Examples {
d.WithTemplate = func(templ tpl.TemplateHandler) error { in, expected := example[0], example[1]
require.NoError(t, templ.AddTemplate("test", in)) d.WithTemplate = func(templ tpl.TemplateHandler) error {
require.NoError(t, templ.AddTemplate("partials/header.html", "<title>Hugo Rocks!</title>")) require.NoError(t, templ.AddTemplate("test", in))
return nil require.NoError(t, templ.AddTemplate("partials/header.html", "<title>Hugo Rocks!</title>"))
} return nil
require.NoError(t, d.LoadResources()) }
require.NoError(t, d.LoadResources())
var b bytes.Buffer var b bytes.Buffer
require.NoError(t, d.Tmpl.Lookup("test").Execute(&b, &data)) require.NoError(t, d.Tmpl.Lookup("test").Execute(&b, &data))
if b.String() != expected { if b.String() != expected {
t.Fatalf("%s[%d]: got %q expected %q", ns.Name, i, b.String(), expected) t.Fatalf("%s[%d]: got %q expected %q", ns.Name, i, b.String(), expected)
}
} }
} }
} }

View file

@ -24,47 +24,72 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New(d) ctx := New(d)
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ "I :heart: Hugo" | emojify }}`, `I ❤️ Hugo`},
{`{{ .Title | markdownify}}`, `<strong>BatMan</strong>`},
{`{{ plainify "Hello <strong>world</strong>, gophers!" }}`, `Hello world, gophers!`},
{
`htmlEscape 1: {{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML}}`,
`htmlEscape 1: Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;`},
{
`htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>"}}`,
`htmlEscape 2: Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;cathal@foo.bar&amp;gt;`},
{
`htmlUnescape 1: {{htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | safeHTML}}`,
`htmlUnescape 1: Cathal Garvey & The Sunshine Band <cathal@foo.bar>`},
{
`htmlUnescape 2: {{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;cathal@foo.bar&amp;gt;" | htmlUnescape | htmlUnescape | safeHTML}}`,
`htmlUnescape 2: Cathal Garvey & The Sunshine Band <cathal@foo.bar>`},
{
`htmlUnescape 3: {{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;cathal@foo.bar&amp;gt;" | htmlUnescape | htmlUnescape }}`,
`htmlUnescape 3: Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;`},
{
`htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlUnescape | safeHTML }}`,
`htmlUnescape 4: Cathal Garvey & The Sunshine Band <cathal@foo.bar>`},
{
`htmlUnescape 5: {{ htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlEscape | safeHTML }}`,
`htmlUnescape 5: Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"emojify": ctx.Emojify,
"highlight": ctx.Highlight,
"htmlEscape": ctx.HTMLEscape,
"htmlUnescape": ctx.HTMLUnescape,
"markdownify": ctx.Markdownify,
"plainify": ctx.Plainify,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.Emojify,
[]string{"emojify"},
[][2]string{
{`{{ "I :heart: Hugo" | emojify }}`, `I ❤️ Hugo`},
},
)
ns.AddMethodMapping(ctx.Highlight,
[]string{"highlight"},
[][2]string{},
)
ns.AddMethodMapping(ctx.HTMLEscape,
[]string{"htmlEscape"},
[][2]string{
{
`{{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML}}`,
`Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;`},
{
`{{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>"}}`,
`Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;cathal@foo.bar&amp;gt;`},
{
`{{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlUnescape | safeHTML }}`,
`Cathal Garvey & The Sunshine Band <cathal@foo.bar>`},
},
)
ns.AddMethodMapping(ctx.HTMLUnescape,
[]string{"htmlUnescape"},
[][2]string{
{
`{{ htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | safeHTML}}`,
`Cathal Garvey & The Sunshine Band <cathal@foo.bar>`},
{
`{{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;cathal@foo.bar&amp;gt;" | htmlUnescape | htmlUnescape | safeHTML}}`,
`Cathal Garvey & The Sunshine Band <cathal@foo.bar>`},
{
`{{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;cathal@foo.bar&amp;gt;" | htmlUnescape | htmlUnescape }}`,
`Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;`},
{
`{{ htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlEscape | safeHTML }}`,
`Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;`},
},
)
ns.AddMethodMapping(ctx.Markdownify,
[]string{"markdownify"},
[][2]string{
{`{{ .Title | markdownify}}`, `<strong>BatMan</strong>`},
},
)
ns.AddMethodMapping(ctx.Plainify,
[]string{"plainify"},
[][2]string{
{`{{ plainify "Hello <strong>world</strong>, gophers!" }}`, `Hello world, gophers!`},
},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)

View file

@ -24,33 +24,43 @@ func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New(d) ctx := New(d)
examples := [][2]string{ ns := &internal.TemplateFuncsNamespace{
{`{{ "index.html" | absLangURL }}`, `http://mysite.com/hugo/en/index.html`},
{`{{ "http://gohugo.io/" | absURL }}`, `http://gohugo.io/`},
{`{{ "mystyle.css" | absURL }}`, `http://mysite.com/hugo/mystyle.css`},
{`{{ 42 | absURL }}`, `http://mysite.com/hugo/42`},
{`{{ "index.html" | relLangURL }}`, `/hugo/en/index.html`},
{`{{ "http://gohugo.io/" | relURL }}`, `http://gohugo.io/`},
{`{{ "mystyle.css" | relURL }}`, `/hugo/mystyle.css`},
{`{{ mul 2 21 | relURL }}`, `/hugo/42`},
{`{{ "Bat Man" | urlize }}`, `bat-man`},
}
return &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func() interface{} { return ctx }, Context: func() interface{} { return ctx },
Aliases: map[string]interface{}{
"absURL": ctx.AbsURL,
"absLangURL": ctx.AbsLangURL,
"ref": ctx.Ref,
"relURL": ctx.RelURL,
"relLangURL": ctx.RelLangURL,
"relref": ctx.RelRef,
"urlize": ctx.URLize,
},
Examples: examples,
} }
ns.AddMethodMapping(ctx.AbsURL,
[]string{"absURL"},
[][2]string{},
)
ns.AddMethodMapping(ctx.AbsLangURL,
[]string{"absLangURL"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Ref,
[]string{"ref"},
[][2]string{},
)
ns.AddMethodMapping(ctx.RelURL,
[]string{"relURL"},
[][2]string{},
)
ns.AddMethodMapping(ctx.RelLangURL,
[]string{"relLangURL"},
[][2]string{},
)
ns.AddMethodMapping(ctx.RelRef,
[]string{"relref"},
[][2]string{},
)
ns.AddMethodMapping(ctx.URLize,
[]string{"urlize"},
[][2]string{},
)
return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)