Skip to content

Commit

Permalink
Avoid reading from Viper for path and URL funcs
Browse files Browse the repository at this point in the history
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 gohugoio#2495
  • Loading branch information
bep committed Oct 24, 2016
1 parent 1992b35 commit 17328ee
Show file tree
Hide file tree
Showing 26 changed files with 348 additions and 90 deletions.
39 changes: 39 additions & 0 deletions helpers/configProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
21 changes: 18 additions & 3 deletions helpers/language.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)) }
Expand All @@ -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 changes: 32 additions & 0 deletions helpers/language_test.go
Original file line number Diff line number Diff line change
@@ -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"))
}
16 changes: 8 additions & 8 deletions helpers/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))

Expand All @@ -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))
Expand Down
18 changes: 15 additions & 3 deletions helpers/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand All @@ -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
Expand All @@ -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)
}
Expand All @@ -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
Expand All @@ -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)
}
Expand Down
54 changes: 54 additions & 0 deletions helpers/pathspec.go
Original file line number Diff line number Diff line change
@@ -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 changes: 45 additions & 0 deletions helpers/pathspec_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 17328ee

Please sign in to comment.