diff --git a/tpl/cast/cast.go b/tpl/cast/cast.go new file mode 100644 index 000000000..d2dfd745f --- /dev/null +++ b/tpl/cast/cast.go @@ -0,0 +1,35 @@ +// 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 ( + _cast "github.com/spf13/cast" +) + +// New returns a new instance of the cast-namespaced template functions. +func New() *Namespace { + return &Namespace{} +} + +// Namespace provides template functions for the "cast" namespace. +type Namespace struct { +} + +func (ns *Namespace) ToInt(v interface{}) (int, error) { + return _cast.ToIntE(v) +} + +func (ns *Namespace) ToString(v interface{}) (string, error) { + return _cast.ToStringE(v) +} diff --git a/tpl/cast/init.go b/tpl/cast/init.go new file mode 100644 index 000000000..acddaa91a --- /dev/null +++ b/tpl/cast/init.go @@ -0,0 +1,45 @@ +// 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/tpl/internal" +) + +const name = "cast" + +func init() { + f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { + ctx := New() + + examples := [][2]string{ + {`{{ "1234" | int | printf "%T" }}`, `int`}, + {`{{ 1234 | string | printf "%T" }}`, `string`}, + } + + return &internal.TemplateFuncsNamespace{ + Name: name, + Context: func() interface{} { return ctx }, + Aliases: map[string]interface{}{ + "int": ctx.ToInt, + "string": ctx.ToString, + }, + Examples: examples, + } + + } + + internal.AddTemplateFuncsNamespace(f) +} diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go index 86674f423..0843fb7bc 100644 --- a/tpl/collections/collections.go +++ b/tpl/collections/collections.go @@ -39,9 +39,6 @@ type Namespace struct { deps *deps.Deps } -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { return ns } - // After returns all the items after the first N in a rangeable list. func (ns *Namespace) After(index interface{}, seq interface{}) (interface{}, error) { if index == nil || seq == nil { diff --git a/tpl/collections/collections_test.go b/tpl/collections/collections_test.go index 9d34d3be0..eefbcef6c 100644 --- a/tpl/collections/collections_test.go +++ b/tpl/collections/collections_test.go @@ -29,14 +29,6 @@ import ( type tstNoStringer struct{} -func TestNamespace(t *testing.T) { - t.Parallel() - - ns := New(&deps.Deps{}) - - assert.Equal(t, ns, ns.Namespace(), "object pointers should match") -} - func TestAfter(t *testing.T) { t.Parallel() diff --git a/tpl/collections/init.go b/tpl/collections/init.go index ded7b803c..29f6809c3 100644 --- a/tpl/collections/init.go +++ b/tpl/collections/init.go @@ -25,18 +25,18 @@ func init() { ctx := New(d) examples := [][2]string{ - {`delimit: {{ delimit (slice "A" "B" "C") ", " " and " }}`, `delimit: A, B and C`}, - {`echoParam: {{ echoParam .Params "langCode" }}`, `echoParam: en`}, - {`in: {{ if in "this string contains a substring" "substring" }}Substring found!{{ end }}`, `in: Substring found!`}, + {`{{ 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 1: {{ (querify "foo" 1 "bar" 2 "baz" "with spaces" "qux" "this&that=those") | safeHTML }}`, - `querify 1: bar=2&baz=with+spaces&foo=1&qux=this%26that%3Dthose`}, + `{{ (querify "foo" 1 "bar" 2 "baz" "with spaces" "qux" "this&that=those") | safeHTML }}`, + `bar=2&baz=with+spaces&foo=1&qux=this%26that%3Dthose`}, { - `querify 2: Search`, - `querify 2: Search`}, - {`sort: {{ slice "B" "C" "A" | sort }}`, `sort: [A B C]`}, - {`seq: {{ seq 3 }}`, `seq: [1 2 3]`}, - {`union: {{ union (slice 1 2 3) (slice 3 4 5) }}`, `union: [1 2 3 4 5]`}, + `Search`, + `Search`}, + {`{{ 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{ diff --git a/tpl/compare/init.go b/tpl/compare/init.go index d8d30d6f4..bf8227353 100644 --- a/tpl/compare/init.go +++ b/tpl/compare/init.go @@ -25,7 +25,9 @@ func init() { ctx := New() examples := [][2]string{ - {`eq: {{ if eq .Section "blog" }}current{{ end }}`, `eq: current`}, + {`{{ if eq .Section "blog" }}current{{ end }}`, `current`}, + {`{{ "Hugo Rocks!" | default "Hugo Rules!" }}`, `Hugo Rocks!`}, + {`{{ "" | default "Hugo Rules!" }}`, `Hugo Rules!`}, } return &internal.TemplateFuncsNamespace{ diff --git a/tpl/crypto/crypto.go b/tpl/crypto/crypto.go index 207e4df39..7aaa9291e 100644 --- a/tpl/crypto/crypto.go +++ b/tpl/crypto/crypto.go @@ -30,9 +30,6 @@ func New() *Namespace { // Namespace provides template functions for the "crypto" namespace. type Namespace struct{} -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { return ns } - // MD5 hashes the given input and returns its MD5 checksum. func (ns *Namespace) MD5(in interface{}) (string, error) { conv, err := cast.ToStringE(in) diff --git a/tpl/crypto/crypto_test.go b/tpl/crypto/crypto_test.go index 53b41bd26..1bd919c31 100644 --- a/tpl/crypto/crypto_test.go +++ b/tpl/crypto/crypto_test.go @@ -21,14 +21,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestNamespace(t *testing.T) { - t.Parallel() - - ns := New() - - assert.Equal(t, ns, ns.Namespace(), "object pointers should match") -} - func TestMD5(t *testing.T) { t.Parallel() diff --git a/tpl/crypto/init.go b/tpl/crypto/init.go index 81e9b3a1a..7c1899f88 100644 --- a/tpl/crypto/init.go +++ b/tpl/crypto/init.go @@ -26,6 +26,7 @@ func init() { examples := [][2]string{ {`{{ md5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`}, + {`{{ crypto.MD5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`}, {`{{ sha1 "Hello world, gophers!" }}`, `c8b5b0e33d408246e30f53e32b8f7627a7a649d4`}, {`{{ sha256 "Hello world, gophers!" }}`, `6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46`}, } diff --git a/tpl/data/data.go b/tpl/data/data.go index 39cbc9b19..a5bba32c3 100644 --- a/tpl/data/data.go +++ b/tpl/data/data.go @@ -26,6 +26,3 @@ func New(deps *deps.Deps) *Namespace { type Namespace struct { deps *deps.Deps } - -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { return ns } diff --git a/tpl/encoding/encoding.go b/tpl/encoding/encoding.go index 311edb209..4b02c426a 100644 --- a/tpl/encoding/encoding.go +++ b/tpl/encoding/encoding.go @@ -29,9 +29,6 @@ func New() *Namespace { // Namespace provides template functions for the "encoding" namespace. type Namespace struct{} -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { return ns } - // Base64Decode returns the base64 decoding of the given content. func (ns *Namespace) Base64Decode(content interface{}) (string, error) { conv, err := cast.ToStringE(content) diff --git a/tpl/encoding/encoding_test.go b/tpl/encoding/encoding_test.go index d03362866..8242561b6 100644 --- a/tpl/encoding/encoding_test.go +++ b/tpl/encoding/encoding_test.go @@ -25,14 +25,6 @@ import ( type tstNoStringer struct{} -func TestNamespace(t *testing.T) { - t.Parallel() - - ns := New() - - assert.Equal(t, ns, ns.Namespace(), "object pointers should match") -} - func TestBase64Decode(t *testing.T) { t.Parallel() diff --git a/tpl/fmt/fmt.go b/tpl/fmt/fmt.go new file mode 100644 index 000000000..5e320fede --- /dev/null +++ b/tpl/fmt/fmt.go @@ -0,0 +1,39 @@ +// 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 fmt + +import ( + _fmt "fmt" +) + +// New returns a new instance of the fmt-namespaced template functions. +func New() *Namespace { + return &Namespace{} +} + +// Namespace provides template functions for the "fmt" namespace. +type Namespace struct { +} + +func (ns *Namespace) Print(a ...interface{}) (n int, err error) { + return _fmt.Print(a...) +} + +func (ns *Namespace) Printf(format string, a ...interface{}) (n int, err error) { + return _fmt.Printf(format, a...) +} + +func (ns *Namespace) Println(a ...interface{}) (n int, err error) { + return _fmt.Println(a...) +} diff --git a/tpl/fmt/init.go b/tpl/fmt/init.go new file mode 100644 index 000000000..0f4296263 --- /dev/null +++ b/tpl/fmt/init.go @@ -0,0 +1,43 @@ +// 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 fmt + +import ( + "github.com/spf13/hugo/deps" + "github.com/spf13/hugo/tpl/internal" +) + +const name = "fmt" + +func init() { + f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { + ctx := New() + + examples := [][2]string{ + {`{{ print "works!" }}`, `works!`}, + {`{{ printf "%s!" "works" }}`, `works!`}, + {`{{ println "works!" }}`, "works!\n"}, + } + + return &internal.TemplateFuncsNamespace{ + Name: name, + Context: func() interface{} { return ctx }, + Aliases: map[string]interface{}{}, + Examples: examples, + } + + } + + internal.AddTemplateFuncsNamespace(f) +} diff --git a/tpl/images/images.go b/tpl/images/images.go index 6700603dd..127057853 100644 --- a/tpl/images/images.go +++ b/tpl/images/images.go @@ -43,9 +43,6 @@ type Namespace struct { deps *deps.Deps } -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { return ns } - // Config returns the image.Config for the specified path relative to the // working directory. func (ns *Namespace) Config(path interface{}) (image.Config, error) { diff --git a/tpl/images/images_test.go b/tpl/images/images_test.go index 740a469af..964d3fb91 100644 --- a/tpl/images/images_test.go +++ b/tpl/images/images_test.go @@ -80,17 +80,6 @@ var configTests = []struct { {path: "", expect: false}, } -func TestNamespace(t *testing.T) { - t.Parallel() - - v := viper.New() - v.Set("workingDir", "/a/b") - - ns := New(&deps.Deps{Fs: hugofs.NewMem(v)}) - - assert.Equal(t, ns, ns.Namespace(), "object pointers should match") -} - func TestNSConfig(t *testing.T) { t.Parallel() diff --git a/tpl/inflect/inflect.go b/tpl/inflect/inflect.go index 9c13238b5..e66aee72f 100644 --- a/tpl/inflect/inflect.go +++ b/tpl/inflect/inflect.go @@ -28,9 +28,6 @@ func New() *Namespace { // Namespace provides template functions for the "inflect" namespace. type Namespace struct{} -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { return ns } - // Humanize returns the humanized form of a single parameter. // // If the parameter is either an integer or a string containing an integer diff --git a/tpl/inflect/inflect_test.go b/tpl/inflect/inflect_test.go index 028d5d9af..a2146a838 100644 --- a/tpl/inflect/inflect_test.go +++ b/tpl/inflect/inflect_test.go @@ -8,14 +8,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestNamespace(t *testing.T) { - t.Parallel() - - ns := New() - - assert.Equal(t, ns, ns.Namespace(), "object pointers should match") -} - func TestInflect(t *testing.T) { t.Parallel() diff --git a/tpl/lang/lang.go b/tpl/lang/lang.go index cd6e7c563..c84728f3b 100644 --- a/tpl/lang/lang.go +++ b/tpl/lang/lang.go @@ -30,10 +30,6 @@ type Namespace struct { deps *deps.Deps } -// Namespace returns a pointer to the current namespace instance. -// TODO(bep) namespace remove this and other unused when done. -func (ns *Namespace) Namespace() *Namespace { return ns } - // Translate ... func (ns *Namespace) Translate(id interface{}, args ...interface{}) (string, error) { sid, err := cast.ToStringE(id) diff --git a/tpl/math/math.go b/tpl/math/math.go index 47b7b8306..57e7baf12 100644 --- a/tpl/math/math.go +++ b/tpl/math/math.go @@ -26,9 +26,6 @@ func New() *Namespace { // Namespace provides template functions for the "math" namespace. type Namespace struct{} -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { return ns } - func (ns *Namespace) Add(a, b interface{}) (interface{}, error) { return DoArithmetic(a, b, '+') } diff --git a/tpl/math/math_test.go b/tpl/math/math_test.go index 649a2756e..40bed5539 100644 --- a/tpl/math/math_test.go +++ b/tpl/math/math_test.go @@ -21,14 +21,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestNamespace(t *testing.T) { - t.Parallel() - - ns := New() - - assert.Equal(t, ns, ns.Namespace(), "object pointers should match") -} - func TestBasicNSArithmetic(t *testing.T) { t.Parallel() diff --git a/tpl/os/init.go b/tpl/os/init.go index 4ab1c56ca..d03ad5f03 100644 --- a/tpl/os/init.go +++ b/tpl/os/init.go @@ -25,7 +25,7 @@ func init() { ctx := New(d) examples := [][2]string{ - {`{{ range (readDir ".") }}{{ .Name }}{{ end }}`, `README.txt`}, + {`{{ range (readDir ".") }}{{ .Name }}{{ end }}`, "README.txt"}, {`{{ readFile "README.txt" }}`, `Hugo Rocks!`}, } diff --git a/tpl/os/os.go b/tpl/os/os.go index 91d6e14f6..03e16e5a9 100644 --- a/tpl/os/os.go +++ b/tpl/os/os.go @@ -35,9 +35,6 @@ type Namespace struct { deps *deps.Deps } -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { return ns } - // Getenv retrieves the value of the environment variable named by the key. // It returns the value, which will be empty if the variable is not present. func (ns *Namespace) Getenv(key interface{}) (string, error) { diff --git a/tpl/partials/init.go b/tpl/partials/init.go new file mode 100644 index 000000000..a9d1f82fe --- /dev/null +++ b/tpl/partials/init.go @@ -0,0 +1,44 @@ +// 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 partials + +import ( + "github.com/spf13/hugo/deps" + "github.com/spf13/hugo/tpl/internal" +) + +const name = "partials" + +func init() { + f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { + ctx := New(d) + + examples := [][2]string{ + {`{{ partial "header.html" . }}`, `Hugo Rocks!`}, + } + + return &internal.TemplateFuncsNamespace{ + Name: name, + Context: func() interface{} { return ctx }, + Aliases: map[string]interface{}{ + "partial": ctx.Include, + "partialCached": ctx.getCached, + }, + Examples: examples, + } + + } + + internal.AddTemplateFuncsNamespace(f) +} diff --git a/tpl/partials/partials.go b/tpl/partials/partials.go new file mode 100644 index 000000000..a57edcaec --- /dev/null +++ b/tpl/partials/partials.go @@ -0,0 +1,130 @@ +// 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 partials + +import ( + "fmt" + "html/template" + "strings" + "sync" + texttemplate "text/template" + + bp "github.com/spf13/hugo/bufferpool" + "github.com/spf13/hugo/deps" +) + +var TestTemplateProvider deps.ResourceProvider + +// partialCache represents a cache of partials protected by a mutex. +type partialCache struct { + sync.RWMutex + p map[string]interface{} +} + +// New returns a new instance of the templates-namespaced template functions. +func New(deps *deps.Deps) *Namespace { + return &Namespace{ + deps: deps, + cachedPartials: partialCache{p: make(map[string]interface{})}, + } +} + +// Namespace provides template functions for the "templates" namespace. +type Namespace struct { + deps *deps.Deps + cachedPartials partialCache +} + +func (ns *Namespace) Foo(i interface{}) { + +} + +// Include executes the named partial and returns either a string, +// when the partial is a text/template, or template.HTML when html/template. +func (ns *Namespace) Include(name string, contextList ...interface{}) (interface{}, error) { + if strings.HasPrefix("partials/", name) { + name = name[8:] + } + var context interface{} + + if len(contextList) == 0 { + context = nil + } else { + context = contextList[0] + } + + for _, n := range []string{"partials/" + name, "theme/partials/" + name} { + templ := ns.deps.Tmpl.Lookup(n) + if templ == nil { + // For legacy reasons. + templ = ns.deps.Tmpl.Lookup(n + ".html") + } + if templ != nil { + b := bp.GetBuffer() + defer bp.PutBuffer(b) + + if err := templ.Execute(b, context); err != nil { + return "", err + } + + if _, ok := templ.Template.(*texttemplate.Template); ok { + return b.String(), nil + } + + return template.HTML(b.String()), nil + + } + } + + return "", fmt.Errorf("Partial %q not found", name) +} + +// getCached executes and caches partial templates. An optional variant +// string parameter (a string slice actually, but be only use a variadic +// argument to make it optional) can be passed so that a given partial can have +// multiple uses. The cache is created with name+variant as the key. +func (ns *Namespace) getCached(name string, context interface{}, variant ...string) (interface{}, error) { + key := name + if len(variant) > 0 { + for i := 0; i < len(variant); i++ { + key += variant[i] + } + } + return ns.getOrCreate(key, name, context) +} + +func (ns *Namespace) getOrCreate(key, name string, context interface{}) (p interface{}, err error) { + var ok bool + + ns.cachedPartials.RLock() + p, ok = ns.cachedPartials.p[key] + ns.cachedPartials.RUnlock() + + if ok { + return + } + + ns.cachedPartials.Lock() + if p, ok = ns.cachedPartials.p[key]; !ok { + ns.cachedPartials.Unlock() + p, err = ns.Include(name, context) + + ns.cachedPartials.Lock() + ns.cachedPartials.p[key] = p + + } + ns.cachedPartials.Unlock() + + return +} diff --git a/tpl/safe/safe.go b/tpl/safe/safe.go index 64f383703..de2718a98 100644 --- a/tpl/safe/safe.go +++ b/tpl/safe/safe.go @@ -28,9 +28,6 @@ func New() *Namespace { // Namespace provides template functions for the "safe" namespace. type Namespace struct{} -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { return ns } - // CSS returns a given string as html/template CSS content. func (ns *Namespace) CSS(a interface{}) (template.CSS, error) { s, err := cast.ToStringE(a) diff --git a/tpl/safe/safe_test.go b/tpl/safe/safe_test.go index ae58d9784..346b448c9 100644 --- a/tpl/safe/safe_test.go +++ b/tpl/safe/safe_test.go @@ -24,14 +24,6 @@ import ( type tstNoStringer struct{} -func TestNamespace(t *testing.T) { - t.Parallel() - - ns := New() - - assert.Equal(t, ns, ns.Namespace(), "object pointers should match") -} - func TestCSS(t *testing.T) { t.Parallel() diff --git a/tpl/strings/strings.go b/tpl/strings/strings.go index 32c5c00ae..bba25677c 100644 --- a/tpl/strings/strings.go +++ b/tpl/strings/strings.go @@ -37,9 +37,6 @@ type Namespace struct { deps *deps.Deps } -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { return ns } - // CountRunes returns the number of runes in s, excluding whitepace. func (ns *Namespace) CountRunes(s interface{}) (int, error) { ss, err := cast.ToStringE(s) diff --git a/tpl/strings/strings_test.go b/tpl/strings/strings_test.go index 9164729fe..0c8919be6 100644 --- a/tpl/strings/strings_test.go +++ b/tpl/strings/strings_test.go @@ -27,11 +27,6 @@ var ns = New(&deps.Deps{}) type tstNoStringer struct{} -func TestNamespace(t *testing.T) { - t.Parallel() - assert.Equal(t, ns, ns.Namespace(), "object pointers should match") -} - func TestChomp(t *testing.T) { t.Parallel() diff --git a/tpl/time/init.go b/tpl/time/init.go index dfa2cbfe9..d91c1376f 100644 --- a/tpl/time/init.go +++ b/tpl/time/init.go @@ -26,6 +26,7 @@ func init() { examples := [][2]string{ {`{{ (time "2015-01-21").Year }}`, `2015`}, + {`dateFormat: {{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }}`, `dateFormat: Wednesday, Jan 21, 2015`}, } return &internal.TemplateFuncsNamespace{ diff --git a/tpl/time/time.go b/tpl/time/time.go index fab2b9266..889650c98 100644 --- a/tpl/time/time.go +++ b/tpl/time/time.go @@ -27,9 +27,6 @@ func New() *Namespace { // Namespace provides template functions for the "time" namespace. type Namespace struct{} -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { return ns } - // AsTime converts the textual representation of the datetime string into // a time.Time interface. func (ns *Namespace) AsTime(v interface{}) (interface{}, error) { diff --git a/tpl/time/time_test.go b/tpl/time/time_test.go index c84d99235..2c54dacc6 100644 --- a/tpl/time/time_test.go +++ b/tpl/time/time_test.go @@ -16,18 +16,8 @@ package time import ( "testing" "time" - - "github.com/stretchr/testify/assert" ) -func TestNamespace(t *testing.T) { - t.Parallel() - - ns := New() - - assert.Equal(t, ns, ns.Namespace(), "object pointers should match") -} - func TestFormat(t *testing.T) { t.Parallel() diff --git a/tpl/tplimpl/templateFuncster.go b/tpl/tplimpl/templateFuncster.go index c37d7f937..656cd89d3 100644 --- a/tpl/tplimpl/templateFuncster.go +++ b/tpl/tplimpl/templateFuncster.go @@ -25,16 +25,14 @@ import ( // Some of the template funcs are'nt entirely stateless. type templateFuncster struct { - funcMap template.FuncMap - cachedPartials partialCache + funcMap template.FuncMap *deps.Deps } func newTemplateFuncster(deps *deps.Deps) *templateFuncster { return &templateFuncster{ - Deps: deps, - cachedPartials: partialCache{p: make(map[string]interface{})}, + Deps: deps, } } diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go index d1a2dd73a..089463a1e 100644 --- a/tpl/tplimpl/template_funcs.go +++ b/tpl/tplimpl/template_funcs.go @@ -1,4 +1,4 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. +// Copyright 2017-present The Hugo Authors. All rights reserved. // // Portions Copyright The Go Authors. @@ -16,24 +16,24 @@ package tplimpl import ( - "fmt" "html/template" - "sync" - "github.com/spf13/cast" "github.com/spf13/hugo/tpl/internal" // Init the namespaces + _ "github.com/spf13/hugo/tpl/cast" _ "github.com/spf13/hugo/tpl/collections" _ "github.com/spf13/hugo/tpl/compare" _ "github.com/spf13/hugo/tpl/crypto" _ "github.com/spf13/hugo/tpl/data" _ "github.com/spf13/hugo/tpl/encoding" + _ "github.com/spf13/hugo/tpl/fmt" _ "github.com/spf13/hugo/tpl/images" _ "github.com/spf13/hugo/tpl/inflect" _ "github.com/spf13/hugo/tpl/lang" _ "github.com/spf13/hugo/tpl/math" _ "github.com/spf13/hugo/tpl/os" + _ "github.com/spf13/hugo/tpl/partials" _ "github.com/spf13/hugo/tpl/safe" _ "github.com/spf13/hugo/tpl/strings" _ "github.com/spf13/hugo/tpl/time" @@ -41,74 +41,15 @@ import ( _ "github.com/spf13/hugo/tpl/urls" ) -// Get retrieves partial output from the cache based upon the partial name. -// If the partial is not found in the cache, the partial is rendered and added -// to the cache. -func (t *templateFuncster) Get(key, name string, context interface{}) (p interface{}, err error) { - var ok bool - - t.cachedPartials.RLock() - p, ok = t.cachedPartials.p[key] - t.cachedPartials.RUnlock() - - if ok { - return - } - - t.cachedPartials.Lock() - if p, ok = t.cachedPartials.p[key]; !ok { - t.cachedPartials.Unlock() - p, err = t.partial(name, context) - - t.cachedPartials.Lock() - t.cachedPartials.p[key] = p - - } - t.cachedPartials.Unlock() - - return -} - -// partialCache represents a cache of partials protected by a mutex. -type partialCache struct { - sync.RWMutex - p map[string]interface{} -} - -// partialCached executes and caches partial templates. An optional variant -// string parameter (a string slice actually, but be only use a variadic -// argument to make it optional) can be passed so that a given partial can have -// multiple uses. The cache is created with name+variant as the key. -func (t *templateFuncster) partialCached(name string, context interface{}, variant ...string) (interface{}, error) { - key := name - if len(variant) > 0 { - for i := 0; i < len(variant); i++ { - key += variant[i] - } - } - return t.Get(key, name, context) -} - func (t *templateFuncster) initFuncMap() { - funcMap := template.FuncMap{ - // Namespaces - //"time": t.time.Namespace, - "int": func(v interface{}) (int, error) { return cast.ToIntE(v) }, - "partial": t.partial, - "partialCached": t.partialCached, - "print": fmt.Sprint, - "printf": fmt.Sprintf, - "println": fmt.Sprintln, - "string": func(v interface{}) (string, error) { return cast.ToStringE(v) }, - "urlize": t.PathSpec.URLize, - } + funcMap := template.FuncMap{} // Merge the namespace funcs for _, nsf := range internal.TemplateFuncsNamespaceRegistry { ns := nsf(t.Deps) - // TODO(bep) namespace ns.Context is a dummy func just to make this work. - // Consider if we can add this context to the rendering context in an easy - // way to make this cleaner. Maybe. + if _, exists := funcMap[ns.Name]; exists { + panic(ns.Name + " is a duplicate template func") + } funcMap[ns.Name] = ns.Context for k, v := range ns.Aliases { funcMap[k] = v diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go index 186da511b..b0a6e87b7 100644 --- a/tpl/tplimpl/template_funcs_test.go +++ b/tpl/tplimpl/template_funcs_test.go @@ -18,7 +18,6 @@ import ( "fmt" "path/filepath" "reflect" - "strings" "testing" "io/ioutil" @@ -92,6 +91,7 @@ func TestTemplateFuncsExamples(t *testing.T) { in, expected := example[0], example[1] d.WithTemplate = func(templ tpl.TemplateHandler) error { require.NoError(t, templ.AddTemplate("test", in)) + require.NoError(t, templ.AddTemplate("partials/header.html", "Hugo Rocks!")) return nil } require.NoError(t, d.LoadResources()) @@ -106,134 +106,8 @@ func TestTemplateFuncsExamples(t *testing.T) { } -func TestFuncsInTemplate(t *testing.T) { - t.Parallel() - - workingDir := "/home/hugo" - - v := viper.New() - - v.Set("workingDir", workingDir) - v.Set("multilingual", true) - - fs := hugofs.NewMem(v) - - afero.WriteFile(fs.Source, filepath.Join(workingDir, "README.txt"), []byte("Hugo Rocks!"), 0755) - - // Add the examples from the docs: As a smoke test and to make sure the examples work. - // TODO(bep): docs: fix title example - // TODO(bep) namespace remove when done - in := - ` -crypto.MD5: {{ crypto.MD5 "Hello world, gophers!" }} -dateFormat: {{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }} -htmlEscape 1: {{ htmlEscape "Cathal Garvey & The Sunshine Band " | safeHTML}} -htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band "}} -htmlUnescape 1: {{htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML}} -htmlUnescape 2: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape | safeHTML}} -htmlUnescape 3: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape }} -htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band " | htmlUnescape | safeHTML }} -htmlUnescape 5: {{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlEscape | safeHTML }} -print: {{ print "works!" }} -printf: {{ printf "%s!" "works" }} -println: {{ println "works!" -}} -strings.TrimPrefix: {{ strings.TrimPrefix "Goodbye,, world!" "Goodbye," }} -urlize: {{ "Bat Man" | urlize }} -` - - expected := ` -crypto.MD5: b3029f756f98f79e7f1b7f1d1f0dd53b -dateFormat: Wednesday, Jan 21, 2015 -htmlEscape 1: Cathal Garvey & The Sunshine Band <cathal@foo.bar> -htmlEscape 2: Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt; -htmlUnescape 1: Cathal Garvey & The Sunshine Band -htmlUnescape 2: Cathal Garvey & The Sunshine Band -htmlUnescape 3: Cathal Garvey & The Sunshine Band <cathal@foo.bar> -htmlUnescape 4: Cathal Garvey & The Sunshine Band -htmlUnescape 5: Cathal Garvey & The Sunshine Band <cathal@foo.bar> -print: works! -printf: works! -println: works! -strings.TrimPrefix: , world! -urlize: bat-man -` - - var b bytes.Buffer - - var data struct { - Title string - Section string - Params map[string]interface{} - } - - data.Title = "**BatMan**" - data.Section = "blog" - data.Params = map[string]interface{}{"langCode": "en"} - - v.Set("baseURL", "http://mysite.com/hugo/") - v.Set("CurrentContentLanguage", helpers.NewLanguage("en", v)) - - config := newDepsConfig(v) - config.WithTemplate = func(templ tpl.TemplateHandler) error { - if err := templ.AddTemplate("test", in); err != nil { - t.Fatal("Got error on parse", err) - } - return nil - } - config.Fs = fs - - d, err := deps.New(config) - if err != nil { - t.Fatal(err) - } - - if err := d.LoadResources(); err != nil { - t.Fatal(err) - } - - err = d.Tmpl.Lookup("test").Execute(&b, &data) - - if err != nil { - t.Fatal("Got error on execute", err) - } - - if b.String() != expected { - sl1 := strings.Split(b.String(), "\n") - sl2 := strings.Split(expected, "\n") - t.Errorf("Diff:\n%q", helpers.DiffStringSlices(sl1, sl2)) - } -} - -func TestDefault(t *testing.T) { - t.Parallel() - for i, this := range []struct { - input interface{} - tpl string - expected string - ok bool - }{ - {map[string]string{"foo": "bar"}, `{{ index . "foo" | default "nope" }}`, `bar`, true}, - {map[string]string{"foo": "pop"}, `{{ index . "bar" | default "nada" }}`, `nada`, true}, - {map[string]string{"foo": "cat"}, `{{ default "nope" .foo }}`, `cat`, true}, - {map[string]string{"foo": "dog"}, `{{ default "nope" .foo "extra" }}`, ``, false}, - {map[string]interface{}{"images": []string{}}, `{{ default "default.jpg" (index .images 0) }}`, `default.jpg`, true}, - } { - - tmpl := newTestTemplate(t, "test", this.tpl) - - buf := new(bytes.Buffer) - err := tmpl.Execute(buf, this.input) - if (err == nil) != this.ok { - t.Errorf("[%d] execute template returned unexpected error: %s", i, err) - continue - } - - if buf.String() != this.expected { - t.Errorf("[%d] execute template got %v, but expected %v", i, buf.String(), this.expected) - } - } -} - +// TODO(bep) it would be dandy to put this one into the partials package, but +// we have some package cycle issues to solve first. func TestPartialCached(t *testing.T) { t.Parallel() testCases := []struct { @@ -388,20 +262,3 @@ func newTestFuncsterWithViper(v *viper.Viper) *templateFuncster { return d.Tmpl.(*templateHandler).html.funcster } - -func newTestTemplate(t *testing.T, name, template string) tpl.Template { - config := newDepsConfig(viper.New()) - config.WithTemplate = func(templ tpl.TemplateHandler) error { - err := templ.AddTemplate(name, template) - if err != nil { - return err - } - return nil - } - - de, err := deps.New(config) - require.NoError(t, err) - require.NoError(t, de.LoadResources()) - - return de.Tmpl.Lookup(name) -} diff --git a/tpl/tplimpl/template_test.go b/tpl/tplimpl/template_test.go deleted file mode 100644 index 998915a46..000000000 --- a/tpl/tplimpl/template_test.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2016 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 tplimpl - -import ( - "errors" - "io/ioutil" - "testing" - - "github.com/spf13/hugo/deps" - - "github.com/spf13/hugo/tpl" - "github.com/spf13/viper" - "github.com/stretchr/testify/require" -) - -// Test for bugs discovered by https://github.com/dvyukov/go-fuzz -func TestTplGoFuzzReports(t *testing.T) { - t.Parallel() - - // The following test case(s) also fail - // See https://github.com/golang/go/issues/10634 - //{"{{ seq 433937734937734969526500969526500 }}", 2}} - - for i, this := range []struct { - data string - expectErr int - }{ - // Issue #1089 - //{"{{apply .C \"first\" }}", 2}, - // Issue #1090 - {"{{ slicestr \"000000\" 10}}", 2}, - // Issue #1091 - //{"{{apply .C \"first\" 0 0 0}}", 2}, - {"{{seq 3e80}}", 2}, - // Issue #1095 - {"{{apply .C \"urlize\" " + - "\".\"}}", 2}} { - - d := &Data{ - A: 42, - B: "foo", - C: []int{1, 2, 3}, - D: map[int]string{1: "foo", 2: "bar"}, - E: Data1{42, "foo"}, - F: []string{"a", "b", "c"}, - G: []string{"a", "b", "c", "d", "e"}, - H: "a,b,c,d,e,f", - } - - config := newDepsConfig(viper.New()) - - config.WithTemplate = func(templ tpl.TemplateHandler) error { - return templ.AddTemplate("fuzz", this.data) - } - - de, err := deps.New(config) - require.NoError(t, err) - require.NoError(t, de.LoadResources()) - - templ := de.Tmpl.(*templateHandler) - - if len(templ.errors) > 0 && this.expectErr == 0 { - t.Errorf("Test %d errored: %v", i, templ.errors) - } else if len(templ.errors) == 0 && this.expectErr == 1 { - t.Errorf("#1 Test %d should have errored", i) - } - - tt := de.Tmpl.Lookup("fuzz") - require.NotNil(t, tt) - err = tt.Execute(ioutil.Discard, d) - - if err != nil && this.expectErr == 0 { - t.Fatalf("Test %d errored: %s", i, err) - } else if err == nil && this.expectErr == 2 { - t.Fatalf("#2 Test %d should have errored", i) - } - - } -} - -type Data struct { - A int - B string - C []int - D map[int]string - E Data1 - F []string - G []string - H string -} - -type Data1 struct { - A int - B string -} - -func (Data1) Q() string { - return "foo" -} - -func (Data1) W() (string, error) { - return "foo", nil -} - -func (Data1) E() (string, error) { - return "foo", errors.New("Data.E error") -} - -func (Data1) R(v int) (string, error) { - return "foo", nil -} - -func (Data1) T(s string) (string, error) { - return s, nil -} diff --git a/tpl/transform/init.go b/tpl/transform/init.go index 3057cf068..98994c0e6 100644 --- a/tpl/transform/init.go +++ b/tpl/transform/init.go @@ -28,6 +28,27 @@ func init() { {`{{ "I :heart: Hugo" | emojify }}`, `I ❤️ Hugo`}, {`{{ .Title | markdownify}}`, `BatMan`}, {`{{ plainify "Hello world, gophers!" }}`, `Hello world, gophers!`}, + { + `htmlEscape 1: {{ htmlEscape "Cathal Garvey & The Sunshine Band " | safeHTML}}`, + `htmlEscape 1: Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, + { + `htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band "}}`, + `htmlEscape 2: Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;`}, + { + `htmlUnescape 1: {{htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML}}`, + `htmlUnescape 1: Cathal Garvey & The Sunshine Band `}, + { + `htmlUnescape 2: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape | safeHTML}}`, + `htmlUnescape 2: Cathal Garvey & The Sunshine Band `}, + { + `htmlUnescape 3: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape }}`, + `htmlUnescape 3: Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, + { + `htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band " | htmlUnescape | safeHTML }}`, + `htmlUnescape 4: Cathal Garvey & The Sunshine Band `}, + { + `htmlUnescape 5: {{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlEscape | safeHTML }}`, + `htmlUnescape 5: Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, } return &internal.TemplateFuncsNamespace{ diff --git a/tpl/transform/transform.go b/tpl/transform/transform.go index 7e3b81bed..e44abe6cb 100644 --- a/tpl/transform/transform.go +++ b/tpl/transform/transform.go @@ -35,11 +35,6 @@ type Namespace struct { deps *deps.Deps } -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { - return ns -} - // Emojify returns a copy of s with all emoji codes replaced with actual emojis. // // See http://www.emoji-cheat-sheet.com/ diff --git a/tpl/transform/transform_test.go b/tpl/transform/transform_test.go index 528697469..45ed5fc1e 100644 --- a/tpl/transform/transform_test.go +++ b/tpl/transform/transform_test.go @@ -29,14 +29,6 @@ import ( type tstNoStringer struct{} -func TestNamespace(t *testing.T) { - t.Parallel() - - ns := New(newDeps(viper.New())) - - assert.Equal(t, ns, ns.Namespace(), "object pointers should match") -} - func TestEmojify(t *testing.T) { t.Parallel() diff --git a/tpl/urls/init.go b/tpl/urls/init.go index a687704af..fb9b00a27 100644 --- a/tpl/urls/init.go +++ b/tpl/urls/init.go @@ -33,6 +33,7 @@ func init() { {`{{ "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{ @@ -45,6 +46,7 @@ func init() { "relURL": ctx.RelURL, "relLangURL": ctx.RelLangURL, "relref": ctx.RelRef, + "urlize": ctx.URLize, }, Examples: examples, } diff --git a/tpl/urls/urls.go b/tpl/urls/urls.go index 04ad1cb4d..5d1077785 100644 --- a/tpl/urls/urls.go +++ b/tpl/urls/urls.go @@ -33,11 +33,6 @@ type Namespace struct { deps *deps.Deps } -// Namespace returns a pointer to the current namespace instance. -func (ns *Namespace) Namespace() *Namespace { - return ns -} - // AbsURL takes a given string and converts it to an absolute URL. func (ns *Namespace) AbsURL(a interface{}) (template.HTML, error) { s, err := cast.ToStringE(a) @@ -59,6 +54,14 @@ func (ns *Namespace) RelURL(a interface{}) (template.HTML, error) { return template.HTML(ns.deps.PathSpec.RelURL(s, false)), nil } +func (ns *Namespace) URLize(a interface{}) (template.URL, error) { + s, err := cast.ToStringE(a) + if err != nil { + return "", nil + } + return template.URL(ns.deps.PathSpec.URLize(s)), nil +} + type reflinker interface { Ref(refs ...string) (string, error) RelRef(refs ...string) (string, error)