mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-29 22:29:56 +02:00
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
This commit is contained in:
committed by
GitHub
parent
dffd7da07c
commit
a10b2cd372
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
32
helpers/language_test.go
Normal file
32
helpers/language_test.go
Normal file
@@ -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"))
|
||||
}
|
@@ -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))
|
||||
|
@@ -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)
|
||||
}
|
||||
|
54
helpers/pathspec.go
Normal file
54
helpers/pathspec.go
Normal file
@@ -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
|
||||
}
|
45
helpers/pathspec_test.go
Normal file
45
helpers/pathspec_test.go
Normal file
@@ -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)
|
||||
}
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
Reference in New Issue
Block a user