shithub: hugo

Download patch

ref: 0e2260421e40c97d9d210724fb44cfdc15ea7855
parent: 4714085a10835b9f4e8d4f699dc94e3120d8067e
author: Bjørn Erik Pedersen <[email protected]>
date: Mon May 1 05:06:42 EDT 2017

tpl: Fix the remaining template funcs namespace issues

See #3042

--- /dev/null
+++ b/tpl/cast/cast.go
@@ -1,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)
+}
--- /dev/null
+++ b/tpl/cast/init.go
@@ -1,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)
+}
--- a/tpl/collections/collections.go
+++ b/tpl/collections/collections.go
@@ -39,9 +39,6 @@
 	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 {
--- a/tpl/collections/collections_test.go
+++ b/tpl/collections/collections_test.go
@@ -29,14 +29,6 @@
 
 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()
 
--- a/tpl/collections/init.go
+++ b/tpl/collections/init.go
@@ -25,18 +25,18 @@
 		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: <a href="https://www.google.com?{{ (querify "q" "test" "page" 3) | safeURL }}">Search</a>`,
-				`querify 2: <a href="https://www.google.com?page=3&amp;q=test">Search</a>`},
-			{`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]`},
+				`<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{
--- a/tpl/compare/init.go
+++ b/tpl/compare/init.go
@@ -25,7 +25,9 @@
 		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{
--- a/tpl/crypto/crypto.go
+++ b/tpl/crypto/crypto.go
@@ -30,9 +30,6 @@
 // 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)
--- a/tpl/crypto/crypto_test.go
+++ b/tpl/crypto/crypto_test.go
@@ -21,14 +21,6 @@
 	"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()
 
--- a/tpl/crypto/init.go
+++ b/tpl/crypto/init.go
@@ -26,6 +26,7 @@
 
 		examples := [][2]string{
 			{`{{ md5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`},
+			{`{{ crypto.MD5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`},
 			{`{{ sha1 "Hello world, gophers!" }}`, `c8b5b0e33d408246e30f53e32b8f7627a7a649d4`},
 			{`{{ sha256 "Hello world, gophers!" }}`, `6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46`},
 		}
--- a/tpl/data/data.go
+++ b/tpl/data/data.go
@@ -26,6 +26,3 @@
 type Namespace struct {
 	deps *deps.Deps
 }
-
-// Namespace returns a pointer to the current namespace instance.
-func (ns *Namespace) Namespace() *Namespace { return ns }
--- a/tpl/encoding/encoding.go
+++ b/tpl/encoding/encoding.go
@@ -29,9 +29,6 @@
 // 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)
--- a/tpl/encoding/encoding_test.go
+++ b/tpl/encoding/encoding_test.go
@@ -25,14 +25,6 @@
 
 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()
 
--- /dev/null
+++ b/tpl/fmt/fmt.go
@@ -1,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...)
+}
--- /dev/null
+++ b/tpl/fmt/init.go
@@ -1,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)
+}
--- a/tpl/images/images.go
+++ b/tpl/images/images.go
@@ -43,9 +43,6 @@
 	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) {
--- a/tpl/images/images_test.go
+++ b/tpl/images/images_test.go
@@ -80,17 +80,6 @@
 	{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()
 
--- a/tpl/inflect/inflect.go
+++ b/tpl/inflect/inflect.go
@@ -28,9 +28,6 @@
 // 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
--- a/tpl/inflect/inflect_test.go
+++ b/tpl/inflect/inflect_test.go
@@ -8,14 +8,6 @@
 	"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()
 
--- a/tpl/lang/lang.go
+++ b/tpl/lang/lang.go
@@ -30,10 +30,6 @@
 	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)
--- a/tpl/math/math.go
+++ b/tpl/math/math.go
@@ -26,9 +26,6 @@
 // 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, '+')
 }
--- a/tpl/math/math_test.go
+++ b/tpl/math/math_test.go
@@ -21,14 +21,6 @@
 	"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()
 
--- a/tpl/os/init.go
+++ b/tpl/os/init.go
@@ -25,7 +25,7 @@
 		ctx := New(d)
 
 		examples := [][2]string{
-			{`{{ range (readDir ".") }}{{ .Name }}{{ end }}`, `README.txt`},
+			{`{{ range (readDir ".") }}{{ .Name }}{{ end }}`, "README.txt"},
 			{`{{ readFile "README.txt" }}`, `Hugo Rocks!`},
 		}
 
--- a/tpl/os/os.go
+++ b/tpl/os/os.go
@@ -35,9 +35,6 @@
 	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) {
--- /dev/null
+++ b/tpl/partials/init.go
@@ -1,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" . }}`, `<title>Hugo Rocks!</title>`},
+		}
+
+		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)
+}
--- /dev/null
+++ b/tpl/partials/partials.go
@@ -1,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
+}
--- a/tpl/safe/safe.go
+++ b/tpl/safe/safe.go
@@ -28,9 +28,6 @@
 // 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)
--- a/tpl/safe/safe_test.go
+++ b/tpl/safe/safe_test.go
@@ -24,14 +24,6 @@
 
 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()
 
