Avoid reading from Viper for path and URL funcs
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 24 Oct 2016 11:45:30 +0000 (13:45 +0200)
committerGitHub <noreply@github.com>
Mon, 24 Oct 2016 11:45:30 +0000 (13:45 +0200)
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

26 files changed:
helpers/configProvider.go
helpers/language.go
helpers/language_test.go [new file with mode: 0644]
helpers/path.go
helpers/path_test.go
helpers/pathspec.go [new file with mode: 0644]
helpers/pathspec_test.go [new file with mode: 0644]
helpers/url.go
helpers/url_test.go
hugolib/hugo_sites_test.go
hugolib/node.go
hugolib/page.go
hugolib/pageSort_test.go
hugolib/page_permalink_test.go
hugolib/page_test.go
hugolib/pagination.go
hugolib/pagination_test.go
hugolib/permalinks.go
hugolib/permalinks_test.go
hugolib/site.go
hugolib/taxonomy.go
tpl/template.go
tpl/template_funcs.go
tpl/template_funcs_test.go
tpl/template_i18n.go
tpl/template_i18n_test.go

index 35500f6de73d857222cbe1db376c68229468c94b..a631ea63d8f7a385ec71a25a2eb7543bee658888 100644 (file)
@@ -21,16 +21,55 @@ import (
        "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
+}
index 9ebec5a65dc0d86ab2aea55dde0ea44716717fa9..0a1affd18d0a7c6e06c824c9010b62d25942a860 100644 (file)
@@ -23,6 +23,19 @@ import (
        "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) Params() map[string]interface{} {
 }
 
 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 @@ func (l *Language) Get(key string) interface{} {
                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)
 }
diff --git a/helpers/language_test.go b/helpers/language_test.go
new file mode 100644 (file)
index 0000000..f812d26
--- /dev/null
@@ -0,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"))
+}
index b8f6424709b938a4d2ee62012aa800d9143352b5..cf49858eec3dc7436b4f02770888230a2fabefff 100644 (file)
@@ -75,16 +75,16 @@ var fpb filepathBridge
 // 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 @@ func ishex(c rune) bool {
 // 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 @@ func UnicodeSanitize(s string) string {
 
        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))
