ref: a10b2cd372798c4e4b862f0ec03010d2aea2ff1e
parent: dffd7da07c3fb198acfa6c4664b53132c4cabe55
	author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
	date: Mon Oct 24 09:45:30 EDT 2016
	
Avoid reading from Viper for path and URL funcs The gain, given the "real sites benchmark" below, is obvious: ``` benchmark old ns/op new ns/op delta BenchmarkHugo-4 14497594101 13084156335 -9.75% benchmark old allocs new allocs delta BenchmarkHugo-4 57404335 48282002 -15.89% benchmark old bytes new bytes delta BenchmarkHugo-4 9933505624 9721984424 -2.13% ``` Fixes #2495
--- a/helpers/configProvider.go
+++ b/helpers/configProvider.go
@@ -21,16 +21,55 @@
"github.com/spf13/viper"
)
+// A cached version of the current ConfigProvider (language) and relatives. These globals
+// are unfortunate, but we still have some places that needs this that does
+// not have access to the site configuration.
+// These values will be set on initialization when rendering a new language.
+//
+// TODO(bep) Get rid of these.
+var (
+ currentConfigProvider ConfigProvider
+ currentPathSpec *PathSpec
+)
+
// ConfigProvider provides the configuration settings for Hugo.
 type ConfigProvider interface {GetString(key string) string
GetInt(key string) int
+ GetBool(key string) bool
 	GetStringMap(key string) map[string]interface{}GetStringMapString(key string) map[string]string
+	Get(key string) interface{}}
// Config returns the currently active Hugo config. This will be set
// per site (language) rendered.
 func Config() ConfigProvider {+	if currentConfigProvider != nil {+ return currentConfigProvider
+ }
+ // Some tests rely on this. We will fix that, eventually.
 	return viper.Get("CurrentContentLanguage").(ConfigProvider)+}
+
+// CurrentPathSpec returns the current PathSpec.
+// If it is not set, a new will be created based in the currently active Hugo config.
+func CurrentPathSpec() *PathSpec {+	if currentPathSpec != nil {+ return currentPathSpec
+ }
+ // Some tests rely on this. We will fix that, eventually.
+ return NewPathSpecFromConfig(Config())
+}
+
+// InitConfigProviderForCurrentContentLanguage does what it says.
+func InitConfigProviderForCurrentContentLanguage() {+	currentConfigProvider = viper.Get("CurrentContentLanguage").(ConfigProvider)+ currentPathSpec = NewPathSpecFromConfig(currentConfigProvider)
+}
+
+// ResetConfigProvider is used in tests.
+func ResetConfigProvider() {+ currentConfigProvider = nil
+ currentPathSpec = nil
}
--- a/helpers/language.go
+++ b/helpers/language.go
@@ -23,6 +23,19 @@
"github.com/spf13/viper"
)
+// These are the settings that should only be looked up in the global Viper
+// config and not per language.
+// This list may not be complete, but contains only settings that we know
+// will be looked up in both.
+// This isn't perfect, but it is ultimately the user who shoots him/herself in
+// the foot.
+// See the pathSpec.
+var globalOnlySettings = map[string]bool{+	strings.ToLower("defaultContentLanguageInSubdir"): true,+	strings.ToLower("defaultContentLanguage"):         true,+	strings.ToLower("multilingual"):                   true,+}
+
 type Language struct {Lang string
LanguageName string
@@ -81,7 +94,7 @@
}
 func (l *Language) SetParam(k string, v interface{}) {- l.params[k] = v
+ l.params[strings.ToLower(k)] = v
}
 func (l *Language) GetBool(key string) bool     { return cast.ToBool(l.Get(key)) }@@ -101,8 +114,10 @@
 		panic("language not set")}
