mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
parent
9096842b04
commit
bb2aa08709
6 changed files with 201 additions and 120 deletions
|
@ -14,9 +14,14 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
"github.com/spf13/afero"
|
||||
|
@ -84,6 +89,102 @@ func loadConfigFromFile(fs afero.Fs, filename string) (map[string]interface{}, e
|
|||
return m, nil
|
||||
}
|
||||
|
||||
func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provider, []string, error) {
|
||||
defaultConfigDir := filepath.Join(configDir, "_default")
|
||||
environmentConfigDir := filepath.Join(configDir, environment)
|
||||
cfg := New()
|
||||
|
||||
var configDirs []string
|
||||
// Merge from least to most specific.
|
||||
for _, dir := range []string{defaultConfigDir, environmentConfigDir} {
|
||||
if _, err := sourceFs.Stat(dir); err == nil {
|
||||
configDirs = append(configDirs, dir)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configDirs) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Keep track of these so we can watch them for changes.
|
||||
var dirnames []string
|
||||
|
||||
for _, configDir := range configDirs {
|
||||
err := afero.Walk(sourceFs, configDir, func(path string, fi os.FileInfo, err error) error {
|
||||
if fi == nil || err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
dirnames = append(dirnames, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !IsValidConfigFilename(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := paths.Filename(filepath.Base(path))
|
||||
|
||||
item, err := metadecoders.Default.UnmarshalFileToMap(sourceFs, path)
|
||||
if err != nil {
|
||||
// This will be used in error reporting, use the most specific value.
|
||||
dirnames = []string{path}
|
||||
return errors.Wrapf(err, "failed to unmarshl config for path %q", path)
|
||||
}
|
||||
|
||||
var keyPath []string
|
||||
|
||||
if name != "config" {
|
||||
// Can be params.jp, menus.en etc.
|
||||
name, lang := paths.FileAndExtNoDelimiter(name)
|
||||
|
||||
keyPath = []string{name}
|
||||
|
||||
if lang != "" {
|
||||
keyPath = []string{"languages", lang}
|
||||
switch name {
|
||||
case "menu", "menus":
|
||||
keyPath = append(keyPath, "menus")
|
||||
case "params":
|
||||
keyPath = append(keyPath, "params")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root := item
|
||||
if len(keyPath) > 0 {
|
||||
root = make(map[string]interface{})
|
||||
m := root
|
||||
for i, key := range keyPath {
|
||||
if i >= len(keyPath)-1 {
|
||||
m[key] = item
|
||||
} else {
|
||||
nm := make(map[string]interface{})
|
||||
m[key] = nm
|
||||
m = nm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate menu => menus etc.
|
||||
RenameKeys(root)
|
||||
|
||||
// Set will overwrite keys with the same name, recursively.
|
||||
cfg.Set("", root)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, dirnames, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return cfg, dirnames, nil
|
||||
|
||||
}
|
||||
|
||||
var keyAliases maps.KeyRenamer
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -79,10 +79,16 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid
|
|||
}
|
||||
|
||||
if d.AbsConfigDir != "" {
|
||||
dirnames, err := l.loadConfigFromConfigDir()
|
||||
dcfg, dirnames, err := config.LoadConfigFromDir(l.Fs, d.AbsConfigDir, l.Environment)
|
||||
if err == nil {
|
||||
configFiles = append(configFiles, dirnames...)
|
||||
if len(dirnames) > 0 {
|
||||
l.cfg.Set("", dcfg.Get(""))
|
||||
configFiles = append(configFiles, dirnames...)
|
||||
}
|
||||
} else if err != ErrNoConfigFile {
|
||||
if len(dirnames) > 0 {
|
||||
return nil, nil, l.wrapFileError(err, dirnames[0])
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
@ -381,9 +387,9 @@ func (l configLoader) collectModules(modConfig modules.Config, v1 config.Provide
|
|||
|
||||
hook := func(m *modules.ModulesConfig) error {
|
||||
for _, tc := range m.ActiveModules {
|
||||
if tc.ConfigFilename() != "" {
|
||||
if len(tc.ConfigFilenames()) > 0 {
|
||||
if tc.Watch() {
|
||||
configFilenames = append(configFilenames, tc.ConfigFilename())
|
||||
configFilenames = append(configFilenames, tc.ConfigFilenames()...)
|
||||
}
|
||||
|
||||
// Merge from theme config into v1 based on configured
|
||||
|
@ -406,6 +412,7 @@ func (l configLoader) collectModules(modConfig modules.Config, v1 config.Provide
|
|||
HookBeforeFinalize: hook,
|
||||
WorkingDir: workingDir,
|
||||
ThemesDir: themesDir,
|
||||
Environment: l.Environment,
|
||||
CacheDir: filecacheConfigs.CacheDirModules(),
|
||||
ModuleConfig: modConfig,
|
||||
IgnoreVendor: ignoreVendor,
|
||||
|
@ -468,106 +475,6 @@ func (l configLoader) loadConfig(configName string) (string, error) {
|
|||
return filename, nil
|
||||
}
|
||||
|
||||
func (l configLoader) loadConfigFromConfigDir() ([]string, error) {
|
||||
sourceFs := l.Fs
|
||||
configDir := l.AbsConfigDir
|
||||
|
||||
if _, err := sourceFs.Stat(configDir); err != nil {
|
||||
// Config dir does not exist.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
defaultConfigDir := filepath.Join(configDir, "_default")
|
||||
environmentConfigDir := filepath.Join(configDir, l.Environment)
|
||||
|
||||
var configDirs []string
|
||||
// Merge from least to most specific.
|
||||
for _, dir := range []string{defaultConfigDir, environmentConfigDir} {
|
||||
if _, err := sourceFs.Stat(dir); err == nil {
|
||||
configDirs = append(configDirs, dir)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configDirs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Keep track of these so we can watch them for changes.
|
||||
var dirnames []string
|
||||
|
||||
for _, configDir := range configDirs {
|
||||
err := afero.Walk(sourceFs, configDir, func(path string, fi os.FileInfo, err error) error {
|
||||
if fi == nil || err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
dirnames = append(dirnames, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !config.IsValidConfigFilename(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := cpaths.Filename(filepath.Base(path))
|
||||
|
||||
item, err := metadecoders.Default.UnmarshalFileToMap(sourceFs, path)
|
||||
if err != nil {
|
||||
return l.wrapFileError(err, path)
|
||||
}
|
||||
|
||||
var keyPath []string
|
||||
|
||||
if name != "config" {
|
||||
// Can be params.jp, menus.en etc.
|
||||
name, lang := cpaths.FileAndExtNoDelimiter(name)
|
||||
|
||||
keyPath = []string{name}
|
||||
|
||||
if lang != "" {
|
||||
keyPath = []string{"languages", lang}
|
||||
switch name {
|
||||
case "menu", "menus":
|
||||
keyPath = append(keyPath, "menus")
|
||||
case "params":
|
||||
keyPath = append(keyPath, "params")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root := item
|
||||
if len(keyPath) > 0 {
|
||||
root = make(map[string]interface{})
|
||||
m := root
|
||||
for i, key := range keyPath {
|
||||
if i >= len(keyPath)-1 {
|
||||
m[key] = item
|
||||
} else {
|
||||
nm := make(map[string]interface{})
|
||||
m[key] = nm
|
||||
m = nm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate menu => menus etc.
|
||||
config.RenameKeys(root)
|
||||
|
||||
// Set will overwrite keys with the same name, recursively.
|
||||
l.cfg.Set("", root)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return dirnames, nil
|
||||
}
|
||||
|
||||
func (l configLoader) loadLanguageSettings(oldLangs langs.Languages) error {
|
||||
_, err := langs.LoadLanguageSettings(l.cfg, oldLangs)
|
||||
return err
|
||||
|
|
|
@ -318,6 +318,59 @@ name = "menu-theme"
|
|||
|
||||
}
|
||||
|
||||
func TestLoadConfigFromThemeDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mainConfig := `
|
||||
theme = "test-theme"
|
||||
|
||||
[params]
|
||||
m1 = "mv1"
|
||||
`
|
||||
|
||||
themeConfig := `
|
||||
[params]
|
||||
t1 = "tv1"
|
||||
t2 = "tv2"
|
||||
`
|
||||
|
||||
themeConfigDir := filepath.Join("themes", "test-theme", "config")
|
||||
themeConfigDirDefault := filepath.Join(themeConfigDir, "_default")
|
||||
themeConfigDirProduction := filepath.Join(themeConfigDir, "production")
|
||||
|
||||
projectConfigDir := "config"
|
||||
|
||||
b := newTestSitesBuilder(t)
|
||||
b.WithConfigFile("toml", mainConfig).WithThemeConfigFile("toml", themeConfig)
|
||||
b.Assert(b.Fs.Source.MkdirAll(themeConfigDirDefault, 0777), qt.IsNil)
|
||||
b.Assert(b.Fs.Source.MkdirAll(themeConfigDirProduction, 0777), qt.IsNil)
|
||||
b.Assert(b.Fs.Source.MkdirAll(projectConfigDir, 0777), qt.IsNil)
|
||||
|
||||
b.WithSourceFile(filepath.Join(projectConfigDir, "config.toml"), `[params]
|
||||
m2 = "mv2"
|
||||
`)
|
||||
b.WithSourceFile(filepath.Join(themeConfigDirDefault, "config.toml"), `[params]
|
||||
t2 = "tv2d"
|
||||
t3 = "tv3d"
|
||||
`)
|
||||
|
||||
b.WithSourceFile(filepath.Join(themeConfigDirProduction, "config.toml"), `[params]
|
||||
t3 = "tv3p"
|
||||
`)
|
||||
|
||||
b.Build(BuildCfg{})
|
||||
|
||||
got := b.Cfg.Get("params").(maps.Params)
|
||||
|
||||
b.Assert(got, qt.DeepEquals, maps.Params{
|
||||
"t3": "tv3p",
|
||||
"m1": "mv1",
|
||||
"t1": "tv1",
|
||||
"t2": "tv2d",
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestPrivacyConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -653,6 +653,9 @@ type ClientConfig struct {
|
|||
// Absolute path to the project's themes dir.
|
||||
ThemesDir string
|
||||
|
||||
// Eg. "production"
|
||||
Environment string
|
||||
|
||||
CacheDir string // Module cache
|
||||
ModuleConfig Config
|
||||
}
|
||||
|
|
|
@ -396,17 +396,16 @@ func (c *collector) applyMounts(moduleImport Import, mod *moduleAdapter) error {
|
|||
func (c *collector) applyThemeConfig(tc *moduleAdapter) error {
|
||||
var (
|
||||
configFilename string
|
||||
cfg config.Provider
|
||||
themeCfg map[string]interface{}
|
||||
hasConfig bool
|
||||
hasConfigFile bool
|
||||
err error
|
||||
)
|
||||
|
||||
// Viper supports more, but this is the sub-set supported by Hugo.
|
||||
for _, configFormats := range config.ValidConfigFileExtensions {
|
||||
configFilename = filepath.Join(tc.Dir(), "config."+configFormats)
|
||||
hasConfig, _ = afero.Exists(c.fs, configFilename)
|
||||
if hasConfig {
|
||||
hasConfigFile, _ = afero.Exists(c.fs, configFilename)
|
||||
if hasConfigFile {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -428,20 +427,38 @@ func (c *collector) applyThemeConfig(tc *moduleAdapter) error {
|
|||
}
|
||||
}
|
||||
|
||||
if hasConfig {
|
||||
if hasConfigFile {
|
||||
if configFilename != "" {
|
||||
var err error
|
||||
cfg, err = config.FromFile(c.fs, configFilename)
|
||||
tc.cfg, err = config.FromFile(c.fs, configFilename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read module config for %q in %q", tc.Path(), configFilename)
|
||||
}
|
||||
}
|
||||
|
||||
tc.configFilename = configFilename
|
||||
tc.cfg = cfg
|
||||
tc.configFilenames = append(tc.configFilenames, configFilename)
|
||||
|
||||
}
|
||||
|
||||
config, err := decodeConfig(cfg, c.moduleConfig.replacementsMap)
|
||||
// Also check for a config dir, which we overlay on top of the file configuration.
|
||||
configDir := filepath.Join(tc.Dir(), "config")
|
||||
dcfg, dirnames, err := config.LoadConfigFromDir(c.fs, configDir, c.ccfg.Environment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dirnames) > 0 {
|
||||
tc.configFilenames = append(tc.configFilenames, dirnames...)
|
||||
|
||||
if hasConfigFile {
|
||||
// Set will overwrite existing keys.
|
||||
tc.cfg.Set("", dcfg.Get(""))
|
||||
} else {
|
||||
tc.cfg = dcfg
|
||||
}
|
||||
}
|
||||
|
||||
config, err := decodeConfig(tc.cfg, c.moduleConfig.replacementsMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -30,10 +30,10 @@ type Module interface {
|
|||
// The decoded module config and mounts.
|
||||
Config() Config
|
||||
|
||||
// Optional configuration filename (e.g. "/themes/mytheme/config.json").
|
||||
// Optional configuration filenames (e.g. "/themes/mytheme/config.json").
|
||||
// This will be added to the special configuration watch list when in
|
||||
// server mode.
|
||||
ConfigFilename() string
|
||||
ConfigFilenames() []string
|
||||
|
||||
// Directory holding files for this module.
|
||||
Dir() string
|
||||
|
@ -82,9 +82,9 @@ type moduleAdapter struct {
|
|||
|
||||
mounts []Mount
|
||||
|
||||
configFilename string
|
||||
cfg config.Provider
|
||||
config Config
|
||||
configFilenames []string
|
||||
cfg config.Provider
|
||||
config Config
|
||||
|
||||
// Set if a Go module.
|
||||
gomod *goModule
|
||||
|
@ -98,8 +98,8 @@ func (m *moduleAdapter) Config() Config {
|
|||
return m.config
|
||||
}
|
||||
|
||||
func (m *moduleAdapter) ConfigFilename() string {
|
||||
return m.configFilename
|
||||
func (m *moduleAdapter) ConfigFilenames() []string {
|
||||
return m.configFilenames
|
||||
}
|
||||
|
||||
func (m *moduleAdapter) Dir() string {
|
||||
|
|
Loading…
Reference in a new issue