diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2018-11-15 09:28:02 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2018-12-11 13:08:36 +0100 |
commit | 7829474088f835251f04caa1121d47e35fe89f7e (patch) | |
tree | f9b91d601befc966162036559e4418bebf46d643 /hugolib | |
parent | 256418917c6642f7e5b3d3206ff4b6fa03b1cb28 (diff) |
Add /config dir support
This commit adds support for a configuration directory (default `config`). The different pieces in this puzzle are:
* A new `--environment` (or `-e`) flag. This can also be set with the `HUGO_ENVIRONMENT` OS environment variable. The value for `environment` defaults to `production` when running `hugo` and `development` when running `hugo server`. You can set it to any value you want (e.g. `hugo server -e "Sensible Environment"`), but as it is used to load configuration from the file system, the letter case may be important. You can get this value in your templates with `{{ hugo.Environment }}`.
* A new `--configDir` flag (defaults to `config` below your project). This can also be set with `HUGO_CONFIGDIR` OS environment variable.
If the `configDir` exists, the configuration files will be read and merged on top of each other from left to right; the right-most value will win on duplicates.
Given the example tree below:
If `environment` is `production`, the left-most `config.toml` would be the one directly below the project (this can now be omitted if you want), and then `_default/config.toml` and finally `production/config.toml`. And since these will be merged, you can just provide the environment specific configuration setting in you production config, e.g. `enableGitInfo = true`. The order within the directories will be lexical (`config.toml` and then `params.toml`).
```bash
config
├── _default
│ ├── config.toml
│ ├── languages.toml
│ ├── menus
│ │ ├── menus.en.toml
│ │ └── menus.zh.toml
│ └── params.toml
├── development
│ └── params.toml
└── production
├── config.toml
└── params.toml
```
Some configuration maps support the language code in the filename (e.g. `menus.en.toml`): `menus` (`menu` also works) and `params`.
Also note that the only folders with "a meaning" in the above listing is the top level directories below `config`. The `menus` sub folder is just added for better organization.
We use `TOML` in the example above, but Hugo also supports `JSON` and `YAML` as configuration formats. These can be mixed.
Fixes #5422
Diffstat (limited to 'hugolib')
-rw-r--r-- | hugolib/config.go | 292 | ||||
-rw-r--r-- | hugolib/config_test.go | 16 | ||||
-rw-r--r-- | hugolib/configdir_test.go | 152 | ||||
-rw-r--r-- | hugolib/hugo_sites.go | 10 | ||||
-rw-r--r-- | hugolib/hugo_sites_build.go | 8 | ||||
-rw-r--r-- | hugolib/hugo_sites_build_test.go | 15 | ||||
-rw-r--r-- | hugolib/page.go | 7 | ||||
-rw-r--r-- | hugolib/page_test.go | 2 | ||||
-rw-r--r-- | hugolib/paths/themes.go | 14 | ||||
-rw-r--r-- | hugolib/site.go | 4 | ||||
-rw-r--r-- | hugolib/testhelpers_test.go | 20 |
11 files changed, 425 insertions, 115 deletions
diff --git a/hugolib/config.go b/hugolib/config.go index 77ebb42ae..3a452d5fd 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -14,14 +14,19 @@ package hugolib import ( - "errors" "fmt" - "io" + + "os" + "path/filepath" "strings" - "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/parser/metadecoders" + "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/common/hugo" + "github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/hugolib/paths" + "github.com/pkg/errors" _errors "github.com/pkg/errors" "github.com/gohugoio/hugo/langs" @@ -65,96 +70,84 @@ func loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err error) { type ConfigSourceDescriptor struct { Fs afero.Fs - // Full path to the config file to use, i.e. /my/project/config.toml + // Path to the config file to use, e.g. /my/project/config.toml Filename string // The path to the directory to look for configuration. Is used if Filename is not - // set. + // set or if it is set to a relative filename. Path string // The project's working dir. Is used to look for additional theme config. WorkingDir string + + // The (optional) directory for additional configuration files. + AbsConfigDir string + + // production, development + Environment 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 + } + 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 } -var ErrNoConfigFile = errors.New("Unable to locate Config file. Perhaps you need to create a new site.\n Run `hugo help new` for details.\n") +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") // 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 + } + var configFiles []string - fs := d.Fs v := viper.New() - v.SetFs(fs) - - if d.Path == "" { - d.Path = "." - } + l := configLoader{ConfigSourceDescriptor: d} - configFilenames := d.configFilenames() v.AutomaticEnv() v.SetEnvPrefix("hugo") - v.SetConfigFile(configFilenames[0]) - v.AddConfigPath(d.Path) - applyFileContext := func(filename string, err error) error { - err, _ = herrors.WithFileContextForFile( - err, - filename, - filename, - fs, - herrors.SimpleLineMatcher) + var cerr error - return err - } - - var configFileErr error - - err := v.ReadInConfig() - if err != nil { - if _, ok := err.(viper.ConfigParseError); ok { - return nil, configFiles, applyFileContext(v.ConfigFileUsed(), err) + for _, name := range d.configFilenames() { + var filename string + if filename, cerr = l.loadConfig(name, v); cerr != nil && cerr != ErrNoConfigFile { + return nil, nil, cerr } - configFileErr = ErrNoConfigFile + configFiles = append(configFiles, filename) } - if configFileErr == nil { - - if cf := v.ConfigFileUsed(); cf != "" { - configFiles = append(configFiles, cf) + if d.AbsConfigDir != "" { + dirnames, err := l.loadConfigFromConfigDir(v) + if err == nil { + configFiles = append(configFiles, dirnames...) } - - for _, configFile := range configFilenames[1:] { - var r io.Reader - var err error - if r, err = fs.Open(configFile); err != nil { - return nil, configFiles, fmt.Errorf("Unable to open Config file.\n (%s)\n", err) - } - if err = v.MergeConfig(r); err != nil { - return nil, configFiles, applyFileContext(configFile, err) - } - configFiles = append(configFiles, configFile) - } - + cerr = err } if err := loadDefaultSettingsFor(v); err != nil { return v, configFiles, err } - if configFileErr == nil { - - themeConfigFiles, err := loadThemeConfig(d, v) + if cerr == nil { + themeConfigFiles, err := l.loadThemeConfig(v) if err != nil { return v, configFiles, err } @@ -176,8 +169,179 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid return v, configFiles, err } - return v, configFiles, configFileErr + return v, configFiles, cerr + +} + +type configLoader struct { + ConfigSourceDescriptor +} + +func (l configLoader) wrapFileInfoError(err error, fi os.FileInfo) error { + rfi, ok := fi.(hugofs.RealFilenameInfo) + if !ok { + return err + } + return l.wrapFileError(err, rfi.RealFilename()) +} + +func (l configLoader) loadConfig(configName string, v *viper.Viper) (string, error) { + baseDir := l.configFileDir() + var baseFilename string + if filepath.IsAbs(configName) { + baseFilename = configName + } else { + baseFilename = filepath.Join(baseDir, configName) + } + + var filename string + fileExt := helpers.ExtNoDelimiter(configName) + if fileExt != "" { + exists, _ := helpers.Exists(baseFilename, l.Fs) + if exists { + filename = baseFilename + } + } else { + for _, ext := range []string{"toml", "yaml", "yml", "json"} { + filenameToCheck := baseFilename + "." + ext + exists, _ := helpers.Exists(filenameToCheck, l.Fs) + if exists { + filename = filenameToCheck + fileExt = ext + break + } + } + } + + if filename == "" { + return "", ErrNoConfigFile + } + + m, err := config.FromFileToMap(l.Fs, filename) + if err != nil { + return "", l.wrapFileError(err, filename) + } + + if err = v.MergeConfigMap(m); err != nil { + return "", l.wrapFileError(err, filename) + } + + 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) newRealBaseFs(path string) afero.Fs { + return hugofs.NewBasePathRealFilenameFs(afero.NewBasePathFs(l.Fs, path).(*afero.BasePathFs)) + +} + +func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]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 { + return nil + } + + if fi.IsDir() { + dirnames = append(dirnames, path) + return nil + } + + name := helpers.Filename(filepath.Base(path)) + + item, err := metadecoders.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 := helpers.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) + + if err := v.MergeConfigMap(root); err != nil { + return l.wrapFileError(err, path) + } + + return nil + + }) + + if err != nil { + return nil, err + } + + } + return dirnames, nil } func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error { @@ -289,12 +453,11 @@ func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error { return nil } -func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) ([]string, error) { - themesDir := paths.AbsPathify(d.WorkingDir, v1.GetString("themesDir")) +func (l configLoader) loadThemeConfig(v1 *viper.Viper) ([]string, error) { + themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir")) themes := config.GetStringSlicePreserveString(v1, "theme") - // CollectThemes(fs afero.Fs, themesDir string, themes []strin - themeConfigs, err := paths.CollectThemes(d.Fs, themesDir, themes) + themeConfigs, err := paths.CollectThemes(l.Fs, themesDir, themes) if err != nil { return nil, err } @@ -309,7 +472,7 @@ func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) ([]string, error for _, tc := range themeConfigs { if tc.ConfigFilename != "" { configFilenames = append(configFilenames, tc.ConfigFilename) - if err := applyThemeConfig(v1, tc); err != nil { + if err := l.applyThemeConfig(v1, tc); err != nil { return nil, err } } @@ -319,18 +482,18 @@ func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) ([]string, error } -func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error { +func (l configLoader) applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error { const ( paramsKey = "params" languagesKey = "languages" - menuKey = "menu" + menuKey = "menus" ) v2 := theme.Cfg for _, key := range []string{paramsKey, "outputformats", "mediatypes"} { - mergeStringMapKeepLeft("", key, v1, v2) + l.mergeStringMapKeepLeft("", key, v1, v2) } themeLower := strings.ToLower(theme.Name) @@ -348,7 +511,7 @@ func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error { v1Langs := v1.GetStringMap(languagesKey) for k := range v1Langs { langParamsKey := languagesKey + "." + k + "." + paramsKey - mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2) + l.mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2) } v2Langs := v2.GetStringMap(languagesKey) for k := range v2Langs { @@ -378,7 +541,7 @@ func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error { } // Add menu definitions from theme not found in project - if v2.IsSet("menu") { + if v2.IsSet(menuKey) { v2menus := v2.GetStringMap(menuKey) for k, v := range v2menus { menuEntry := menuKey + "." + k @@ -392,7 +555,7 @@ func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error { } -func mergeStringMapKeepLeft(rootKey, key string, v1, v2 config.Provider) { +func (configLoader) mergeStringMapKeepLeft(rootKey, key string, v1, v2 config.Provider) { if !v2.IsSet(key) { return } @@ -440,6 +603,7 @@ func loadDefaultSettingsFor(v *viper.Viper) error { 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) diff --git a/hugolib/config_test.go b/hugolib/config_test.go index 1f9e7377c..885a07ee9 100644 --- a/hugolib/config_test.go +++ b/hugolib/config_test.go @@ -247,8 +247,8 @@ map[string]interface {}{ b.AssertObject(`map[string]interface {}{ "en": map[string]interface {}{ "languagename": "English", - "menu": map[string]interface {}{ - "theme": []interface {}{ + "menus": map[string]interface {}{ + "theme": []map[string]interface {}{ map[string]interface {}{ "name": "menu-lang-en-theme", }, @@ -265,8 +265,8 @@ map[string]interface {}{ }, "nb": map[string]interface {}{ "languagename": "Norsk", - "menu": map[string]interface {}{ - "theme": []interface {}{ + "menus": map[string]interface {}{ + "theme": []map[string]interface {}{ map[string]interface {}{ "name": "menu-lang-nb-theme", }, @@ -287,23 +287,23 @@ map[string]interface {}{ b.AssertObject(` map[string]interface {}{ - "main": []interface {}{ + "main": []map[string]interface {}{ map[string]interface {}{ "name": "menu-main-main", }, }, - "thememenu": []interface {}{ + "thememenu": []map[string]interface {}{ map[string]interface {}{ "name": "menu-theme", }, }, - "top": []interface {}{ + "top": []map[string]interface {}{ map[string]interface {}{ "name": "menu-top-main", }, }, } -`, got["menu"]) +`, got["menus"]) assert.Equal("https://example.com/", got["baseurl"]) diff --git a/hugolib/configdir_test.go b/hugolib/configdir_test.go new file mode 100644 index 000000000..80fcda61f --- /dev/null +++ b/hugolib/configdir_test.go @@ -0,0 +1,152 @@ +// Copyright 2018 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 hugolib + +import ( + "path/filepath" + "testing" + + "github.com/gohugoio/hugo/common/herrors" + + "github.com/gohugoio/hugo/htesting" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +func TestLoadConfigDir(t *testing.T) { + t.Parallel() + + assert := require.New(t) + + configContent := ` +baseURL = "https://example.org" +paginagePath = "pag_root" + +[languages.en] +weight = 0 +languageName = "English" + +[languages.no] +weight = 10 +languageName = "FOO" + +[params] +p1 = "p1_base" + +` + + mm := afero.NewMemMapFs() + + writeToFs(t, mm, "hugo.toml", configContent) + + fb := htesting.NewTestdataBuilder(mm, "config/_default", t) + + fb.Add("config.toml", `paginatePath = "pag_default"`) + + fb.Add("params.yaml", ` +p2: "p2params_default" +p3: "p3params_default" +p4: "p4params_default" +`) + fb.Add("menus.toml", ` +[[docs]] +name = "About Hugo" +weight = 1 +[[docs]] +name = "Home" +weight = 2 + `) + + fb.Add("menus.no.toml", ` + [[docs]] + name = "Om Hugo" + weight = 1 + `) + + fb.Add("params.no.toml", + ` +p3 = "p3params_no_default" +p4 = "p4params_no_default"`, + ) + fb.Add("languages.no.toml", `languageName = "Norsk_no_default"`) + + fb.Build() + + fb = fb.WithWorkingDir("config/production") + + fb.Add("config.toml", `paginatePath = "pag_production"`) + + fb.Add("params.no.toml", ` +p2 = "p2params_no_production" +p3 = "p3params_no_production" +`) + + fb.Build() + + fb = fb.WithWorkingDir("config/development") + + // This is set in all the config.toml variants above, but this will win. + fb.Add("config.toml", `paginatePath = "pag_development"`) + + fb.Add("params.no.toml", `p3 = "p3params_no_development"`) + fb.Add("params.toml", `p3 = "p3params_development"`) + + fb.Build() + + cfg, _, err := LoadConfig(ConfigSourceDescriptor{Fs: mm, Environment: "development", Filename: "hugo.toml", AbsConfigDir: "config"}) + assert.NoError(err) + + assert.Equal("pag_development", cfg.GetString("paginatePath")) // /config/development/config.toml + + assert.Equal(10, cfg.GetInt("languages.no.weight")) // /config.toml + assert.Equal("Norsk_no_default", cfg.GetString("languages.no.languageName")) // /config/_default/languages.no.toml + + assert.Equal("p1_base", cfg.GetString("params.p1")) + assert.Equal("p2params_default", cfg.GetString("params.p2")) // Is in both _default and production + assert.Equal("p3params_development", cfg.GetString("params.p3")) + assert.Equal("p3params_no_development", cfg.GetString("languages.no.params.p3")) + + assert.Equal(2, len(cfg.Get("menus.docs").(([]map[string]interface{})))) + noMenus := cfg.Get("languages.no.menus.docs") + assert.NotNil(noMenus) + assert.Equal(1, len(noMenus.(([]map[string]interface{})))) + +} + +func TestLoadConfigDirError(t *testing.T) { + t.Parallel() + + assert := require.New(t) + + configContent := ` +baseURL = "https://example.org" + +` + + mm := afero.NewMemMapFs() + + writeToFs(t, mm, "hugo.toml", configContent) + + fb := htesting.NewTestdataBuilder(mm, "config/development", t) + + fb.Add("config.toml", `invalid & syntax`).Build() + + _, _, err := LoadConfig(ConfigSourceDescriptor{Fs: mm, Environment: "development", Filename: "hugo.toml", AbsConfigDir: "config"}) + assert.Error(err) + + fe := herrors.UnwrapErrorWithFileContext(err) + assert.NotNil(fe) + assert.Equal(filepath.FromSlash("config/development/config.toml"), fe.Position().Filename) + +} diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 0bb3b4362..5e75135c0 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -21,6 +21,8 @@ import ( "strings" "sync" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/publisher" "github.com/gohugoio/hugo/common/herrors" @@ -361,14 +363,14 @@ func (h *HugoSites) resetLogs() { } } -func (h *HugoSites) createSitesFromConfig() error { +func (h *HugoSites) createSitesFromConfig(cfg config.Provider) error { oldLangs, _ := h.Cfg.Get("languagesSorted").(langs.Languages) if err := loadLanguageSettings(h.Cfg, oldLangs); err != nil { return err } - depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: h.Cfg} + depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: cfg} sites, err := createSitesFromConfig(depsCfg) @@ -412,9 +414,9 @@ func (h *HugoSites) toSiteInfos() []*SiteInfo { type BuildCfg struct { // Reset site state before build. Use to force full rebuilds. ResetState bool - // Re-creates the sites from configuration before a build. + // If set, we re-create the sites from the given configuration before a build. // This is needed if new languages are added. - CreateSitesFromConfig bool + NewConfig config.Provider // Skip rendering. Useful for testing. SkipRender bool // Use this to indicate what changed (for rebuilds). diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index 4c275f55b..ec5070fa8 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -144,8 +144,8 @@ func (h *HugoSites) init(config *BuildCfg) error { h.reset() } - if config.CreateSitesFromConfig { - if err := h.createSitesFromConfig(); err != nil { + if config.NewConfig != nil { + if err := h.createSitesFromConfig(config.NewConfig); err != nil { return err } } @@ -154,8 +154,8 @@ func (h *HugoSites) init(config *BuildCfg) error { } func (h *HugoSites) initRebuild(config *BuildCfg) error { - if config.CreateSitesFromConfig { - return errors.New("Rebuild does not support 'CreateSitesFromConfig'.") + if config.NewConfig != nil { + return errors.New("Rebuild does not support 'NewConfig'.") } if config.ResetState { diff --git a/hugolib/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go index f1e317f59..91ae8434d 100644 --- a/hugolib/hugo_sites_build_test.go +++ b/hugolib/hugo_sites_build_test.go @@ -11,14 +11,11 @@ import ( "path/filepath" "time" - "github.com/gohugoio/hugo/langs" - "github.com/fortytw2/leaktest" "github.com/fsnotify/fsnotify" "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/hugofs" "github.com/spf13/afero" - "github.com/spf13/viper" "github.com/stretchr/testify/require" ) @@ -661,9 +658,8 @@ title = "Svenska" sites := b.H - // Watching does not work with in-memory fs, so we trigger a reload manually - assert.NoError(sites.Cfg.(*langs.Language).Cfg.(*viper.Viper).ReadInConfig()) - err := b.H.Build(BuildCfg{CreateSitesFromConfig: true}) + assert.NoError(b.LoadConfig()) + err := b.H.Build(BuildCfg{NewConfig: b.Cfg}) if err != nil { t.Fatalf("Failed to rebuild sites: %s", err) @@ -723,10 +719,9 @@ func TestChangeDefaultLanguage(t *testing.T) { "DefaultContentLanguageInSubdir": false, }) - // Watching does not work with in-memory fs, so we trigger a reload manually - // This does not look pretty, so we should think of something else. - assert.NoError(b.H.Cfg.(*langs.Language).Cfg.(*viper.Viper).ReadInConfig()) - err := b.H.Build(BuildCfg{CreateSitesFromConfig: true}) + assert.NoError(b.LoadConfig()) + err := b.H.Build(BuildCfg{NewConfig: b.Cfg}) + if err != nil { t.Fatalf("Failed to rebuild sites: %s", err) } diff --git a/hugolib/page.go b/hugolib/page.go index 81880023a..15ed631c1 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -1647,7 +1647,12 @@ func (p *Page) Menus() PageMenus { p.pageMenusInit.Do(func() { p.pageMenus = PageMenus{} - if ms, ok := p.params["menu"]; ok { + ms, ok := p.params["menus"] + if !ok { + ms, ok = p.params["menu"] + } + + if ok { link := p.RelPermalink() me := MenuEntry{Page: p, Name: p.LinkTitle(), Weight: p.Weight, URL: link} diff --git a/hugolib/page_test.go b/hugolib/page_test.go index 177ed7fb1..b8273ce28 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -1436,7 +1436,7 @@ func TestIndexPageSimpleMethods(t *testing.T) { {func(n *Page) bool { return n.IsNode() }}, {func(n *Page) bool { return !n.IsPage() }}, {func(n *Page) bool { return n.Scratch() != nil }}, - {func(n *Page) bool { return n.Hugo().Version != "" }}, + {func(n *Page) bool { return n.Hugo().Version() != "" }}, } { n := s.newHomePage() diff --git a/hugolib/paths/themes.go b/hugolib/paths/themes.go index c6dae5e62..1ce8e7997 100644 --- a/hugolib/paths/themes.go +++ b/hugolib/paths/themes.go @@ -20,7 +20,6 @@ import ( "github.com/gohugoio/hugo/config" "github.com/spf13/afero" "github.com/spf13/cast" - "github.com/spf13/viper" ) type ThemeConfig struct { @@ -73,18 +72,11 @@ func (c *themesCollector) add(name, configFilename string) (ThemeConfig, error) var tc ThemeConfig if configFilename != "" { - v := viper.New() - v.SetFs(c.fs) - v.AutomaticEnv() - v.SetEnvPrefix("hugo") - v.SetConfigFile(configFilename) - - err := v.ReadInConfig() + var err error + cfg, err = config.FromFile(c.fs, configFilename) if err != nil { - return tc, err + return tc, nil |