key = strings.ToLower(key)
-	if v, ok := l.params[key]; ok {- return v
+	if !globalOnlySettings[key] {+		if v, ok := l.params[key]; ok {+ return v
+ }
}
return viper.Get(key)
}
--- /dev/null
+++ b/helpers/language_test.go
@@ -1,0 +1,32 @@
+// Copyright 2016-present 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 helpers
+
+import (
+ "testing"
+
+ "github.com/spf13/viper"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGetGlobalOnlySetting(t *testing.T) {+ lang := NewDefaultLanguage()
+	lang.SetParam("defaultContentLanguageInSubdir", false)+	lang.SetParam("paginatePath", "side")+	viper.Set("defaultContentLanguageInSubdir", true)+	viper.Set("paginatePath", "page")+
+	require.True(t, lang.GetBool("defaultContentLanguageInSubdir"))+	require.Equal(t, "side", lang.GetString("paginatePath"))+}
--- a/helpers/path.go
+++ b/helpers/path.go
@@ -75,16 +75,16 @@
// It does so by creating a Unicode-sanitized string, with the spaces replaced,
// whilst preserving the original casing of the string.
// E.g. Social Media -> Social-Media
-func MakePath(s string) string {- return UnicodeSanitize(strings.Replace(strings.TrimSpace(s), " ", "-", -1))
+func (p *PathSpec) MakePath(s string) string {+ return p.UnicodeSanitize(strings.Replace(strings.TrimSpace(s), " ", "-", -1))
}
// MakePathSanitized creates a Unicode-sanitized string, with the spaces replaced
-func MakePathSanitized(s string) string {-	if viper.GetBool("DisablePathToLower") {- return MakePath(s)
+func (p *PathSpec) MakePathSanitized(s string) string {+	if p.disablePathToLower {+ return p.MakePath(s)
}
- return strings.ToLower(MakePath(s))
+ return strings.ToLower(p.MakePath(s))
}
// MakeTitle converts the path given to a suitable title, trimming whitespace
@@ -110,7 +110,7 @@
// a predefined set of special Unicode characters.
// If RemovePathAccents configuration flag is enabled, Uniccode accents
// are also removed.
-func UnicodeSanitize(s string) string {+func (p *PathSpec) UnicodeSanitize(s string) string {source := []rune(s)
target := make([]rune, 0, len(source))
@@ -124,7 +124,7 @@
var result string
-	if viper.GetBool("RemovePathAccents") {+	if p.removePathAccents {// remove accents - see https://blog.golang.org/normalization
t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC)
result, _, _ = transform.String(t, string(target))
--- a/helpers/path_test.go
+++ b/helpers/path_test.go
@@ -33,9 +33,14 @@
"github.com/spf13/viper"
)
+func initCommonTestConfig() {+	viper.Set("CurrentContentLanguage", NewLanguage("en"))+}
+
 func TestMakePath(t *testing.T) {viper.Reset()
defer viper.Reset()
+ initCommonTestConfig()
 	tests := []struct {input string
@@ -57,7 +62,8 @@
 	for _, test := range tests { 		viper.Set("RemovePathAccents", test.removeAccents)- output := MakePath(test.input)
+ p := NewPathSpecFromConfig(viper.GetViper())
+ output := p.MakePath(test.input)
 		if output != test.expected { 			t.Errorf("Expected %#v, got %#v\n", test.expected, output)}
@@ -67,7 +73,10 @@
 func TestMakePathSanitized(t *testing.T) {viper.Reset()
defer viper.Reset()
+ initCommonTestConfig()
+ p := NewPathSpecFromConfig(viper.GetViper())
+
 	tests := []struct {input string
expected string
@@ -81,7 +90,7 @@
}
 	for _, test := range tests {- output := MakePathSanitized(test.input)
+ output := p.MakePathSanitized(test.input)
 		if output != test.expected { 			t.Errorf("Expected %#v, got %#v\n", test.expected, output)}
@@ -91,7 +100,10 @@
 func TestMakePathSanitizedDisablePathToLower(t *testing.T) {viper.Reset()
defer viper.Reset()
+
+ initCommonTestConfig()
 	viper.Set("DisablePathToLower", true)+ p := NewPathSpecFromConfig(viper.GetViper())
 	tests := []struct {input string
@@ -106,7 +118,7 @@
}
 	for _, test := range tests {- output := MakePathSanitized(test.input)
+ output := p.MakePathSanitized(test.input)
 		if output != test.expected { 			t.Errorf("Expected %#v, got %#v\n", test.expected, output)}
--- /dev/null
+++ b/helpers/pathspec.go
@@ -1,0 +1,54 @@
+// Copyright 2016-present 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 helpers
+
+// PathSpec holds methods that decides how paths in URLs and files in Hugo should look like.
+type PathSpec struct {+ disablePathToLower bool
+ removePathAccents bool
+ uglyURLs bool
+ canonifyURLs bool
+
+ currentContentLanguage *Language
+
+ // pagination path handling
+ paginatePath string
+
+ // The PathSpec looks up its config settings in both the current language
+ // and then in the global Viper config.
+ // Some settings, the settings listed below, does not make sense to be set
+ // on per-language-basis. We have no good way of protecting against this
+ // other than a "white-list". See language.go.
+ defaultContentLanguageInSubdir bool
+ defaultContentLanguage string
+ multilingual bool
+}
+
+func NewPathSpecFromConfig(config ConfigProvider) *PathSpec {+	return &PathSpec{+		disablePathToLower:             config.GetBool("disablePathToLower"),+		removePathAccents:              config.GetBool("removePathAccents"),+		uglyURLs:                       config.GetBool("uglyURLs"),+		canonifyURLs:                   config.GetBool("canonifyURLs"),+		multilingual:                   config.GetBool("multilingual"),+		defaultContentLanguageInSubdir: config.GetBool("defaultContentLanguageInSubdir"),+		defaultContentLanguage:         config.GetString("defaultContentLanguage"),+		currentContentLanguage:         config.Get("currentContentLanguage").(*Language),+		paginatePath:                   config.GetString("paginatePath"),+ }
+}
+
+func (p *PathSpec) PaginatePath() string {+ return p.paginatePath
+}
--- /dev/null
+++ b/helpers/pathspec_test.go
@@ -1,0 +1,45 @@
+// Copyright 2016-present 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 helpers
+
+import (
+ "testing"
+
+ "github.com/spf13/viper"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNewPathSpecFromConfig(t *testing.T) {+	viper.Set("disablePathToLower", true)+	viper.Set("removePathAccents", true)+	viper.Set("uglyURLs", true)+	viper.Set("multilingual", true)+	viper.Set("defaultContentLanguageInSubdir", true)+	viper.Set("defaultContentLanguage", "no")+	viper.Set("currentContentLanguage", NewLanguage("no"))+	viper.Set("canonifyURLs", true)+	viper.Set("paginatePath", "side")+
+ pathSpec := NewPathSpecFromConfig(viper.GetViper())
+
+ require.True(t, pathSpec.canonifyURLs)
+ require.True(t, pathSpec.defaultContentLanguageInSubdir)
+ require.True(t, pathSpec.disablePathToLower)
+ require.True(t, pathSpec.multilingual)
+ require.True(t, pathSpec.removePathAccents)
+ require.True(t, pathSpec.uglyURLs)
+ require.Equal(t, "no", pathSpec.defaultContentLanguage)
+ require.Equal(t, "no", pathSpec.currentContentLanguage.Lang)
+ require.Equal(t, "side", pathSpec.paginatePath)
+}
--- a/helpers/url.go
+++ b/helpers/url.go
@@ -101,8 +101,8 @@
// Example:
// uri: Vim (text editor)
// urlize: vim-text-editor
-func URLize(uri string) string {- sanitized := MakePathSanitized(uri)
+func (p *PathSpec) URLize(uri string) string {+ sanitized := p.MakePathSanitized(uri)
// escape unicode letters
parsedURI, err := url.Parse(sanitized)
@@ -147,7 +147,7 @@
}
// AbsURL creates a absolute URL from the relative path given and the BaseURL set in config.
-func AbsURL(in string, addLanguage bool) string {+func (p *PathSpec) AbsURL(in string, addLanguage bool) string {url, err := url.Parse(in)
 	if err != nil {return in
@@ -168,7 +168,7 @@
}
 	if addLanguage {- prefix := getLanguagePrefix()
+ prefix := p.getLanguagePrefix()
 		if prefix != "" {hasPrefix := false
// avoid adding language prefix if already present
@@ -191,15 +191,15 @@
return MakePermalink(baseURL, in).String()
}
-func getLanguagePrefix() string {-	if !viper.GetBool("Multilingual") {+func (p *PathSpec) getLanguagePrefix() string {+	if !p.multilingual {return ""
}
-	defaultLang := viper.GetString("DefaultContentLanguage")-	defaultInSubDir := viper.GetBool("DefaultContentLanguageInSubdir")+ defaultLang := p.defaultContentLanguage
+ defaultInSubDir := p.defaultContentLanguageInSubdir
-	currentLang := viper.Get("CurrentContentLanguage").(*Language).Lang+ currentLang := p.currentContentLanguage.Lang
 	if currentLang == "" || (currentLang == defaultLang && !defaultInSubDir) {return ""
}
@@ -218,9 +218,9 @@
// RelURL creates a URL relative to the BaseURL root.
// Note: The result URL will not include the context root if canonifyURLs is enabled.
-func RelURL(in string, addLanguage bool) string {+func (p *PathSpec) RelURL(in string, addLanguage bool) string { 	baseURL := viper.GetString("BaseURL")-	canonifyURLs := viper.GetBool("canonifyURLs")+ canonifyURLs := p.canonifyURLs
 	if (!strings.HasPrefix(in, baseURL) && strings.HasPrefix(in, "http")) || strings.HasPrefix(in, "//") {return in
}
@@ -232,7 +232,7 @@
}
 	if addLanguage {- prefix := getLanguagePrefix()
+ prefix := p.getLanguagePrefix()
 		if prefix != "" {hasPrefix := false
// avoid adding language prefix if already present
@@ -288,8 +288,8 @@
return newPath
}
-func URLizeAndPrep(in string) string {-	return URLPrep(viper.GetBool("UglyURLs"), URLize(in))+func (p *PathSpec) URLizeAndPrep(in string) string {+ return URLPrep(p.uglyURLs, p.URLize(in))
}
 func URLPrep(ugly bool, in string) string {--- a/helpers/url_test.go
+++ b/helpers/url_test.go
@@ -24,6 +24,10 @@
)
 func TestURLize(t *testing.T) {+ initCommonTestConfig()
+
+ p := NewPathSpecFromConfig(viper.GetViper())
+
 	tests := []struct {input string
expected string
@@ -37,7 +41,7 @@
}
 	for _, test := range tests {- output := URLize(test.input)
+ output := p.URLize(test.input)
 		if output != test.expected { 			t.Errorf("Expected %#v, got %#v\n", test.expected, output)}
@@ -83,7 +87,8 @@
 	for _, test := range tests { 		viper.Set("BaseURL", test.baseURL)- output := AbsURL(test.input, addLanguage)
+ p := NewPathSpecFromConfig(viper.GetViper())
+ output := p.AbsURL(test.input, addLanguage)
expected := test.expected
 		if multilingual && addLanguage { 			if !defaultInSubDir && lang == "en" {@@ -159,8 +164,9 @@
 	for i, test := range tests { 		viper.Set("BaseURL", test.baseURL) 		viper.Set("canonifyURLs", test.canonify)+ p := NewPathSpecFromConfig(viper.GetViper())
- output := RelURL(test.input, addLanguage)
+ output := p.RelURL(test.input, addLanguage)
expected := test.expected
 		if multilingual && addLanguage {--- a/hugolib/hugo_sites_test.go
+++ b/hugolib/hugo_sites_test.go
@@ -35,6 +35,7 @@
hugofs.InitMemFs()
viper.Reset()
viper.SetFs(hugofs.Source())
+ helpers.ResetConfigProvider()
loadDefaultSettings()
// Default is false, but true is easier to use as default in tests
--- a/hugolib/node.go
+++ b/hugolib/node.go
@@ -178,7 +178,7 @@
}
 func (n *Node) Permalink() string {- return permalink(n.URL())
+ return n.Site.permalink(n.URL())
}
// Scratch returns the writable context associated with this Node.
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -569,9 +569,9 @@
 func (p *Page) permalink() (*url.URL, error) {baseURL := string(p.Site.BaseURL)
- dir := strings.TrimSpace(helpers.MakePath(filepath.ToSlash(strings.ToLower(p.Source.Dir()))))
- pSlug := strings.TrimSpace(helpers.URLize(p.Slug))
- pURL := strings.TrimSpace(helpers.URLize(p.URLPath.URL))
+ dir := strings.TrimSpace(p.Site.pathSpec.MakePath(filepath.ToSlash(strings.ToLower(p.Source.Dir()))))
+ pSlug := strings.TrimSpace(p.Site.pathSpec.URLize(p.Slug))
+ pURL := strings.TrimSpace(p.Site.pathSpec.URLize(p.URLPath.URL))
var permalink string
var err error
@@ -1171,5 +1171,6 @@
outfile = helpers.ReplaceExtension(p.Source.TranslationBaseName(), p.Extension())
}
- return p.addLangFilepathPrefix(filepath.Join(strings.ToLower(helpers.MakePath(p.Source.Dir())), strings.TrimSpace(outfile)))
+ return p.addLangFilepathPrefix(filepath.Join(strings.ToLower(
+ p.Site.pathSpec.MakePath(p.Source.Dir())), strings.TrimSpace(outfile)))
}
--- a/hugolib/pageSort_test.go
+++ b/hugolib/pageSort_test.go
@@ -20,6 +20,7 @@
"testing"
"time"
+ "github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/source"
"github.com/stretchr/testify/assert"
)
@@ -134,6 +135,8 @@
 func createSortTestPages(num int) Pages {pages := make(Pages, num)
+	info := newSiteInfo(siteBuilderCfg{baseURL: "http://base", language: helpers.NewDefaultLanguage()})+
 	for i := 0; i < num; i++ { 		pages[i] = &Page{ 			Node: Node{@@ -141,7 +144,7 @@
Section: "z",
 					URL:     fmt.Sprintf("http://base/x/y/p%d.html", i),},
-				Site: newSiteInfoDefaultLanguage("http://base/"),+ Site: &info,
},
 			Source: Source{File: *source.NewFile(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i)))},}
--- a/hugolib/page_permalink_test.go
+++ b/hugolib/page_permalink_test.go
@@ -18,6 +18,7 @@
"path/filepath"
"testing"
+ "github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/source"
"github.com/spf13/viper"
)
@@ -59,10 +60,11 @@
}
 	viper.Set("DefaultExtension", "html")-
 	for i, test := range tests { 		viper.Set("uglyurls", test.uglyURLs) 		viper.Set("canonifyurls", test.canonifyURLs)+		info := newSiteInfo(siteBuilderCfg{baseURL: string(test.base), language: helpers.NewDefaultLanguage()})+
 		p := &Page{ 			Node: Node{ 				URLPath: URLPath{@@ -69,7 +71,7 @@
Section: "z",
URL: test.url,
},
- Site: newSiteInfoDefaultLanguage(string(test.base)),
+ Site: &info,
},
 			Source: Source{File: *source.NewFile(filepath.FromSlash(test.file))},}
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -1122,7 +1122,8 @@
 	for _, test := range tests {p, _ := NewPageFrom(strings.NewReader(test.content), filepath.FromSlash(test.path))
-		p.Node.Site = newSiteInfoDefaultLanguage("")+		info := newSiteInfo(siteBuilderCfg{language: helpers.NewDefaultLanguage()})+ p.Node.Site = &info
 		if test.hasPermalink {p.Node.Site.Permalinks = siteParmalinksSetting
--- a/hugolib/pagination.go
+++ b/hugolib/pagination.go
@@ -508,7 +508,7 @@
}
 func newPaginationURLFactory(pathElements ...string) paginationURLFactory {-	paginatePath := helpers.Config().GetString("paginatePath")+ pathSpec := helpers.CurrentPathSpec()
 	return func(page int) string {var rel string
@@ -515,9 +515,9 @@
 		if page == 1 { 			rel = fmt.Sprintf("/%s/", path.Join(pathElements...)) 		} else {-			rel = fmt.Sprintf("/%s/%s/%d/", path.Join(pathElements...), paginatePath, page)+			rel = fmt.Sprintf("/%s/%s/%d/", path.Join(pathElements...), pathSpec.PaginatePath(), page)}
- return helpers.URLizeAndPrep(rel)
+ return pathSpec.URLizeAndPrep(rel)
}
}
--- a/hugolib/pagination_test.go
+++ b/hugolib/pagination_test.go
@@ -19,6 +19,7 @@
"path/filepath"
"testing"
+ "github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/source"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
@@ -453,6 +454,7 @@
 func createTestPages(num int) Pages {pages := make(Pages, num)
+	info := newSiteInfo(siteBuilderCfg{baseURL: "http://base/", language: helpers.NewDefaultLanguage()}) 	for i := 0; i < num; i++ { 		pages[i] = &Page{ 			Node: Node{@@ -460,7 +462,7 @@
Section: "z",
 					URL:     fmt.Sprintf("http://base/x/y/p%d.html", i),},
-				Site: newSiteInfoDefaultLanguage("http://base/"),+ Site: &info,
},
 			Source: Source{File: *source.NewFile(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i)))},}
--- a/hugolib/permalinks.go
+++ b/hugolib/permalinks.go
@@ -19,8 +19,6 @@
"regexp"
"strconv"
"strings"
-
- "github.com/spf13/hugo/helpers"
)
// pathPattern represents a string which builds up a URL from attributes
@@ -152,7 +150,7 @@
 func pageToPermalinkTitle(p *Page, _ string) (string, error) {// Page contains Node which has Title
// (also contains URLPath which has Slug, sometimes)
- return helpers.URLize(p.Title), nil
+ return p.Site.pathSpec.URLize(p.Title), nil
}
// pageToPermalinkFilename returns the URL-safe form of the filename
@@ -159,7 +157,7 @@
 func pageToPermalinkFilename(p *Page, _ string) (string, error) {//var extension = p.Source.Ext
//var name = p.Source.Path()[0 : len(p.Source.Path())-len(extension)]
- return helpers.URLize(p.Source.TranslationBaseName()), nil
+ return p.Site.pathSpec.URLize(p.Source.TranslationBaseName()), nil
}
// if the page has a slug, return the slug, else return the title
@@ -173,7 +171,7 @@
 		if strings.HasSuffix(p.Slug, "-") {p.Slug = p.Slug[0 : len(p.Slug)-1]
}
- return helpers.URLize(p.Slug), nil
+ return p.Site.pathSpec.URLize(p.Slug), nil
}
return pageToPermalinkTitle(p, a)
}
--- a/hugolib/permalinks_test.go
+++ b/hugolib/permalinks_test.go
@@ -16,6 +16,8 @@
import (
"strings"
"testing"
+
+ "github.com/spf13/hugo/helpers"
)
// testdataPermalinks is used by a couple of tests; the expandsTo content is
@@ -70,6 +72,8 @@
 func TestPermalinkExpansion(t *testing.T) {page, err := NewPageFrom(strings.NewReader(simplePageJSON), "blue/test-page.md")
+	info := newSiteInfo(siteBuilderCfg{language: helpers.NewDefaultLanguage()})+ page.Site = &info
 	if err != nil { 		t.Fatalf("failed before we began, could not parse SIMPLE_PAGE_JSON: %s", err)}
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -117,7 +117,8 @@
// newSite creates a new site in the given language.
 func newSite(lang *helpers.Language) *Site {-	return &Site{Language: lang, Info: SiteInfo{multilingual: newMultiLingualForLanguage(lang)}}+	return &Site{Language: lang, Info: newSiteInfo(siteBuilderCfg{language: lang})}+
}
// newSite creates a new site in the default language.
@@ -139,9 +140,12 @@
 		sources = append(sources, source.ByteSource{Name: filepath.FromSlash(path), Content: []byte(content)})}
+ lang := helpers.NewDefaultLanguage()
+
 	return &Site{ 		Source:   &source.InMemorySource{ByteSource: sources},- Language: helpers.NewDefaultLanguage(),
+ Language: lang,
+		Info:     newSiteInfo(siteBuilderCfg{language: lang}),}
}
@@ -195,16 +199,25 @@
LanguagePrefix string
Languages helpers.Languages
defaultContentLanguageInSubdir bool
+
+ pathSpec *helpers.PathSpec
}
// Used in tests.
-func newSiteInfoDefaultLanguage(baseURL string, pages ...*Page) *SiteInfo {- ps := Pages(pages)
-	return &SiteInfo{- BaseURL: template.URL(baseURL),
- rawAllPages: &ps,
- multilingual: newMultiLingualDefaultLanguage(),
+type siteBuilderCfg struct {+ language *helpers.Language
+ baseURL string
+
+ pages *Pages
+}
+
+func newSiteInfo(cfg siteBuilderCfg) SiteInfo {+	return SiteInfo{+ BaseURL: template.URL(cfg.baseURL),
+ rawAllPages: cfg.pages,
+ pathSpec: helpers.NewPathSpecFromConfig(cfg.language),
+ multilingual: newMultiLingualForLanguage(cfg.language),
}
}
@@ -808,7 +821,9 @@
// There are sadly some global template funcs etc. that need the language information.
 	viper.Set("Multilingual", s.multilingualEnabled()) 	viper.Set("CurrentContentLanguage", s.Language)- return tpl.SetTranslateLang(s.Language.Lang)
+ // Cache the current config.
+ helpers.InitConfigProviderForCurrentContentLanguage()
+ return tpl.SetTranslateLang(s.Language)
}
 func (s *Site) render() (err error) {@@ -887,7 +902,7 @@
 	if s.IsMultiLingual() {base = s.Language.Lang
}
- return helpers.AbsURL(base, false)
+ return s.pathSpec.AbsURL(base, false)
}
// SitemapAbsURL is a convenience method giving the absolute URL to the sitemap.
@@ -946,7 +961,6 @@
Languages: languages,
defaultContentLanguageInSubdir: defaultContentInSubDir,
 		GoogleAnalytics:                lang.GetString("GoogleAnalytics"),-		RSSLink:                        permalinkStr(lang.GetString("RSSUri")), 		BuildDrafts:                    viper.GetBool("BuildDrafts"), 		canonifyURLs:                   viper.GetBool("CanonifyURLs"), 		preserveTaxonomyNames:          lang.GetBool("PreserveTaxonomyNames"),@@ -959,7 +973,10 @@
Permalinks: permalinks,
Data: &s.Data,
owner: s.owner,
+ pathSpec: helpers.NewPathSpecFromConfig(lang),
}
+
+	s.Info.RSSLink = s.Info.permalinkStr(lang.GetString("RSSUri"))}
 func (s *Site) hasTheme() bool {@@ -1407,7 +1424,7 @@
}
// make it match the nodes
menuEntryURL := in
- menuEntryURL = helpers.SanitizeURLKeepTrailingSlash(helpers.URLize(menuEntryURL))
+ menuEntryURL = helpers.SanitizeURLKeepTrailingSlash(s.pathSpec.URLize(menuEntryURL))
 	if !s.canonifyURLs {menuEntryURL = helpers.AddContextRoot(string(s.BaseURL), menuEntryURL)
}
@@ -1586,13 +1603,13 @@
 	if s.owner.multilingual.enabled() {mainLang := s.owner.multilingual.DefaultLang.Lang
 		if s.Info.defaultContentLanguageInSubdir {- mainLangURL := helpers.AbsURL(mainLang, false)
+ mainLangURL := s.Info.pathSpec.AbsURL(mainLang, false)
 			jww.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL) 			if err := s.publishDestAlias(s.languageAliasTarget(), "/", mainLangURL, nil); err != nil {return err
}
 		} else {-			mainLangURL := helpers.AbsURL("", false)+			mainLangURL := s.Info.pathSpec.AbsURL("", false) 			jww.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL) 			if err := s.publishDestAlias(s.languageAliasTarget(), mainLang, mainLangURL, nil); err != nil {return err
@@ -1763,7 +1780,7 @@
 	n := s.nodeLookup(fmt.Sprintf("tax-%s-%s", t.plural, key), counter, prepare) 	if s.Info.preserveTaxonomyNames {- key = helpers.MakePathSanitized(key)
+ key = s.Info.pathSpec.MakePathSanitized(key)
}
base := t.plural + "/" + key
@@ -1952,7 +1969,7 @@
 			[]string{"section/" + section + ".html", "_default/section.html", "_default/list.html", "indexes/" + section + ".html", "_default/indexes.html"}) 		if s.Info.preserveTaxonomyNames {- section = helpers.MakePathSanitized(section)
+ section = s.Info.pathSpec.MakePathSanitized(section)
}
base := n.addLangPathPrefix(section)
@@ -1966,7 +1983,7 @@
 			paginatePath := helpers.Config().GetString("paginatePath")// write alias for page 1
- s.writeDestAlias(helpers.PaginateAliasPath(base, 1), permalink(base), nil)
+ s.writeDestAlias(helpers.PaginateAliasPath(base, 1), s.Info.permalink(base), nil)
pagers := n.paginator.Pagers()
@@ -2111,6 +2128,15 @@
return n
}
+func (s *Site) newPage() *Page {+	page := &Page{}+ page.language = s.Language
+ page.Date = s.Info.LastChange
+ page.Lastmod = s.Info.LastChange
+ page.Site = &s.Info
+ return page
+}
+
 func (s *Site) renderSitemap() error { 	if viper.GetBool("DisableSitemap") {return nil
@@ -2123,11 +2149,7 @@
// Prepend homepage to the list of pages
pages := make(Pages, 0)
-	page := &Page{}- page.language = s.Language
- page.Date = s.Info.LastChange
- page.Lastmod = s.Info.LastChange
- page.Site = &s.Info
+ page := s.newPage()
page.URLPath.URL = ""
page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
page.Sitemap.Priority = sitemapDefault.Priority
@@ -2199,18 +2221,21 @@
}
 func (s *Site) setURLs(n *Node, in string) {- n.URLPath.URL = helpers.URLizeAndPrep(in)
- n.URLPath.Permalink = permalink(n.URLPath.URL)
- n.RSSLink = template.HTML(permalink(in + ".xml"))
+ n.URLPath.URL = s.Info.pathSpec.URLizeAndPrep(in)
+ n.URLPath.Permalink = s.Info.permalink(n.URLPath.URL)
+ n.RSSLink = template.HTML(s.Info.permalink(in + ".xml"))
}
-func permalink(plink string) string {- return permalinkStr(plink)
+func (s *SiteInfo) permalink(plink string) string {+ return s.permalinkStr(plink)
}
-func permalinkStr(plink string) string {-	return helpers.MakePermalink(viper.GetString("BaseURL"), helpers.URLizeAndPrep(plink)).String()+func (s *SiteInfo) permalinkStr(plink string) string {+ return helpers.MakePermalink(
+		viper.GetString("BaseURL"),+ s.pathSpec.URLizeAndPrep(plink)).String()
}
+
 func (s *Site) newNode(nodeID string) *Node {return s.nodeLookup(nodeID, 0, true)
}
--- a/hugolib/taxonomy.go
+++ b/hugolib/taxonomy.go
@@ -52,7 +52,7 @@
// KeyPrep... Taxonomies should be case insensitive. Can make it easily conditional later.
 func kp(in string) string {- return helpers.MakePathSanitized(in)
+ return helpers.CurrentPathSpec().MakePathSanitized(in)
}
// Get the weighted pages for the given key.
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -94,6 +94,10 @@
localTemplates = &templates.Template
+ // The URL funcs in the funcMap is somewhat language dependent,
+ // so need to be reinit per site.
+ initFuncMap()
+
 	for k, v := range funcMap {amber.FuncMap[k] = v
}
--- a/tpl/template_funcs.go
+++ b/tpl/template_funcs.go
@@ -47,7 +47,9 @@
"github.com/spf13/viper"
)
-var funcMap template.FuncMap
+var (
+ funcMap template.FuncMap
+)
// eq returns the boolean truth of arg1 == arg2.
 func eq(x, y interface{}) bool {@@ -1940,7 +1942,7 @@
 	if err != nil {return "", nil
}
- return template.HTML(helpers.AbsURL(s, false)), nil
+ return template.HTML(helpers.CurrentPathSpec().AbsURL(s, false)), nil
}
 func relURL(a interface{}) (template.HTML, error) {@@ -1948,10 +1950,10 @@
 	if err != nil {return "", nil
}
- return template.HTML(helpers.RelURL(s, false)), nil
+ return template.HTML(helpers.CurrentPathSpec().RelURL(s, false)), nil
}
-func init() {+func initFuncMap() { 	funcMap = template.FuncMap{"absURL": absURL,
 		"absLangURL": func(i interface{}) (template.HTML, error) {@@ -1959,7 +1961,7 @@
 			if err != nil {return "", err
}
- return template.HTML(helpers.AbsURL(s, true)), nil
+ return template.HTML(helpers.CurrentPathSpec().AbsURL(s, true)), nil
},
 		"add":           func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '+') },"after": after,
@@ -2020,7 +2022,7 @@
 			if err != nil {return "", err
}
- return template.HTML(helpers.RelURL(s, true)), nil
+ return template.HTML(helpers.CurrentPathSpec().RelURL(s, true)), nil
},
"relref": relRef,
"replace": replace,
@@ -2047,7 +2049,7 @@
"time": asTime,
"trim": trim,
 		"upper":        func(a string) string { return strings.ToUpper(a) },- "urlize": helpers.URLize,
+ "urlize": helpers.CurrentPathSpec().URLize,
"where": where,
"i18n": I18nTranslate,
"T": I18nTranslate,
--- a/tpl/template_funcs_test.go
+++ b/tpl/template_funcs_test.go
@@ -63,6 +63,11 @@
return tp == tstLt || tp == tstLe
}
+func tstInitTemplates() {+	viper.Set("CurrentContentLanguage", helpers.NewLanguage("en"))+ helpers.ResetConfigProvider()
+}
+
 func TestFuncsInTemplate(t *testing.T) {viper.Reset()
@@ -234,6 +239,8 @@
 	viper.Set("baseURL", "http://mysite.com/hugo/")+ tstInitTemplates()
+
 	if err != nil { 		t.Fatal("Got error on parse", err)}
@@ -2498,6 +2505,7 @@
data.Section = "blog"
 	data.Params = map[string]interface{}{"langCode": "en"}+ tstInitTemplates()
InitializeT()
 	for i, tc := range testCases {var tmp string
--- a/tpl/template_i18n.go
+++ b/tpl/template_i18n.go
@@ -25,6 +25,7 @@
var (
Logi18nWarnings bool
i18nWarningLogger = helpers.NewDistinctFeedbackLogger()
+ currentLanguage *helpers.Language
)
 type translate struct {@@ -37,11 +38,12 @@
// SetTranslateLang sets the translations language to use during template processing.
// This construction is unfortunate, but the template system is currently global.
-func SetTranslateLang(lang string) error {-	if f, ok := translator.translateFuncs[lang]; ok {+func SetTranslateLang(language *helpers.Language) error {+ currentLanguage = language
+	if f, ok := translator.translateFuncs[language.Lang]; ok {translator.current = f
 	} else {-		jww.WARN.Printf("Translation func for language %v not found, use default.", lang)+		jww.WARN.Printf("Translation func for language %v not found, use default.", language.Lang) 		translator.current = translator.translateFuncs[viper.GetString("DefaultContentLanguage")]}
return nil
--- a/tpl/template_i18n_test.go
+++ b/tpl/template_i18n_test.go
@@ -17,6 +17,7 @@
"testing"
"github.com/nicksnyder/go-i18n/i18n/bundle"
+ "github.com/spf13/hugo/helpers"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)
@@ -116,7 +117,7 @@
}
SetI18nTfuncs(i18nBundle)
- SetTranslateLang(lang)
+ SetTranslateLang(helpers.NewLanguage(lang))
translated, err := I18nTranslate(id, args)
 	if err != nil {@@ -129,6 +130,7 @@
var actual, expected string
 	viper.SetDefault("DefaultContentLanguage", "en")+	viper.Set("CurrentContentLanguage", helpers.NewLanguage("en"))// Test without and with placeholders
 	for _, enablePlaceholders := range []bool{false, true} {--
⑨