Create a struct with all of Hugo's config options

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 #10896
Closes #10620
This commit is contained in:
Bjørn Erik Pedersen 2023-01-04 18:24:36 +01:00
parent 6aededf6b4
commit 241b21b0fd
337 changed files with 13377 additions and 14898 deletions

View file

@ -433,9 +433,9 @@ func (c *Client) Clean(pattern string) error {
if g != nil && !g.Match(m.Path) {
continue
}
_, err = hugofs.MakeReadableAndRemoveAllModulePkgDir(c.fs, m.Dir)
dirCount, err := hugofs.MakeReadableAndRemoveAllModulePkgDir(c.fs, m.Dir)
if err == nil {
c.logger.Printf("hugo: cleaned module cache for %q", m.Path)
c.logger.Printf("hugo: removed %d dirs in module cache for %q", dirCount, m.Path)
}
}
return err

View file

@ -52,20 +52,6 @@ func IsNotExist(err error) bool {
return errors.Is(err, os.ErrNotExist)
}
// CreateProjectModule creates modules from the given config.
// This is used in tests only.
func CreateProjectModule(cfg config.Provider) (Module, error) {
workingDir := cfg.GetString("workingDir")
var modConfig Config
mod := createProjectModule(nil, workingDir, modConfig)
if err := ApplyProjectConfigDefaults(cfg, mod); err != nil {
return nil, err
}
return mod, nil
}
func (h *Client) Collect() (ModulesConfig, error) {
mc, coll := h.collect(true)
if coll.err != nil {
@ -90,6 +76,9 @@ func (h *Client) Collect() (ModulesConfig, error) {
}
func (h *Client) collect(tidy bool) (ModulesConfig, *collector) {
if h == nil {
panic("nil client")
}
c := &collector{
Client: h,
}
@ -133,6 +122,16 @@ type ModulesConfig struct {
GoWorkspaceFilename string
}
func (m ModulesConfig) HasConfigFile() bool {
for _, mod := range m.ActiveModules {
if len(mod.ConfigFilenames()) > 0 {
return true
}
}
return false
}
func (m *ModulesConfig) setActiveMods(logger loggers.Logger) error {
var activeMods Modules
for _, mod := range m.AllModules {
@ -230,6 +229,7 @@ func (c *collector) getVendoredDir(path string) (vendoredModule, bool) {
}
func (c *collector) add(owner *moduleAdapter, moduleImport Import, disabled bool) (*moduleAdapter, error) {
var (
mod *goModule
moduleDir string
@ -299,7 +299,7 @@ func (c *collector) add(owner *moduleAdapter, moduleImport Import, disabled bool
return nil, nil
}
if found, _ := afero.Exists(c.fs, moduleDir); !found {
c.err = c.wrapModuleNotFound(fmt.Errorf(`module %q not found; either add it as a Hugo Module or store it in %q.`, modulePath, c.ccfg.ThemesDir))
c.err = c.wrapModuleNotFound(fmt.Errorf(`module %q not found in % q; either add it as a Hugo Module or store it in %q.`, modulePath, moduleDir, c.ccfg.ThemesDir))
return nil, nil
}
}
@ -347,7 +347,7 @@ func (c *collector) addAndRecurse(owner *moduleAdapter, disabled bool) error {
moduleConfig := owner.Config()
if owner.projectMod {
if err := c.applyMounts(Import{}, owner); err != nil {
return err
return fmt.Errorf("failed to apply mounts for project module: %w", err)
}
}
@ -618,7 +618,7 @@ func (c *collector) mountCommonJSConfig(owner *moduleAdapter, mounts []Mount) ([
// Mount the common JS config files.
fis, err := afero.ReadDir(c.fs, owner.Dir())
if err != nil {
return mounts, err
return mounts, fmt.Errorf("failed to read dir %q: %q", owner.Dir(), err)
}
for _, fi := range fis {

View file

@ -20,10 +20,9 @@ import (
"strings"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/langs"
"github.com/mitchellh/mapstructure"
)
@ -58,12 +57,9 @@ var DefaultModuleConfig = Config{
// ApplyProjectConfigDefaults applies default/missing module configuration for
// the main project.
func ApplyProjectConfigDefaults(cfg config.Provider, mod Module) error {
moda := mod.(*moduleAdapter)
func ApplyProjectConfigDefaults(mod Module, cfgs ...config.AllProvider) error {
// Map legacy directory config into the new module.
languages := cfg.Get("languagesSortedDefaultFirst").(langs.Languages)
isMultiHost := languages.IsMultihost()
moda := mod.(*moduleAdapter)
// To bridge between old and new configuration format we need
// a way to make sure all of the core components are configured on
@ -75,121 +71,92 @@ func ApplyProjectConfigDefaults(cfg config.Provider, mod Module) error {
}
}
type dirKeyComponent struct {
key string
component string
multilingual bool
}
dirKeys := []dirKeyComponent{
{"contentDir", files.ComponentFolderContent, true},
{"dataDir", files.ComponentFolderData, false},
{"layoutDir", files.ComponentFolderLayouts, false},
{"i18nDir", files.ComponentFolderI18n, false},
{"archetypeDir", files.ComponentFolderArchetypes, false},
{"assetDir", files.ComponentFolderAssets, false},
{"", files.ComponentFolderStatic, isMultiHost},
}
createMountsFor := func(d dirKeyComponent, cfg config.Provider) []Mount {
var lang string
if language, ok := cfg.(*langs.Language); ok {
lang = language.Lang
}
// Static mounts are a little special.
if d.component == files.ComponentFolderStatic {
var mounts []Mount
staticDirs := getStaticDirs(cfg)
if len(staticDirs) > 0 {
componentsConfigured[d.component] = true
}
for _, dir := range staticDirs {
mounts = append(mounts, Mount{Lang: lang, Source: dir, Target: d.component})
}
return mounts
}
if cfg.IsSet(d.key) {
source := cfg.GetString(d.key)
componentsConfigured[d.component] = true
return []Mount{{
// No lang set for layouts etc.
Source: source,
Target: d.component,
}}
}
return nil
}
createMounts := func(d dirKeyComponent) []Mount {
var mounts []Mount
if d.multilingual {
if d.component == files.ComponentFolderContent {
seen := make(map[string]bool)
hasContentDir := false
for _, language := range languages {
if language.ContentDir != "" {
hasContentDir = true
break
}
}
if hasContentDir {
for _, language := range languages {
contentDir := language.ContentDir
if contentDir == "" {
contentDir = files.ComponentFolderContent
}
if contentDir == "" || seen[contentDir] {
continue
}
seen[contentDir] = true
mounts = append(mounts, Mount{Lang: language.Lang, Source: contentDir, Target: d.component})
}
}
componentsConfigured[d.component] = len(seen) > 0
} else {
for _, language := range languages {
mounts = append(mounts, createMountsFor(d, language)...)
}
}
} else {
mounts = append(mounts, createMountsFor(d, cfg)...)
}
return mounts
}
var mounts []Mount
for _, dirKey := range dirKeys {
if componentsConfigured[dirKey.component] {
for _, component := range []string{
files.ComponentFolderContent,
files.ComponentFolderData,
files.ComponentFolderLayouts,
files.ComponentFolderI18n,
files.ComponentFolderArchetypes,
files.ComponentFolderAssets,
files.ComponentFolderStatic,
} {
if componentsConfigured[component] {
continue
}
mounts = append(mounts, createMounts(dirKey)...)
first := cfgs[0]
dirsBase := first.DirsBase()
isMultiHost := first.IsMultihost()
for i, cfg := range cfgs {
dirs := cfg.Dirs()
var dir string
var dropLang bool
switch component {
case files.ComponentFolderContent:
dir = dirs.ContentDir
dropLang = dir == dirsBase.ContentDir
case files.ComponentFolderData:
dir = dirs.DataDir
case files.ComponentFolderLayouts:
dir = dirs.LayoutDir
case files.ComponentFolderI18n:
dir = dirs.I18nDir
case files.ComponentFolderArchetypes:
dir = dirs.ArcheTypeDir
case files.ComponentFolderAssets:
dir = dirs.AssetDir
case files.ComponentFolderStatic:
// For static dirs, we only care about the language in multihost setups.
dropLang = !isMultiHost
}
var perLang bool
switch component {
case files.ComponentFolderContent, files.ComponentFolderStatic:
perLang = true
default:
}
if i > 0 && !perLang {
continue
}
var lang string
if perLang && !dropLang {
lang = cfg.Language().Lang
}
// Static mounts are a little special.
if component == files.ComponentFolderStatic {
staticDirs := cfg.StaticDirs()
for _, dir := range staticDirs {
mounts = append(mounts, Mount{Lang: lang, Source: dir, Target: component})
}
continue
}
if dir != "" {
mounts = append(mounts, Mount{Lang: lang, Source: dir, Target: component})
}
}
}
// Add default configuration
for _, dirKey := range dirKeys {
if componentsConfigured[dirKey.component] {
moda.mounts = append(moda.mounts, mounts...)
// Temporary: Remove duplicates.
seen := make(map[string]bool)
var newMounts []Mount
for _, m := range moda.mounts {
key := m.Source + m.Target + m.Lang
if seen[key] {
continue
}
mounts = append(mounts, Mount{Source: dirKey.component, Target: dirKey.component})
seen[key] = true
newMounts = append(newMounts, m)
}
// Prepend the mounts from configuration.
mounts = append(moda.mounts, mounts...)
moda.mounts = mounts
moda.mounts = newMounts
return nil
}
@ -275,7 +242,6 @@ func decodeConfig(cfg config.Provider, pathReplacements map[string]string) (Conf
Path: imp,
})
}
}
return c, nil
@ -283,7 +249,10 @@ func decodeConfig(cfg config.Provider, pathReplacements map[string]string) (Conf
// Config holds a module config.
type Config struct {
Mounts []Mount
// File system mounts.
Mounts []Mount
// Module imports.
Imports []Import
// Meta info about this module (license information etc.).
@ -292,8 +261,7 @@ type Config struct {
// Will be validated against the running Hugo version.
HugoVersion HugoVersion
// A optional Glob pattern matching module paths to skip when vendoring, e.g.
// "github.com/**".
// Optional Glob pattern matching module paths to skip when vendoring, e.g. “github.com/**”
NoVendor string
// When enabled, we will pick the vendored module closest to the module
@ -303,21 +271,31 @@ type Config struct {
// so once it is in use it cannot be redefined.
VendorClosest bool
// A comma separated (or a slice) list of module path to directory replacement mapping,
// e.g. github.com/bep/my-theme -> ../..,github.com/bep/shortcodes -> /some/path.
// This is mostly useful for temporary locally development of a module, and then it makes sense to set it as an
// OS environment variable, e.g: env HUGO_MODULE_REPLACEMENTS="github.com/bep/my-theme -> ../..".
// Any relative path is relate to themesDir, and absolute paths are allowed.
Replacements []string
replacementsMap map[string]string
// Configures GOPROXY.
// Defines the proxy server to use to download remote modules. Default is direct, which means “git clone” and similar.
// Configures GOPROXY when running the Go command for module operations.
Proxy string
// Configures GONOPROXY.
// Comma separated glob list matching paths that should not use the proxy configured above.
// Configures GONOPROXY when running the Go command for module operations.
NoProxy string
// Configures GOPRIVATE.
// Comma separated glob list matching paths that should be treated as private.
// Configures GOPRIVATE when running the Go command for module operations.
Private string
// Defaults to "off".
// Set to a work file, e.g. hugo.work, to enable Go "Workspace" mode.
// Can be relative to the working directory or absolute.
// Requires Go 1.18+
// See https://tip.golang.org/doc/go1.18
// Requires Go 1.18+.
// Note that this can also be set via OS env, e.g. export HUGO_MODULE_WORKSPACE=/my/hugo.work.
Workspace string
}
@ -387,21 +365,33 @@ func (v HugoVersion) IsValid() bool {
}
type Import struct {
Path string // Module path
pathProjectReplaced bool // Set when Path is replaced in project config.
IgnoreConfig bool // Ignore any config in config.toml (will still follow imports).
IgnoreImports bool // Do not follow any configured imports.
NoMounts bool // Do not mount any folder in this import.
NoVendor bool // Never vendor this import (only allowed in main project).
Disable bool // Turn off this module.
Mounts []Mount
// Module path
Path string
// Set when Path is replaced in project config.
pathProjectReplaced bool
// Ignore any config in config.toml (will still follow imports).
IgnoreConfig bool
// Do not follow any configured imports.
IgnoreImports bool
// Do not mount any folder in this import.
NoMounts bool
// Never vendor this import (only allowed in main project).
NoVendor bool
// Turn off this module.
Disable bool
// File mounts.
Mounts []Mount
}
type Mount struct {
Source string // relative path in source repo, e.g. "scss"
Target string // relative target path, e.g. "assets/bootstrap/scss"
// Relative path in source repo, e.g. "scss".
Source string
Lang string // any language code associated with this mount.
// Relative target path, e.g. "assets/bootstrap/scss".
Target string
// Any file in this mount will be associated with this language.
Lang string
// Include only files matching the given Glob patterns (string or slice).
IncludeFiles any
@ -423,19 +413,3 @@ func (m Mount) ComponentAndName() (string, string) {
c, n, _ := strings.Cut(m.Target, fileSeparator)
return c, n
}
func getStaticDirs(cfg config.Provider) []string {
var staticDirs []string
for i := -1; i <= 10; i++ {
staticDirs = append(staticDirs, getStringOrStringSlice(cfg, "staticDir", i)...)
}
return staticDirs
}
func getStringOrStringSlice(cfg config.Provider, key string, id int) []string {
if id >= 0 {
key = fmt.Sprintf("%s%d", key, id)
}
return config.GetStringSlicePreserveString(cfg, key)
}