Skip to content

Commit

Permalink
Create a struct with all of Hugo's config options
Browse files Browse the repository at this point in the history
Primary motivation is documentation, but it will also hopefully simplify the code.
  • Loading branch information
bep committed Apr 8, 2023
1 parent f1e8f01 commit 485aaef
Show file tree
Hide file tree
Showing 104 changed files with 2,875 additions and 1,421 deletions.
2 changes: 2 additions & 0 deletions cache/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package cache contains the differenct cache implementations.
package cache
2 changes: 1 addition & 1 deletion cache/filecache/filecache.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ func NewCaches(p *helpers.PathSpec) (Caches, error) {
continue
}

baseDir := v.Dir
baseDir := v.dirCompiled

if err := cfs.MkdirAll(baseDir, 0777); err != nil && !os.IsExist(err) {
return nil, err
Expand Down
37 changes: 22 additions & 15 deletions cache/filecache/filecache_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package filecache provides a file based cache for Hugo.
package filecache

import (
Expand Down Expand Up @@ -39,7 +40,7 @@ const (
cacheDirProject = ":cacheDir/:project"
)

var defaultCacheConfig = Config{
var defaultCacheConfig = FileCacheConfig{
MaxAge: -1, // Never expire
Dir: cacheDirProject,
}
Expand All @@ -53,10 +54,11 @@ const (
cacheKeyGetResource = "getresource"
)

type Configs map[string]Config
type Configs map[string]FileCacheConfig

// For internal use.
func (c Configs) CacheDirModules() string {
return c[cacheKeyModules].Dir
return c[cacheKeyModules].dirCompiled
}

var defaultCacheConfigs = Configs{
Expand All @@ -74,20 +76,25 @@ var defaultCacheConfigs = Configs{
MaxAge: -1,
Dir: resourcesGenDir,
},
cacheKeyGetResource: Config{
cacheKeyGetResource: FileCacheConfig{
MaxAge: -1, // Never expire
Dir: cacheDirProject,
},
}

type Config struct {
type FileCacheConfig struct {
// Max age of cache entries in this cache. Any items older than this will
// be removed and not returned from the cache.
// a negative value means forever, 0 means cache is disabled.
// A negative value means forever, 0 means cache is disabled.
// Hugo is leninent with what types it accepts here, but we recommend using
// a duration string, a sequence of decimal numbers, each with optional fraction and a unit suffix,
// such as "300ms", "1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
MaxAge time.Duration

// The directory where files are stored.
Dir string
Dir string
dirCompiled string

// Will resources/_gen will get its own composite filesystem that
// also checks any theme.
Expand Down Expand Up @@ -195,29 +202,29 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
if hadSlash {
dir = "/" + dir
}
v.Dir = filepath.Clean(filepath.FromSlash(dir))
v.dirCompiled = filepath.Clean(filepath.FromSlash(dir))

if !v.isResourceDir {
if isOsFs && !filepath.IsAbs(v.Dir) {
return c, fmt.Errorf("%q must resolve to an absolute directory", v.Dir)
if isOsFs && !filepath.IsAbs(v.dirCompiled) {
return c, fmt.Errorf("%q must resolve to an absolute directory", v.dirCompiled)
}

// Avoid cache in root, e.g. / (Unix) or c:\ (Windows)
if len(strings.TrimPrefix(v.Dir, filepath.VolumeName(v.Dir))) == 1 {
return c, fmt.Errorf("%q is a root folder and not allowed as cache dir", v.Dir)
if len(strings.TrimPrefix(v.dirCompiled, filepath.VolumeName(v.dirCompiled))) == 1 {
return c, fmt.Errorf("%q is a root folder and not allowed as cache dir", v.dirCompiled)
}
}

if !strings.HasPrefix(v.Dir, "_gen") {
if !strings.HasPrefix(v.dirCompiled, "_gen") {
// We do cache eviction (file removes) and since the user can set
// his/hers own cache directory, we really want to make sure
// we do not delete any files that do not belong to this cache.
// We do add the cache name as the root, but this is an extra safe
// guard. We skip the files inside /resources/_gen/ because
// that would be breaking.
v.Dir = filepath.Join(v.Dir, filecacheRootDirname, k)
v.dirCompiled = filepath.Join(v.dirCompiled, filecacheRootDirname, k)
} else {
v.Dir = filepath.Join(v.Dir, k)
v.dirCompiled = filepath.Join(v.dirCompiled, k)
}

if disabled {
Expand Down
12 changes: 6 additions & 6 deletions cache/filecache/filecache_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ dir = "/path/to/c4"

c2 := decoded["getcsv"]
c.Assert(c2.MaxAge.String(), qt.Equals, "11h0m0s")
c.Assert(c2.Dir, qt.Equals, filepath.FromSlash("/path/to/c2/filecache/getcsv"))
c.Assert(c2.dirCompiled, qt.Equals, filepath.FromSlash("/path/to/c2/filecache/getcsv"))

c3 := decoded["images"]
c.Assert(c3.MaxAge, qt.Equals, time.Duration(-1))
c.Assert(c3.Dir, qt.Equals, filepath.FromSlash("/path/to/c3/filecache/images"))
c.Assert(c3.dirCompiled, qt.Equals, filepath.FromSlash("/path/to/c3/filecache/images"))

c4 := decoded["getresource"]
c.Assert(c4.MaxAge, qt.Equals, time.Duration(-1))
c.Assert(c4.Dir, qt.Equals, filepath.FromSlash("/path/to/c4/filecache/getresource"))
c.Assert(c4.dirCompiled, qt.Equals, filepath.FromSlash("/path/to/c4/filecache/getresource"))
}

func TestDecodeConfigIgnoreCache(t *testing.T) {
Expand Down Expand Up @@ -141,10 +141,10 @@ func TestDecodeConfigDefault(t *testing.T) {
jsonConfig := decoded[cacheKeyGetJSON]

if runtime.GOOS == "windows" {
c.Assert(imgConfig.Dir, qt.Equals, filepath.FromSlash("_gen/images"))
c.Assert(imgConfig.dirCompiled, qt.Equals, filepath.FromSlash("_gen/images"))
} else {
c.Assert(imgConfig.Dir, qt.Equals, "_gen/images")
c.Assert(jsonConfig.Dir, qt.Equals, "/cache/thecache/hugoproject/filecache/getjson")
c.Assert(imgConfig.dirCompiled, qt.Equals, "_gen/images")
c.Assert(jsonConfig.dirCompiled, qt.Equals, "/cache/thecache/hugoproject/filecache/getjson")
}

c.Assert(imgConfig.isResourceDir, qt.Equals, true)
Expand Down
47 changes: 47 additions & 0 deletions common/hstrings/strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2019 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 hstrings

import (
"fmt"
"strings"

"github.com/gohugoio/hugo/compare"
)

var _ compare.Eqer = StringEqualFold("")

// StringEqualFold is a string that implements the compare.Eqer interface and considers
// two strings equal if they are equal when folded to lower case.
// The compare.Eqer interface is used in Hugo to compare values in templates (e.g. using the eq template function).
type StringEqualFold string

func (s StringEqualFold) EqualFold(s2 string) bool {
return strings.EqualFold(string(s), s2)
}

func (s StringEqualFold) String() string {
return string(s)
}

func (s StringEqualFold) Eq(s2 any) bool {
switch ss := s2.(type) {
case string:
return s.EqualFold(ss)
case fmt.Stringer:
return s.EqualFold(ss.String())
}

return false
}
36 changes: 36 additions & 0 deletions common/hstrings/strings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2023 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 hstrings

import (
"testing"

qt "github.com/frankban/quicktest"
)

func TestStringEqualFold(t *testing.T) {
c := qt.New(t)

s1 := "A"
s2 := "a"

c.Assert(StringEqualFold(s1).EqualFold(s2), qt.Equals, true)
c.Assert(StringEqualFold(s1).EqualFold(s1), qt.Equals, true)
c.Assert(StringEqualFold(s2).EqualFold(s1), qt.Equals, true)
c.Assert(StringEqualFold(s2).EqualFold(s2), qt.Equals, true)
c.Assert(StringEqualFold(s1).EqualFold("b"), qt.Equals, false)
c.Assert(StringEqualFold(s1).Eq(s2), qt.Equals, true)
c.Assert(StringEqualFold(s1).Eq("b"), qt.Equals, false)

}
18 changes: 9 additions & 9 deletions common/hugo/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ var (
vendorInfo string
)

// Info contains information about the current Hugo environment
type Info struct {
// HugoInfo contains information about the current Hugo environment
type HugoInfo struct {
CommitHash string
BuildDate string

Expand All @@ -64,30 +64,30 @@ type Info struct {
}

// Version returns the current version as a comparable version string.
func (i Info) Version() VersionString {
func (i HugoInfo) Version() VersionString {
return CurrentVersion.Version()
}

// Generator a Hugo meta generator HTML tag.
func (i Info) Generator() template.HTML {
func (i HugoInfo) Generator() template.HTML {
return template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s">`, CurrentVersion.String()))
}

func (i Info) IsProduction() bool {
func (i HugoInfo) IsProduction() bool {
return i.Environment == EnvironmentProduction
}

func (i Info) IsExtended() bool {
func (i HugoInfo) IsExtended() bool {
return IsExtended
}

// Deps gets a list of dependencies for this Hugo build.
func (i Info) Deps() []*Dependency {
func (i HugoInfo) Deps() []*Dependency {
return i.deps
}

// NewInfo creates a new Hugo Info object.
func NewInfo(environment string, deps []*Dependency) Info {
func NewInfo(environment string, deps []*Dependency) HugoInfo {
if environment == "" {
environment = EnvironmentProduction
}
Expand All @@ -104,7 +104,7 @@ func NewInfo(environment string, deps []*Dependency) Info {
goVersion = bi.GoVersion
}

return Info{
return HugoInfo{
CommitHash: commitHash,
BuildDate: buildDate,
Environment: environment,
Expand Down
33 changes: 25 additions & 8 deletions common/maps/maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,25 @@ func ToStringMapE(in any) (map[string]any, error) {
// ToParamsAndPrepare converts in to Params and prepares it for use.
// If in is nil, an empty map is returned.
// See PrepareParams.
func ToParamsAndPrepare(in any) (Params, bool) {
func ToParamsAndPrepare(in any) (Params, error) {
if types.IsNil(in) {
return Params{}, true
return Params{}, nil
}
m, err := ToStringMapE(in)
if err != nil {
return nil, false
return nil, err
}
PrepareParams(m)
return m, true
return m, nil
}

// MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
func MustToParamsAndPrepare(in any) Params {
if p, ok := ToParamsAndPrepare(in); ok {
return p
} else {
panic(fmt.Sprintf("cannot convert %T to maps.Params", in))
p, err := ToParamsAndPrepare(in)
if err != nil {
panic(fmt.Sprintf("cannot convert %T to maps.Params: %s", in, err))
}
return p
}

// ToStringMap converts in to map[string]interface{}.
Expand Down Expand Up @@ -123,6 +123,23 @@ func LookupEqualFold[T any | string](m map[string]T, key string) (T, bool) {
return s, false
}

// MergeShallow merges src into dst, but only if the key does not already exist in dst.
// The keys are compared case insensitively.
func MergeShallow(dst, src map[string]any) {
for k, v := range src {
found := false
for dk := range dst {
if strings.EqualFold(dk, k) {
found = true
break
}
}
if !found {
dst[k] = v
}
}
}

type keyRename struct {
pattern glob.Glob
newKey string
Expand Down
Loading

0 comments on commit 485aaef

Please sign in to comment.