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.

Also,

* Lower case the default output format names; this is in line with the custom ones (map keys) and how
it's treated all the places. This avoids doing `stringds.EqualFold` everywhere.

Closes gohugoio#10896
Closes gohugoio#10620
  • Loading branch information
bep committed Apr 15, 2023
1 parent 9906c1a commit 3bfc87e
Show file tree
Hide file tree
Showing 119 changed files with 3,350 additions and 1,434 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
6 changes: 4 additions & 2 deletions cache/filecache/filecache.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"time"

"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/config"

"github.com/gohugoio/hugo/helpers"

Expand Down Expand Up @@ -333,7 +334,8 @@ func NewCaches(p *helpers.PathSpec) (Caches, error) {
dcfg = c
} else {
var err error
dcfg, err = DecodeConfig(p.Fs.Source, p.Cfg)
// TODO1
dcfg, err = DecodeConfig(p.Fs.Source, config.BaseConfig{}, nil) // TODO1 fixme.
if err != nil {
return nil, err
}
Expand All @@ -359,7 +361,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
58 changes: 28 additions & 30 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 All @@ -21,25 +22,20 @@ import (
"time"

"github.com/gohugoio/hugo/common/maps"

"github.com/gohugoio/hugo/config"

"github.com/gohugoio/hugo/helpers"

"errors"

"github.com/mitchellh/mapstructure"
"github.com/spf13/afero"
)

const (
cachesConfigKey = "caches"

resourcesGenDir = ":resourceDir/_gen"
cacheDirProject = ":cacheDir/:project"
)

var defaultCacheConfig = Config{
var defaultCacheConfig = FileCacheConfig{
MaxAge: -1, // Never expire
Dir: cacheDirProject,
}
Expand All @@ -53,10 +49,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 +71,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 @@ -124,7 +126,7 @@ func (f Caches) GetResourceCache() *Cache {
return f[cacheKeyGetResource]
}

func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
func DecodeConfig(fs afero.Fs, bcfg config.BaseConfig, m map[string]any) (Configs, error) {
c := make(Configs)
valid := make(map[string]bool)
// Add defaults
Expand All @@ -133,8 +135,6 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
valid[k] = true
}

m := cfg.GetStringMap(cachesConfigKey)

_, isOsFs := fs.(*afero.OsFs)

for k, v := range m {
Expand Down Expand Up @@ -171,7 +171,7 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
}

// This is a very old flag in Hugo, but we need to respect it.
disabled := cfg.GetBool("ignoreCache")
disabled := false // TODO1 cfg.GetBool("ignoreCache")

for k, v := range c {
dir := filepath.ToSlash(filepath.Clean(v.Dir))
Expand All @@ -180,7 +180,7 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {

for i, part := range parts {
if strings.HasPrefix(part, ":") {
resolved, isResource, err := resolveDirPlaceholder(fs, cfg, part)
resolved, isResource, err := resolveDirPlaceholder(fs, bcfg, part)
if err != nil {
return c, err
}
Expand All @@ -195,29 +195,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 All @@ -231,17 +231,15 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
}

// Resolves :resourceDir => /myproject/resources etc., :cacheDir => ...
func resolveDirPlaceholder(fs afero.Fs, cfg config.Provider, placeholder string) (cacheDir string, isResource bool, err error) {
workingDir := cfg.GetString("workingDir")
func resolveDirPlaceholder(fs afero.Fs, bcfg config.BaseConfig, placeholder string) (cacheDir string, isResource bool, err error) {

switch strings.ToLower(placeholder) {
case ":resourcedir":
return "", true, nil
case ":cachedir":
d, err := helpers.GetCacheDir(fs, cfg)
return d, false, err
return bcfg.CacheDir, false, nil
case ":project":
return filepath.Base(workingDir), false, nil
return filepath.Base(bcfg.WorkingDir), false, nil
}

return "", false, fmt.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder)
Expand Down
24 changes: 14 additions & 10 deletions cache/filecache/filecache_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,23 @@ dir = "/path/to/c4"
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
decoded, err := DecodeConfig(fs, cfg)
m := cfg.GetStringMap("caches")
decoded, err := DecodeConfig(fs, config.BaseConfig{}, m)
c.Assert(err, qt.IsNil)

c.Assert(len(decoded), qt.Equals, 6)

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 @@ -106,7 +107,8 @@ dir = "/path/to/c4"
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
decoded, err := DecodeConfig(fs, cfg)
m := cfg.GetStringMap("caches")
decoded, err := DecodeConfig(fs, config.BaseConfig{}, m)
c.Assert(err, qt.IsNil)

c.Assert(len(decoded), qt.Equals, 6)
Expand All @@ -131,7 +133,8 @@ func TestDecodeConfigDefault(t *testing.T) {

fs := afero.NewMemMapFs()

decoded, err := DecodeConfig(fs, cfg)
m := cfg.GetStringMap("caches")
decoded, err := DecodeConfig(fs, config.BaseConfig{}, m)

c.Assert(err, qt.IsNil)

Expand All @@ -141,10 +144,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 Expand Up @@ -179,7 +182,8 @@ dir = "/"
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()

_, err = DecodeConfig(fs, cfg)
m := cfg.GetStringMap("caches")
_, err := DecodeConfig(fs, config.BaseConfig{}, m)
c.Assert(err, qt.Not(qt.IsNil))
}

Expand Down
10 changes: 6 additions & 4 deletions commands/commandeer.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ func (c *commandeer) loadConfig() error {
if configPath == "" {
configPath = dir
}
config, configFiles, err := hugolib.LoadConfig(
res, err := hugolib.LoadConfig(
hugolib.ConfigSourceDescriptor{
Fs: sourceFs,
Logger: c.logger,
Expand All @@ -364,11 +364,11 @@ func (c *commandeer) loadConfig() error {
// Just make it a warning.
c.logger.Warnln(err)
}
} else if c.mustHaveConfigFile && len(configFiles) == 0 {
} else if c.mustHaveConfigFile && len(res.ConfigFiles) == 0 {
return hugolib.ErrNoConfigFile
}

c.configFiles = configFiles
c.configFiles = res.ConfigFiles

var ok bool
loc := time.Local
Expand All @@ -395,6 +395,8 @@ func (c *commandeer) loadConfig() error {
}
}

config := res.Cfg

logger, err := c.createLogger(config)
if err != nil {
return err
Expand Down Expand Up @@ -516,7 +518,7 @@ func (c *commandeer) loadConfig() error {
return err
}

cacheDir, err := helpers.GetCacheDir(sourceFs, config)
cacheDir, err := helpers.GetCacheDir(sourceFs, config.GetString("cacheDir"))
if err != nil {
return err
}
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
}

0 comments on commit 3bfc87e

Please sign in to comment.