--- a/tpl/strings/strings.go
+++ b/tpl/strings/strings.go
@@ -37,9 +37,6 @@
 	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)
--- a/tpl/strings/strings_test.go
+++ b/tpl/strings/strings_test.go
@@ -27,11 +27,6 @@
 
 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()
 
--- a/tpl/time/init.go
+++ b/tpl/time/init.go
@@ -26,6 +26,7 @@
 
 		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{
--- a/tpl/time/time.go
+++ b/tpl/time/time.go
@@ -27,9 +27,6 @@
 // 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) {
--- a/tpl/time/time_test.go
+++ b/tpl/time/time_test.go
@@ -16,17 +16,7 @@
 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()
--- a/tpl/tplimpl/templateFuncster.go
+++ b/tpl/tplimpl/templateFuncster.go
@@ -25,8 +25,7 @@
 
 // Some of the template funcs are'nt entirely stateless.
 type templateFuncster struct {
-	funcMap        template.FuncMap
-	cachedPartials partialCache
+	funcMap template.FuncMap
 
 	*deps.Deps
 }
@@ -33,8 +32,7 @@
 
 func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
 	return &templateFuncster{
-		Deps:           deps,
-		cachedPartials: partialCache{p: make(map[string]interface{})},
+		Deps: deps,
 	}
 }
 
--- 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 @@
 	_ "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
--- a/tpl/tplimpl/template_funcs_test.go
+++ b/tpl/tplimpl/template_funcs_test.go
@@ -18,7 +18,6 @@
 	"fmt"
 	"path/filepath"
 	"reflect"
-	"strings"
 	"testing"
 
 	"io/ioutil"
@@ -92,6 +91,7 @@
 			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", "<title>Hugo Rocks!</title>"))
 				return nil
 			}
 			require.NoError(t, d.LoadResources())
@@ -106,134 +106,8 @@
 
 }
 
-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 <[email protected]>" | safeHTML}}
-htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>"}}
-htmlUnescape 1: {{htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | safeHTML}}
-htmlUnescape 2: {{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;" | htmlUnescape | htmlUnescape | safeHTML}}
-htmlUnescape 3: {{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;" | htmlUnescape | htmlUnescape }}
-htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>" | htmlUnescape | safeHTML }}
-htmlUnescape 5: {{ htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | 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 &amp; The Sunshine Band &lt;[email protected]&gt;
-htmlEscape 2: Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;
-htmlUnescape 1: Cathal Garvey & The Sunshine Band <[email protected]>
-htmlUnescape 2: Cathal Garvey & The Sunshine Band <[email protected]>
-htmlUnescape 3: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;
-htmlUnescape 4: Cathal Garvey & The Sunshine Band <[email protected]>
-htmlUnescape 5: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;
-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 {
@@ -387,21 +261,4 @@
 	}
 
 	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)
 }
--- a/tpl/tplimpl/template_test.go
+++ /dev/null
@@ -1,127 +1,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
-}
--- a/tpl/transform/init.go
+++ b/tpl/transform/init.go
@@ -28,6 +28,27 @@
 			{`{{ "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 <[email protected]>" | safeHTML}}`,
+				`htmlEscape 1: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;`},
+			{
+				`htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>"}}`,
+				`htmlEscape 2: Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;`},
+			{
+				`htmlUnescape 1: {{htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | safeHTML}}`,
+				`htmlUnescape 1: Cathal Garvey & The Sunshine Band <[email protected]>`},
+			{
+				`htmlUnescape 2: {{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;" | htmlUnescape | htmlUnescape | safeHTML}}`,
+				`htmlUnescape 2: Cathal Garvey & The Sunshine Band <[email protected]>`},
+			{
+				`htmlUnescape 3: {{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;" | htmlUnescape | htmlUnescape }}`,
+				`htmlUnescape 3: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;`},
+			{
+				`htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>" | htmlUnescape | safeHTML }}`,
+				`htmlUnescape 4: Cathal Garvey & The Sunshine Band <[email protected]>`},
+			{
+				`htmlUnescape 5: {{ htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | htmlEscape | safeHTML }}`,
+				`htmlUnescape 5: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;`},
 		}
 
 		return &internal.TemplateFuncsNamespace{
--- a/tpl/transform/transform.go
+++ b/tpl/transform/transform.go
@@ -35,11 +35,6 @@
 	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/
--- a/tpl/transform/transform_test.go
+++ b/tpl/transform/transform_test.go
@@ -29,14 +29,6 @@
 
 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()
 
--- a/tpl/urls/init.go
+++ b/tpl/urls/init.go
@@ -33,6 +33,7 @@
 			{`{{ "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 @@
 				"relURL":     ctx.RelURL,
 				"relLangURL": ctx.RelLangURL,
 				"relref":     ctx.RelRef,
+				"urlize":     ctx.URLize,
 			},
 			Examples: examples,
 		}
--- a/tpl/urls/urls.go
+++ b/tpl/urls/urls.go
@@ -33,11 +33,6 @@
 	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)
@@ -57,6 +52,14 @@
 	}
 
 	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 {