index cbdcd8da2090f7a57ae3838e7e44d2019b0e8b9a..ef0c165051a716bd1dcc1ec32db8da6e78750d5a 100644 (file)
@@ -33,9 +33,14 @@ import (
        "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 @@ func TestMakePath(t *testing.T) {
 
        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,6 +73,9 @@ func TestMakePath(t *testing.T) {
 func TestMakePathSanitized(t *testing.T) {
        viper.Reset()
        defer viper.Reset()
+       initCommonTestConfig()
+
+       p := NewPathSpecFromConfig(viper.GetViper())
 
        tests := []struct {
                input    string
@@ -81,7 +90,7 @@ func TestMakePathSanitized(t *testing.T) {
        }
 
        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 TestMakePathSanitized(t *testing.T) {
 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 @@ func TestMakePathSanitizedDisablePathToLower(t *testing.T) {
        }
 
        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)
                }
diff --git a/helpers/pathspec.go b/helpers/pathspec.go
new file mode 100644 (file)
index 0000000..4c578cd
--- /dev/null
@@ -0,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
+}
diff --git a/helpers/pathspec_test.go b/helpers/pathspec_test.go
new file mode 100644 (file)
index 0000000..9cd0af8
--- /dev/null
@@ -0,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)
+}
index 49f670da2db6c51eb4f7df7fe29f17f4b1d0e8ea..ffb0ba23427c58d5218056b7bc5a4c07c0df0e91 100644 (file)
@@ -101,8 +101,8 @@ func SanitizeURLKeepTrailingSlash(in string) string {
 // 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 @@ func MakePermalink(host, plink string) *url.URL {
 }
 
 // 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 @@ func AbsURL(in string, addLanguage bool) string {
        }
 
        if addLanguage {
-               prefix := getLanguagePrefix()
+               prefix := p.getLanguagePrefix()
                if prefix != "" {
                        hasPrefix := false
                        // avoid adding language prefix if already present
@@ -191,15 +191,15 @@ func AbsURL(in string, addLanguage bool) string {
        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 @@ func IsAbsURL(path string) bool {
 
 // 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 @@ func RelURL(in string, addLanguage bool) string {
        }
 
        if addLanguage {
-               prefix := getLanguagePrefix()
+               prefix := p.getLanguagePrefix()
                if prefix != "" {
                        hasPrefix := false
                        // avoid adding language prefix if already present
@@ -288,8 +288,8 @@ func AddContextRoot(baseURL, relativePath string) string {
        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 {
index 2cf93b859dedf5acd5d7caeb28200465a46fe503..5394e95731bedb25afa5e13d7d613333df262a7d 100644 (file)
@@ -24,6 +24,10 @@ import (
 )
 
 func TestURLize(t *testing.T) {
+       initCommonTestConfig()
+
+       p := NewPathSpecFromConfig(viper.GetViper())
+
        tests := []struct {
                input    string
                expected string
@@ -37,7 +41,7 @@ func TestURLize(t *testing.T) {
        }
 
        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 @@ func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
 
        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 @@ func doTestRelURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
        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 {
index b7a52b1e4f1126b2a96bc21a98374cbee5ec5e65..be2ba9803383cb3c474597390d768cda2793d177 100644 (file)
@@ -35,6 +35,7 @@ func testCommonResetState() {
        hugofs.InitMemFs()
        viper.Reset()
        viper.SetFs(hugofs.Source())
+       helpers.ResetConfigProvider()
        loadDefaultSettings()
 
        // Default is false, but true is easier to use as default in tests
index eefb78becd70c6932d1b4552792716610aceea6f..16c83e2e8f0c4293868ccc4782605151735b014d 100644 (file)
@@ -178,7 +178,7 @@ func (n *Node) URL() string {
 }
 
 func (n *Node) Permalink() string {
-       return permalink(n.URL())
+       return n.Site.permalink(n.URL())
 }
 
 // Scratch returns the writable context associated with this Node.
index fee7c334a0a4467c495b0e5c8e164652f1d08c20..484425697a27161dc486856560494703c05a3591 100644 (file)
@@ -569,9 +569,9 @@ func (p *Page) analyzePage() {
 
 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 @@ func (p *Page) TargetPath() (outfile string) {
                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)))
 }
index 23a3fd07cea58107c39418d87fc128064841bfa0..738fcc80e1a73dbfc3ed3d0a30abd6b85d6f7259 100644 (file)
@@ -20,6 +20,7 @@ import (
        "testing"
        "time"
 
+       "github.com/spf13/hugo/helpers"
        "github.com/spf13/hugo/source"
        "github.com/stretchr/testify/assert"
 )
@@ -134,6 +135,8 @@ func setSortVals(dates [3]time.Time, titles [3]string, weights [3]int, pages Pag
 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 @@ func createSortTestPages(num int) Pages {
                                        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)))},
                }
index d185bebabfa47ac2e230e13c5a7ffcfdd3a866cd..eb3a6c878e64983aa56c4baebd029818f09a216a 100644 (file)
@@ -18,6 +18,7 @@ import (
        "path/filepath"
        "testing"
 
+       "github.com/spf13/hugo/helpers"
        "github.com/spf13/hugo/source"
        "github.com/spf13/viper"
 )
@@ -59,17 +60,18 @@ func TestPermalink(t *testing.T) {
        }
 
        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{
                                        Section: "z",
                                        URL:     test.url,
                                },
-                               Site: newSiteInfoDefaultLanguage(string(test.base)),
+                               Site: &info,
                        },
                        Source: Source{File: *source.NewFile(filepath.FromSlash(test.file))},
                }
index 0bb924f0162fb156ab952de7533217d099905c40..342ff3bd5edaa5191c842216506fa7a7a7acafd1 100644 (file)
@@ -1122,7 +1122,8 @@ func TestPagePaths(t *testing.T) {
 
        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
index 5bba5d89fb02d0ef4fb4f6b0e60f907bd6e52fc3..81f3e0cda39c47c14fb348f6e35f2066bacaa066 100644 (file)
@@ -508,16 +508,16 @@ func newPaginator(elements []paginatedElement, total, size int, urlFactory pagin
 }
 
 func newPaginationURLFactory(pathElements ...string) paginationURLFactory {
-       paginatePath := helpers.Config().GetString("paginatePath")
+       pathSpec := helpers.CurrentPathSpec()
 
        return func(page int) string {
                var rel string
                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)
        }
 }
index 78665046948144a20e41944ded228e6cc9a39c50..7bbc380587eb69bdc7d0039618621e1d899c5691 100644 (file)
@@ -19,6 +19,7 @@ import (
        "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 TestPage(t *testing.T) {
 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 @@ func createTestPages(num int) Pages {
                                        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)))},
                }
index 581f8740fc2461b015ef338ca9209aeb8ee82dab..c149ae13d203b1de92baeaa35873fd7809b7e96b 100644 (file)
@@ -19,8 +19,6 @@ import (
        "regexp"
        "strconv"
        "strings"
-
-       "github.com/spf13/hugo/helpers"
 )
 
 // pathPattern represents a string which builds up a URL from attributes
@@ -152,14 +150,14 @@ func pageToPermalinkDate(p *Page, dateField string) (string, error) {
 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
 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 @@ func pageToPermalinkSlugElseTitle(p *Page, a string) (string, error) {
                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)
 }
index 672cf2d6628d06b5ba0b4467abb10b869ba82040..bdc96d41a16c32bb3dceeadf98cc269de75ee780 100644 (file)
@@ -16,6 +16,8 @@ package hugolib
 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 TestPermalinkValidation(t *testing.T) {
 
 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)
        }
