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 May 13, 2023
1 parent 0cb6ca5 commit 7dfdff9
Show file tree
Hide file tree
Showing 335 changed files with 12,685 additions and 14,475 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@

*.test
*.test
imports.*
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
28 changes: 7 additions & 21 deletions cache/filecache/filecache.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import (
var ErrFatal = errors.New("fatal filecache error")

const (
filecacheRootDirname = "filecache"
FilecacheRootDirname = "filecache"
)

// Cache caches a set of files in a directory. This is usually a file on
Expand Down Expand Up @@ -301,7 +301,7 @@ func (c *Cache) isExpired(modTime time.Time) bool {
}

// For testing
func (c *Cache) getString(id string) string {
func (c *Cache) GetString(id string) string {
id = cleanID(id)

c.nlocker.Lock(id)
Expand All @@ -328,38 +328,24 @@ func (f Caches) Get(name string) *Cache {
// NewCaches creates a new set of file caches from the given
// configuration.
func NewCaches(p *helpers.PathSpec) (Caches, error) {
var dcfg Configs
if c, ok := p.Cfg.Get("filecacheConfigs").(Configs); ok {
dcfg = c
} else {
var err error
dcfg, err = DecodeConfig(p.Fs.Source, p.Cfg)
if err != nil {
return nil, err
}
}

dcfg := p.Cfg.GetConfigSection("caches").(Configs)
fs := p.Fs.Source

m := make(Caches)
for k, v := range dcfg {
var cfs afero.Fs

if v.isResourceDir {
if v.IsResourceDir {
cfs = p.BaseFs.ResourcesCache
} else {
cfs = fs
}

if cfs == nil {
// TODO(bep) we still have some places that do not initialize the
// full dependencies of a site, e.g. the import Jekyll command.
// That command does not need these caches, so let us just continue
// for now.
continue
panic("nil fs")
}

baseDir := v.Dir
baseDir := v.DirCompiled

if err := cfs.MkdirAll(baseDir, 0777); err != nil && !os.IsExist(err) {
return nil, err
Expand All @@ -368,7 +354,7 @@ func NewCaches(p *helpers.PathSpec) (Caches, error) {
bfs := afero.NewBasePathFs(cfs, baseDir)

var pruneAllRootDir string
if k == cacheKeyModules {
if k == CacheKeyModules {
pruneAllRootDir = "pkg"
}

Expand Down
103 changes: 47 additions & 56 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,110 +22,111 @@ 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,
}

const (
cacheKeyGetJSON = "getjson"
cacheKeyGetCSV = "getcsv"
cacheKeyImages = "images"
cacheKeyAssets = "assets"
cacheKeyModules = "modules"
cacheKeyGetResource = "getresource"
CacheKeyGetJSON = "getjson"
CacheKeyGetCSV = "getcsv"
CacheKeyImages = "images"
CacheKeyAssets = "assets"
CacheKeyModules = "modules"
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{
cacheKeyModules: {
CacheKeyModules: {
MaxAge: -1,
Dir: ":cacheDir/modules",
},
cacheKeyGetJSON: defaultCacheConfig,
cacheKeyGetCSV: defaultCacheConfig,
cacheKeyImages: {
CacheKeyGetJSON: defaultCacheConfig,
CacheKeyGetCSV: defaultCacheConfig,
CacheKeyImages: {
MaxAge: -1,
Dir: resourcesGenDir,
},
cacheKeyAssets: {
CacheKeyAssets: {
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 `json:"-"`

// Will resources/_gen will get its own composite filesystem that
// also checks any theme.
isResourceDir bool
IsResourceDir bool
}

// GetJSONCache gets the file cache for getJSON.
func (f Caches) GetJSONCache() *Cache {
return f[cacheKeyGetJSON]
return f[CacheKeyGetJSON]
}

// GetCSVCache gets the file cache for getCSV.
func (f Caches) GetCSVCache() *Cache {
return f[cacheKeyGetCSV]
return f[CacheKeyGetCSV]
}

// ImageCache gets the file cache for processed images.
func (f Caches) ImageCache() *Cache {
return f[cacheKeyImages]
return f[CacheKeyImages]
}

// ModulesCache gets the file cache for Hugo Modules.
func (f Caches) ModulesCache() *Cache {
return f[cacheKeyModules]
return f[CacheKeyModules]
}

// AssetsCache gets the file cache for assets (processed resources, SCSS etc.).
func (f Caches) AssetsCache() *Cache {
return f[cacheKeyAssets]
return f[CacheKeyAssets]
}

// GetResourceCache gets the file cache for remote resources.
func (f Caches) GetResourceCache() *Cache {
return f[cacheKeyGetResource]
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 @@ -170,22 +170,19 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
c[name] = cc
}

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

for k, v := range c {
dir := filepath.ToSlash(filepath.Clean(v.Dir))
hadSlash := strings.HasPrefix(dir, "/")
parts := strings.Split(dir, "/")

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
}
if isResource {
v.isResourceDir = true
v.IsResourceDir = true
}
parts[i] = resolved
}
Expand All @@ -195,33 +192,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 !v.IsResourceDir {
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)
}

if disabled {
v.MaxAge = 0
v.DirCompiled = filepath.Join(v.DirCompiled, k)
}

c[k] = v
Expand All @@ -231,17 +224,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

0 comments on commit 7dfdff9

Please sign in to comment.