mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-16 20:53:59 +02:00
Misc config loading fixes
The main motivation behind this is simplicity and correctnes, but the new small config library is also faster: ``` BenchmarkDefaultConfigProvider/Viper-16 252418 4546 ns/op 2720 B/op 30 allocs/op BenchmarkDefaultConfigProvider/Custom-16 450756 2651 ns/op 1008 B/op 6 allocs/op ``` Fixes #8633 Fixes #8618 Fixes #8630 Updates #8591 Closes #6680 Closes #5192
This commit is contained in:
@@ -43,34 +43,136 @@ import (
|
||||
"github.com/gohugoio/hugo/config/services"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// SiteConfig represents the config in .Site.Config.
|
||||
type SiteConfig struct {
|
||||
// This contains all privacy related settings that can be used to
|
||||
// make the YouTube template etc. GDPR compliant.
|
||||
Privacy privacy.Config
|
||||
var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\n Run `hugo help new` for details.\n")
|
||||
|
||||
// Services contains config for services such as Google Analytics etc.
|
||||
Services services.Config
|
||||
// LoadConfig loads Hugo configuration into a new Viper and then adds
|
||||
// a set of defaults.
|
||||
func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provider) error) (config.Provider, []string, error) {
|
||||
|
||||
if d.Environment == "" {
|
||||
d.Environment = hugo.EnvironmentProduction
|
||||
}
|
||||
|
||||
if len(d.Environ) == 0 {
|
||||
d.Environ = os.Environ()
|
||||
}
|
||||
|
||||
var configFiles []string
|
||||
|
||||
l := configLoader{ConfigSourceDescriptor: d, cfg: config.New()}
|
||||
|
||||
if err := l.applyConfigDefaults(); err != nil {
|
||||
return l.cfg, configFiles, err
|
||||
}
|
||||
|
||||
for _, name := range d.configFilenames() {
|
||||
var filename string
|
||||
filename, err := l.loadConfig(name)
|
||||
if err == nil {
|
||||
configFiles = append(configFiles, filename)
|
||||
} else if err != ErrNoConfigFile {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if d.AbsConfigDir != "" {
|
||||
dirnames, err := l.loadConfigFromConfigDir()
|
||||
if err == nil {
|
||||
configFiles = append(configFiles, dirnames...)
|
||||
} else if err != ErrNoConfigFile {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(bep) improve this. This is currently needed to get the merge correctly.
|
||||
if l.cfg.IsSet("languages") {
|
||||
langs := l.cfg.GetParams("languages")
|
||||
for _, lang := range langs {
|
||||
langp := lang.(maps.Params)
|
||||
if _, ok := langp["menus"]; !ok {
|
||||
langp["menus"] = make(maps.Params)
|
||||
}
|
||||
if _, ok := langp["params"]; !ok {
|
||||
langp["params"] = make(maps.Params)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
l.cfg.SetDefaultMergeStrategy()
|
||||
|
||||
// We create languages based on the settings, so we need to make sure that
|
||||
// all configuration is loaded/set before doing that.
|
||||
for _, d := range doWithConfig {
|
||||
if err := d(l.cfg); err != nil {
|
||||
return l.cfg, configFiles, err
|
||||
}
|
||||
}
|
||||
|
||||
// We made this a Glob pattern in Hugo 0.75, we don't need both.
|
||||
if l.cfg.GetBool("ignoreVendor") {
|
||||
helpers.Deprecated("--ignoreVendor", "--ignoreVendorPaths **", false)
|
||||
l.cfg.Set("ignoreVendorPaths", "**")
|
||||
}
|
||||
|
||||
// Some settings are used before we're done collecting all settings,
|
||||
// so apply OS environment both before and after.
|
||||
if err := l.applyOsEnvOverrides(d.Environ); err != nil {
|
||||
return l.cfg, configFiles, err
|
||||
}
|
||||
|
||||
modulesConfig, err := l.loadModulesConfig()
|
||||
if err != nil {
|
||||
return l.cfg, configFiles, err
|
||||
}
|
||||
|
||||
// Need to run these after the modules are loaded, but before
|
||||
// they are finalized.
|
||||
collectHook := func(m *modules.ModulesConfig) error {
|
||||
// We don't need the merge strategy configuration anymore,
|
||||
// remove it so it doesn't accidentaly show up in other settings.
|
||||
l.cfg.WalkParams(func(params ...config.KeyParams) bool {
|
||||
params[len(params)-1].Params.DeleteMergeStrategy()
|
||||
return false
|
||||
})
|
||||
|
||||
if err := l.loadLanguageSettings(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mods := m.ActiveModules
|
||||
|
||||
// Apply default project mounts.
|
||||
if err := modules.ApplyProjectConfigDefaults(l.cfg, mods[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_, modulesConfigFiles, err := l.collectModules(modulesConfig, l.cfg, collectHook)
|
||||
if err != nil {
|
||||
return l.cfg, configFiles, err
|
||||
}
|
||||
|
||||
configFiles = append(configFiles, modulesConfigFiles...)
|
||||
|
||||
if err := l.applyOsEnvOverrides(d.Environ); err != nil {
|
||||
return l.cfg, configFiles, err
|
||||
}
|
||||
|
||||
if err = l.applyConfigAliases(); err != nil {
|
||||
return l.cfg, configFiles, err
|
||||
}
|
||||
|
||||
return l.cfg, configFiles, err
|
||||
}
|
||||
|
||||
func loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err error) {
|
||||
privacyConfig, err := privacy.DecodeConfig(cfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
servicesConfig, err := services.DecodeConfig(cfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
scfg.Privacy = privacyConfig
|
||||
scfg.Services = servicesConfig
|
||||
|
||||
return
|
||||
// LoadConfigDefault is a convenience method to load the default "config.toml" config.
|
||||
func LoadConfigDefault(fs afero.Fs) (config.Provider, error) {
|
||||
v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs, Filename: "config.toml"})
|
||||
return v, err
|
||||
}
|
||||
|
||||
// ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.).
|
||||
@@ -98,13 +200,6 @@ type ConfigSourceDescriptor struct {
|
||||
Environ []string
|
||||
}
|
||||
|
||||
func (d ConfigSourceDescriptor) configFilenames() []string {
|
||||
if d.Filename == "" {
|
||||
return []string{"config"}
|
||||
}
|
||||
return strings.Split(d.Filename, ",")
|
||||
}
|
||||
|
||||
func (d ConfigSourceDescriptor) configFileDir() string {
|
||||
if d.Path != "" {
|
||||
return d.Path
|
||||
@@ -112,178 +207,226 @@ func (d ConfigSourceDescriptor) configFileDir() string {
|
||||
return d.WorkingDir
|
||||
}
|
||||
|
||||
// LoadConfigDefault is a convenience method to load the default "config.toml" config.
|
||||
func LoadConfigDefault(fs afero.Fs) (*viper.Viper, error) {
|
||||
v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs, Filename: "config.toml"})
|
||||
return v, err
|
||||
func (d ConfigSourceDescriptor) configFilenames() []string {
|
||||
if d.Filename == "" {
|
||||
return []string{"config"}
|
||||
}
|
||||
return strings.Split(d.Filename, ",")
|
||||
}
|
||||
|
||||
var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\n Run `hugo help new` for details.\n")
|
||||
// SiteConfig represents the config in .Site.Config.
|
||||
type SiteConfig struct {
|
||||
// This contains all privacy related settings that can be used to
|
||||
// make the YouTube template etc. GDPR compliant.
|
||||
Privacy privacy.Config
|
||||
|
||||
// LoadConfig loads Hugo configuration into a new Viper and then adds
|
||||
// a set of defaults.
|
||||
func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provider) error) (*viper.Viper, []string, error) {
|
||||
if d.Environment == "" {
|
||||
d.Environment = hugo.EnvironmentProduction
|
||||
}
|
||||
|
||||
if len(d.Environ) == 0 {
|
||||
d.Environ = os.Environ()
|
||||
}
|
||||
|
||||
var configFiles []string
|
||||
|
||||
v := viper.New()
|
||||
l := configLoader{ConfigSourceDescriptor: d}
|
||||
|
||||
for _, name := range d.configFilenames() {
|
||||
var filename string
|
||||
filename, err := l.loadConfig(name, v)
|
||||
if err == nil {
|
||||
configFiles = append(configFiles, filename)
|
||||
} else if err != ErrNoConfigFile {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if d.AbsConfigDir != "" {
|
||||
dirnames, err := l.loadConfigFromConfigDir(v)
|
||||
if err == nil {
|
||||
configFiles = append(configFiles, dirnames...)
|
||||
} else if err != ErrNoConfigFile {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := loadDefaultSettingsFor(v); err != nil {
|
||||
return v, configFiles, err
|
||||
}
|
||||
|
||||
// We create languages based on the settings, so we need to make sure that
|
||||
// all configuration is loaded/set before doing that.
|
||||
for _, d := range doWithConfig {
|
||||
if err := d(v); err != nil {
|
||||
return v, configFiles, err
|
||||
}
|
||||
}
|
||||
|
||||
// This is invoked both after we load the main config and at the end
|
||||
// to support OS env override of config options used in the module collector.
|
||||
applyOsEnvOverrides := func() error {
|
||||
if d.Environ == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
const delim = "__env__delim"
|
||||
|
||||
// Extract all that start with the HUGO prefix.
|
||||
// The delimiter is the following rune, usually "_".
|
||||
const hugoEnvPrefix = "HUGO"
|
||||
var hugoEnv []types.KeyValueStr
|
||||
for _, v := range d.Environ {
|
||||
key, val := config.SplitEnvVar(v)
|
||||
if strings.HasPrefix(key, hugoEnvPrefix) {
|
||||
delimiterAndKey := strings.TrimPrefix(key, hugoEnvPrefix)
|
||||
if len(delimiterAndKey) < 2 {
|
||||
continue
|
||||
}
|
||||
// Allow delimiters to be case sensitive.
|
||||
// It turns out there isn't that many allowed special
|
||||
// chars in environment variables when used in Bash and similar,
|
||||
// so variables on the form HUGOxPARAMSxFOO=bar is one option.
|
||||
key := strings.ReplaceAll(delimiterAndKey[1:], delimiterAndKey[:1], delim)
|
||||
key = strings.ToLower(key)
|
||||
hugoEnv = append(hugoEnv, types.KeyValueStr{
|
||||
Key: key,
|
||||
Value: val,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for _, env := range hugoEnv {
|
||||
existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, v.Get)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if existing != nil {
|
||||
val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if owner != nil {
|
||||
owner[nestedKey] = val
|
||||
} else {
|
||||
v.Set(env.Key, val)
|
||||
}
|
||||
} else if nestedKey != "" {
|
||||
owner[nestedKey] = env.Value
|
||||
} else {
|
||||
v.Set(strings.ReplaceAll(env.Key, delim, "."), env.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
if err := applyOsEnvOverrides(); err != nil {
|
||||
return v, configFiles, err
|
||||
}
|
||||
|
||||
// We made this a Glob pattern in Hugo 0.75, we don't need both.
|
||||
if v.GetBool("ignoreVendor") {
|
||||
helpers.Deprecated("--ignoreVendor", "--ignoreVendorPaths **", false)
|
||||
v.Set("ignoreVendorPaths", "**")
|
||||
}
|
||||
|
||||
modulesConfig, err := l.loadModulesConfig(v)
|
||||
if err != nil {
|
||||
return v, configFiles, err
|
||||
}
|
||||
|
||||
// Need to run these after the modules are loaded, but before
|
||||
// they are finalized.
|
||||
collectHook := func(m *modules.ModulesConfig) error {
|
||||
if err := loadLanguageSettings(v, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mods := m.ActiveModules
|
||||
|
||||
// Apply default project mounts.
|
||||
if err := modules.ApplyProjectConfigDefaults(v, mods[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_, modulesConfigFiles, err := l.collectModules(modulesConfig, v, collectHook)
|
||||
|
||||
if err == nil && len(modulesConfigFiles) > 0 {
|
||||
configFiles = append(configFiles, modulesConfigFiles...)
|
||||
}
|
||||
|
||||
if err := applyOsEnvOverrides(); err != nil {
|
||||
return v, configFiles, err
|
||||
}
|
||||
|
||||
return v, configFiles, err
|
||||
}
|
||||
|
||||
func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error {
|
||||
_, err := langs.LoadLanguageSettings(cfg, oldLangs)
|
||||
return err
|
||||
// Services contains config for services such as Google Analytics etc.
|
||||
Services services.Config
|
||||
}
|
||||
|
||||
type configLoader struct {
|
||||
cfg config.Provider
|
||||
ConfigSourceDescriptor
|
||||
}
|
||||
|
||||
func (l configLoader) loadConfig(configName string, v *viper.Viper) (string, error) {
|
||||
// Handle some legacy values.
|
||||
func (l configLoader) applyConfigAliases() error {
|
||||
aliases := []types.KeyValueStr{{Key: "taxonomies", Value: "indexes"}}
|
||||
|
||||
for _, alias := range aliases {
|
||||
if l.cfg.IsSet(alias.Key) {
|
||||
vv := l.cfg.Get(alias.Key)
|
||||
l.cfg.Set(alias.Value, vv)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l configLoader) applyConfigDefaults() error {
|
||||
defaultSettings := maps.Params{
|
||||
"cleanDestinationDir": false,
|
||||
"watch": false,
|
||||
"resourceDir": "resources",
|
||||
"publishDir": "public",
|
||||
"themesDir": "themes",
|
||||
"buildDrafts": false,
|
||||
"buildFuture": false,
|
||||
"buildExpired": false,
|
||||
"environment": hugo.EnvironmentProduction,
|
||||
"uglyURLs": false,
|
||||
"verbose": false,
|
||||
"ignoreCache": false,
|
||||
"canonifyURLs": false,
|
||||
"relativeURLs": false,
|
||||
"removePathAccents": false,
|
||||
"titleCaseStyle": "AP",
|
||||
"taxonomies": map[string]string{"tag": "tags", "category": "categories"},
|
||||
"permalinks": make(map[string]string),
|
||||
"sitemap": config.Sitemap{Priority: -1, Filename: "sitemap.xml"},
|
||||
"disableLiveReload": false,
|
||||
"pluralizeListTitles": true,
|
||||
"forceSyncStatic": false,
|
||||
"footnoteAnchorPrefix": "",
|
||||
"footnoteReturnLinkContents": "",
|
||||
"newContentEditor": "",
|
||||
"paginate": 10,
|
||||
"paginatePath": "page",
|
||||
"summaryLength": 70,
|
||||
"rssLimit": -1,
|
||||
"sectionPagesMenu": "",
|
||||
"disablePathToLower": false,
|
||||
"hasCJKLanguage": false,
|
||||
"enableEmoji": false,
|
||||
"pygmentsCodeFencesGuessSyntax": false,
|
||||
"defaultContentLanguage": "en",
|
||||
"defaultContentLanguageInSubdir": false,
|
||||
"enableMissingTranslationPlaceholders": false,
|
||||
"enableGitInfo": false,
|
||||
"ignoreFiles": make([]string, 0),
|
||||
"disableAliases": false,
|
||||
"debug": false,
|
||||
"disableFastRender": false,
|
||||
"timeout": "30s",
|
||||
"enableInlineShortcodes": false,
|
||||
}
|
||||
|
||||
l.cfg.Merge("", defaultSettings)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l configLoader) applyOsEnvOverrides(environ []string) error {
|
||||
if len(environ) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
const delim = "__env__delim"
|
||||
|
||||
// Extract all that start with the HUGO prefix.
|
||||
// The delimiter is the following rune, usually "_".
|
||||
const hugoEnvPrefix = "HUGO"
|
||||
var hugoEnv []types.KeyValueStr
|
||||
for _, v := range environ {
|
||||
key, val := config.SplitEnvVar(v)
|
||||
if strings.HasPrefix(key, hugoEnvPrefix) {
|
||||
delimiterAndKey := strings.TrimPrefix(key, hugoEnvPrefix)
|
||||
if len(delimiterAndKey) < 2 {
|
||||
continue
|
||||
}
|
||||
// Allow delimiters to be case sensitive.
|
||||
// It turns out there isn't that many allowed special
|
||||
// chars in environment variables when used in Bash and similar,
|
||||
// so variables on the form HUGOxPARAMSxFOO=bar is one option.
|
||||
key := strings.ReplaceAll(delimiterAndKey[1:], delimiterAndKey[:1], delim)
|
||||
key = strings.ToLower(key)
|
||||
hugoEnv = append(hugoEnv, types.KeyValueStr{
|
||||
Key: key,
|
||||
Value: val,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for _, env := range hugoEnv {
|
||||
existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, l.cfg.Get)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if existing != nil {
|
||||
val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if owner != nil {
|
||||
owner[nestedKey] = val
|
||||
} else {
|
||||
l.cfg.Set(env.Key, val)
|
||||
}
|
||||
} else if nestedKey != "" {
|
||||
owner[nestedKey] = env.Value
|
||||
} else {
|
||||
// The container does not exist yet.
|
||||
l.cfg.Set(strings.ReplaceAll(env.Key, delim, "."), env.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l configLoader) collectModules(modConfig modules.Config, v1 config.Provider, hookBeforeFinalize func(m *modules.ModulesConfig) error) (modules.Modules, []string, error) {
|
||||
workingDir := l.WorkingDir
|
||||
if workingDir == "" {
|
||||
workingDir = v1.GetString("workingDir")
|
||||
}
|
||||
|
||||
themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir"))
|
||||
|
||||
var ignoreVendor glob.Glob
|
||||
if s := v1.GetString("ignoreVendorPaths"); s != "" {
|
||||
ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
|
||||
}
|
||||
|
||||
filecacheConfigs, err := filecache.DecodeConfig(l.Fs, v1)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
v1.Set("filecacheConfigs", filecacheConfigs)
|
||||
|
||||
var configFilenames []string
|
||||
|
||||
hook := func(m *modules.ModulesConfig) error {
|
||||
for _, tc := range m.ActiveModules {
|
||||
if tc.ConfigFilename() != "" {
|
||||
if tc.Watch() {
|
||||
configFilenames = append(configFilenames, tc.ConfigFilename())
|
||||
}
|
||||
|
||||
// Merge from theme config into v1 based on configured
|
||||
// merge strategy.
|
||||
v1.Merge("", tc.Cfg().Get(""))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if hookBeforeFinalize != nil {
|
||||
return hookBeforeFinalize(m)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
modulesClient := modules.NewClient(modules.ClientConfig{
|
||||
Fs: l.Fs,
|
||||
Logger: l.Logger,
|
||||
HookBeforeFinalize: hook,
|
||||
WorkingDir: workingDir,
|
||||
ThemesDir: themesDir,
|
||||
CacheDir: filecacheConfigs.CacheDirModules(),
|
||||
ModuleConfig: modConfig,
|
||||
IgnoreVendor: ignoreVendor,
|
||||
})
|
||||
|
||||
v1.Set("modulesClient", modulesClient)
|
||||
|
||||
moduleConfig, err := modulesClient.Collect()
|
||||
|
||||
// Avoid recreating these later.
|
||||
v1.Set("allModules", moduleConfig.ActiveModules)
|
||||
|
||||
if moduleConfig.GoModulesFilename != "" {
|
||||
// We want to watch this for changes and trigger rebuild on version
|
||||
// changes etc.
|
||||
configFilenames = append(configFilenames, moduleConfig.GoModulesFilename)
|
||||
}
|
||||
|
||||
return moduleConfig.ActiveModules, configFilenames, err
|
||||
}
|
||||
|
||||
func (l configLoader) loadConfig(configName string) (string, error) {
|
||||
baseDir := l.configFileDir()
|
||||
var baseFilename string
|
||||
if filepath.IsAbs(configName) {
|
||||
@@ -318,24 +461,13 @@ func (l configLoader) loadConfig(configName string, v *viper.Viper) (string, err
|
||||
return "", l.wrapFileError(err, filename)
|
||||
}
|
||||
|
||||
if err = v.MergeConfigMap(m); err != nil {
|
||||
return "", l.wrapFileError(err, filename)
|
||||
}
|
||||
// Set overwrites keys of the same name, recursively.
|
||||
l.cfg.Set("", m)
|
||||
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
func (l configLoader) wrapFileError(err error, filename string) error {
|
||||
err, _ = herrors.WithFileContextForFile(
|
||||
err,
|
||||
filename,
|
||||
filename,
|
||||
l.Fs,
|
||||
herrors.SimpleLineMatcher)
|
||||
return err
|
||||
}
|
||||
|
||||
func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]string, error) {
|
||||
func (l configLoader) loadConfigFromConfigDir() ([]string, error) {
|
||||
sourceFs := l.Fs
|
||||
configDir := l.AbsConfigDir
|
||||
|
||||
@@ -421,9 +553,8 @@ func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]string, error)
|
||||
// Migrate menu => menus etc.
|
||||
config.RenameKeys(root)
|
||||
|
||||
if err := v.MergeConfigMap(root); err != nil {
|
||||
return l.wrapFileError(err, path)
|
||||
}
|
||||
// Set will overwrite keys with the same name, recursively.
|
||||
l.cfg.Set("", root)
|
||||
|
||||
return nil
|
||||
})
|
||||
@@ -436,8 +567,13 @@ func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]string, error)
|
||||
return dirnames, nil
|
||||
}
|
||||
|
||||
func (l configLoader) loadModulesConfig(v1 *viper.Viper) (modules.Config, error) {
|
||||
modConfig, err := modules.DecodeConfig(v1)
|
||||
func (l configLoader) loadLanguageSettings(oldLangs langs.Languages) error {
|
||||
_, err := langs.LoadLanguageSettings(l.cfg, oldLangs)
|
||||
return err
|
||||
}
|
||||
|
||||
func (l configLoader) loadModulesConfig() (modules.Config, error) {
|
||||
modConfig, err := modules.DecodeConfig(l.cfg)
|
||||
if err != nil {
|
||||
return modules.Config{}, err
|
||||
}
|
||||
@@ -445,211 +581,29 @@ func (l configLoader) loadModulesConfig(v1 *viper.Viper) (modules.Config, error)
|
||||
return modConfig, nil
|
||||
}
|
||||
|
||||
func (l configLoader) collectModules(modConfig modules.Config, v1 *viper.Viper, hookBeforeFinalize func(m *modules.ModulesConfig) error) (modules.Modules, []string, error) {
|
||||
workingDir := l.WorkingDir
|
||||
if workingDir == "" {
|
||||
workingDir = v1.GetString("workingDir")
|
||||
}
|
||||
|
||||
themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir"))
|
||||
|
||||
var ignoreVendor glob.Glob
|
||||
if s := v1.GetString("ignoreVendorPaths"); s != "" {
|
||||
ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
|
||||
}
|
||||
|
||||
filecacheConfigs, err := filecache.DecodeConfig(l.Fs, v1)
|
||||
func (configLoader) loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err error) {
|
||||
privacyConfig, err := privacy.DecodeConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
v1.Set("filecacheConfigs", filecacheConfigs)
|
||||
|
||||
var configFilenames []string
|
||||
|
||||
hook := func(m *modules.ModulesConfig) error {
|
||||
for _, tc := range m.ActiveModules {
|
||||
if tc.ConfigFilename() != "" {
|
||||
if tc.Watch() {
|
||||
configFilenames = append(configFilenames, tc.ConfigFilename())
|
||||
}
|
||||
if err := l.applyThemeConfig(v1, tc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hookBeforeFinalize != nil {
|
||||
return hookBeforeFinalize(m)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
modulesClient := modules.NewClient(modules.ClientConfig{
|
||||
Fs: l.Fs,
|
||||
Logger: l.Logger,
|
||||
HookBeforeFinalize: hook,
|
||||
WorkingDir: workingDir,
|
||||
ThemesDir: themesDir,
|
||||
CacheDir: filecacheConfigs.CacheDirModules(),
|
||||
ModuleConfig: modConfig,
|
||||
IgnoreVendor: ignoreVendor,
|
||||
})
|
||||
|
||||
v1.Set("modulesClient", modulesClient)
|
||||
|
||||
moduleConfig, err := modulesClient.Collect()
|
||||
|
||||
// Avoid recreating these later.
|
||||
v1.Set("allModules", moduleConfig.ActiveModules)
|
||||
|
||||
if moduleConfig.GoModulesFilename != "" {
|
||||
// We want to watch this for changes and trigger rebuild on version
|
||||
// changes etc.
|
||||
configFilenames = append(configFilenames, moduleConfig.GoModulesFilename)
|
||||
}
|
||||
|
||||
return moduleConfig.ActiveModules, configFilenames, err
|
||||
}
|
||||
|
||||
func (l configLoader) applyThemeConfig(v1 *viper.Viper, theme modules.Module) error {
|
||||
const (
|
||||
paramsKey = "params"
|
||||
languagesKey = "languages"
|
||||
menuKey = "menus"
|
||||
)
|
||||
|
||||
v2 := theme.Cfg()
|
||||
|
||||
for _, key := range []string{paramsKey, "outputformats", "mediatypes"} {
|
||||
l.mergeStringMapKeepLeft("", key, v1, v2)
|
||||
}
|
||||
|
||||
// Only add params and new menu entries, we do not add language definitions.
|
||||
if v1.IsSet(languagesKey) && v2.IsSet(languagesKey) {
|
||||
v1Langs := v1.GetStringMap(languagesKey)
|
||||
for k := range v1Langs {
|
||||
langParamsKey := languagesKey + "." + k + "." + paramsKey
|
||||
l.mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2)
|
||||
}
|
||||
v2Langs := v2.GetStringMap(languagesKey)
|
||||
for k := range v2Langs {
|
||||
if k == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
langMenuKey := languagesKey + "." + k + "." + menuKey
|
||||
if v2.IsSet(langMenuKey) {
|
||||
// Only add if not in the main config.
|
||||
v2menus := v2.GetStringMap(langMenuKey)
|
||||
for k, v := range v2menus {
|
||||
menuEntry := menuKey + "." + k
|
||||
menuLangEntry := langMenuKey + "." + k
|
||||
if !v1.IsSet(menuEntry) && !v1.IsSet(menuLangEntry) {
|
||||
v1.Set(menuLangEntry, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add menu definitions from theme not found in project
|
||||
if v2.IsSet(menuKey) {
|
||||
v2menus := v2.GetStringMap(menuKey)
|
||||
for k, v := range v2menus {
|
||||
menuEntry := menuKey + "." + k
|
||||
if !v1.IsSet(menuEntry) {
|
||||
v1.SetDefault(menuEntry, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (configLoader) mergeStringMapKeepLeft(rootKey, key string, v1, v2 config.Provider) {
|
||||
if !v2.IsSet(key) {
|
||||
return
|
||||
}
|
||||
|
||||
if !v1.IsSet(key) && !(rootKey != "" && rootKey != key && v1.IsSet(rootKey)) {
|
||||
v1.Set(key, v2.Get(key))
|
||||
servicesConfig, err := services.DecodeConfig(cfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m1 := v1.GetStringMap(key)
|
||||
m2 := v2.GetStringMap(key)
|
||||
scfg.Privacy = privacyConfig
|
||||
scfg.Services = servicesConfig
|
||||
|
||||
for k, v := range m2 {
|
||||
if _, found := m1[k]; !found {
|
||||
if rootKey != "" && v1.IsSet(rootKey+"."+k) {
|
||||
continue
|
||||
}
|
||||
m1[k] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func loadDefaultSettingsFor(v *viper.Viper) error {
|
||||
v.RegisterAlias("indexes", "taxonomies")
|
||||
|
||||
/*
|
||||
|
||||
TODO(bep) from 0.56 these are configured as module mounts.
|
||||
v.SetDefault("contentDir", "content")
|
||||
v.SetDefault("layoutDir", "layouts")
|
||||
v.SetDefault("assetDir", "assets")
|
||||
v.SetDefault("staticDir", "static")
|
||||
v.SetDefault("dataDir", "data")
|
||||
v.SetDefault("i18nDir", "i18n")
|
||||
v.SetDefault("archetypeDir", "archetypes")
|
||||
*/
|
||||
|
||||
v.SetDefault("cleanDestinationDir", false)
|
||||
v.SetDefault("watch", false)
|
||||
v.SetDefault("resourceDir", "resources")
|
||||
v.SetDefault("publishDir", "public")
|
||||
v.SetDefault("themesDir", "themes")
|
||||
v.SetDefault("buildDrafts", false)
|
||||
v.SetDefault("buildFuture", false)
|
||||
v.SetDefault("buildExpired", false)
|
||||
v.SetDefault("environment", hugo.EnvironmentProduction)
|
||||
v.SetDefault("uglyURLs", false)
|
||||
v.SetDefault("verbose", false)
|
||||
v.SetDefault("ignoreCache", false)
|
||||
v.SetDefault("canonifyURLs", false)
|
||||
v.SetDefault("relativeURLs", false)
|
||||
v.SetDefault("removePathAccents", false)
|
||||
v.SetDefault("titleCaseStyle", "AP")
|
||||
v.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
|
||||
v.SetDefault("permalinks", make(map[string]string))
|
||||
v.SetDefault("sitemap", config.Sitemap{Priority: -1, Filename: "sitemap.xml"})
|
||||
v.SetDefault("disableLiveReload", false)
|
||||
v.SetDefault("pluralizeListTitles", true)
|
||||
v.SetDefault("forceSyncStatic", false)
|
||||
v.SetDefault("footnoteAnchorPrefix", "")
|
||||
v.SetDefault("footnoteReturnLinkContents", "")
|
||||
v.SetDefault("newContentEditor", "")
|
||||
v.SetDefault("paginate", 10)
|
||||
v.SetDefault("paginatePath", "page")
|
||||
v.SetDefault("summaryLength", 70)
|
||||
v.SetDefault("rssLimit", -1)
|
||||
v.SetDefault("sectionPagesMenu", "")
|
||||
v.SetDefault("disablePathToLower", false)
|
||||
v.SetDefault("hasCJKLanguage", false)
|
||||
v.SetDefault("enableEmoji", false)
|
||||
v.SetDefault("pygmentsCodeFencesGuessSyntax", false)
|
||||
v.SetDefault("defaultContentLanguage", "en")
|
||||
v.SetDefault("defaultContentLanguageInSubdir", false)
|
||||
v.SetDefault("enableMissingTranslationPlaceholders", false)
|
||||
v.SetDefault("enableGitInfo", false)
|
||||
v.SetDefault("ignoreFiles", make([]string, 0))
|
||||
v.SetDefault("disableAliases", false)
|
||||
v.SetDefault("debug", false)
|
||||
v.SetDefault("disableFastRender", false)
|
||||
v.SetDefault("timeout", "30s")
|
||||
v.SetDefault("enableInlineShortcodes", false)
|
||||
|
||||
return nil
|
||||
func (l configLoader) wrapFileError(err error, filename string) error {
|
||||
err, _ = herrors.WithFileContextForFile(
|
||||
err,
|
||||
filename,
|
||||
filename,
|
||||
l.Fs,
|
||||
herrors.SimpleLineMatcher)
|
||||
return err
|
||||
}
|
||||
|
Reference in New Issue
Block a user