index f6a787298dc5d605655449d19c5d582fc910e6b1..7f96bb5aa365618989e57e7de6c653eb0ed9d40f 100644 (file)
@@ -117,7 +117,8 @@ func (s *Site) reset() *Site {
 
 // 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 @@ func newSiteFromSources(pathContentPairs ...string) *Site {
                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 @@ type SiteInfo struct {
        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 @@ func (s *Site) setCurrentLanguageConfig() error {
        // 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 @@ func (s *SiteInfo) HomeAbsURL() string {
        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 @@ func (s *Site) initializeSiteInfo() {
                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 @@ func (s *Site) initializeSiteInfo() {
                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 @@ func (s *SiteInfo) createNodeMenuEntryURL(in string) string {
        }
        // 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 @@ func (s *Site) renderAliases() error {
        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 @@ func (s *Site) newTaxonomyNode(prepare bool, t taxRenderInfo, counter int) (*Nod
        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 @@ func (s *Site) renderSectionLists(prepare bool) error {
                        []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 @@ func (s *Site) renderSectionLists(prepare bool) error {
                        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 @@ func (s *Site) newHomeNode(prepare bool, counter int) *Node {
        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 @@ func (s *Site) renderSitemap() error {
        // 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) Stats() {
 }
 
 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)
 }
index ffe586ffc02b15175ef2776275cdb05eee0f5807..112d22b373223dd4e9367b8dba6ad54da84e2c7e 100644 (file)
@@ -52,7 +52,7 @@ type OrderedTaxonomyEntry struct {
 
 // 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.
index 1fba368e4fcae1ebce6802f2bf5dad1fb45ec2e0..fa358ca37e302b231207924557ebb82763916cf4 100644 (file)
@@ -94,6 +94,10 @@ func New() Template {
 
        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
        }
index e820ccf8db944e01d5251bade142767b07d27a84..d24bc7ee8d8e3058f3f07056dd6b7b24d79bf4b7 100644 (file)
@@ -47,7 +47,9 @@ import (
        "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 @@ func absURL(a interface{}) (template.HTML, error) {
        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 @@ func relURL(a interface{}) (template.HTML, error) {
        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 @@ func init() {
                        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 @@ func init() {
                        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 @@ func init() {
                "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,
index 7f46aeba9b7fcfa903dfd156974b63c61db0e1a9..071910579b065a2829ecaee808ccbcf35c96dfb3 100644 (file)
@@ -63,6 +63,11 @@ func tstIsLt(tp tstCompareType) bool {
        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 @@ urlize: bat-man
 
        viper.Set("baseURL", "http://mysite.com/hugo/")
 
+       tstInitTemplates()
+
        if err != nil {
                t.Fatal("Got error on parse", err)
        }
@@ -2498,6 +2505,7 @@ func TestPartialCached(t *testing.T) {
        data.Section = "blog"
        data.Params = map[string]interface{}{"langCode": "en"}
 
+       tstInitTemplates()
        InitializeT()
        for i, tc := range testCases {
                var tmp string
index 453351a108f39219f08ee820e2b39d2d867a4f34..080bfbd5427eae20cfb7802df9214f85339a302e 100644 (file)
@@ -25,6 +25,7 @@ import (
 var (
        Logi18nWarnings   bool
        i18nWarningLogger = helpers.NewDistinctFeedbackLogger()
+       currentLanguage   *helpers.Language
 )
 
 type translate struct {
@@ -37,11 +38,12 @@ var translator *translate
 
 // 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
index ffb27ae1d14e1b782d2c68e252637ec08bf8f5cc..29235b4625ed28e96c1f5116914791150359b334 100644 (file)
@@ -17,6 +17,7 @@ import (
        "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 @@ func doTestI18nTranslate(t *testing.T, data map[string][]byte, lang, id string,
        }
 
        SetI18nTfuncs(i18nBundle)
-       SetTranslateLang(lang)
+       SetTranslateLang(helpers.NewLanguage(lang))
 
        translated, err := I18nTranslate(id, args)
        if err != nil {
@@ -129,6 +130,7 @@ func TestI18nTranslate(t *testing.T) {
        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} {