summaryrefslogtreecommitdiffstats
path: root/hugolib/config.go
diff options
context:
space:
mode:
Diffstat (limited to 'hugolib/config.go')
-rw-r--r--hugolib/config.go204
1 files changed, 184 insertions, 20 deletions
diff --git a/hugolib/config.go b/hugolib/config.go
index e47e65435..6eca1a969 100644
--- a/hugolib/config.go
+++ b/hugolib/config.go
@@ -16,6 +16,7 @@ package hugolib
import (
"errors"
"fmt"
+ "path/filepath"
"io"
"strings"
@@ -28,64 +29,91 @@ import (
// ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.).
type ConfigSourceDescriptor struct {
- Fs afero.Fs
- Src string
- Name string
+ Fs afero.Fs
+
+ // Full path to the config file to use, i.e. /my/project/config.toml
+ Filename string
+
+ // The path to the directory to look for configuration. Is used if Filename is not
+ // set.
+ Path string
+
+ // The project's working dir. Is used to look for additional theme config.
+ WorkingDir string
}
func (d ConfigSourceDescriptor) configFilenames() []string {
- return strings.Split(d.Name, ",")
+ return strings.Split(d.Filename, ",")
}
// LoadConfigDefault is a convenience method to load the default "config.toml" config.
func LoadConfigDefault(fs afero.Fs) (*viper.Viper, error) {
- return LoadConfig(ConfigSourceDescriptor{Fs: fs, Name: "config.toml"})
+ v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs, Filename: "config.toml"})
+ return v, err
}
// LoadConfig loads Hugo configuration into a new Viper and then adds
// a set of defaults.
-func LoadConfig(d ConfigSourceDescriptor) (*viper.Viper, error) {
+func LoadConfig(d ConfigSourceDescriptor) (*viper.Viper, []string, error) {
+ var configFiles []string
+
fs := d.Fs
v := viper.New()
v.SetFs(fs)
- if d.Name == "" {
- d.Name = "config.toml"
- }
-
- if d.Src == "" {
- d.Src = "."
+ if d.Path == "" {
+ d.Path = "."
}
configFilenames := d.configFilenames()
v.AutomaticEnv()
v.SetEnvPrefix("hugo")
v.SetConfigFile(configFilenames[0])
- v.AddConfigPath(d.Src)
+ v.AddConfigPath(d.Path)
err := v.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigParseError); ok {
- return nil, err
+ return nil, configFiles, err
}
- return nil, fmt.Errorf("Unable to locate Config file. Perhaps you need to create a new site.\n Run `hugo help new` for details. (%s)\n", err)
+ return nil, configFiles, fmt.Errorf("Unable to locate Config file. Perhaps you need to create a new site.\n Run `hugo help new` for details. (%s)\n", err)
+ }
+
+ if cf := v.ConfigFileUsed(); cf != "" {
+ configFiles = append(configFiles, cf)
}
+
for _, configFile := range configFilenames[1:] {
var r io.Reader
var err error
if r, err = fs.Open(configFile); err != nil {
- return nil, fmt.Errorf("Unable to open Config file.\n (%s)\n", err)
+ return nil, configFiles, fmt.Errorf("Unable to open Config file.\n (%s)\n", err)
}
if err = v.MergeConfig(r); err != nil {
- return nil, fmt.Errorf("Unable to parse/merge Config file (%s).\n (%s)\n", configFile, err)
+ return nil, configFiles, fmt.Errorf("Unable to parse/merge Config file (%s).\n (%s)\n", configFile, err)
}
+ configFiles = append(configFiles, configFile)
}
if err := loadDefaultSettingsFor(v); err != nil {
- return v, err
+ return v, configFiles, err
}
- return v, nil
+ themeConfigFile, err := loadThemeConfig(d, v)
+ if err != nil {
+ return v, configFiles, err
+ }
+
+ if themeConfigFile != "" {
+ configFiles = append(configFiles, themeConfigFile)
+ }
+
+ if err := loadLanguageSettings(v, nil); err != nil {
+ return v, configFiles, err
+ }
+
+ return v, configFiles, nil
+
}
func loadLanguageSettings(cfg config.Provider, oldLangs helpers.Languages) error {
@@ -201,6 +229,142 @@ func loadLanguageSettings(cfg config.Provider, oldLangs helpers.Languages) error
return nil
}
+func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) (string, error) {
+
+ theme := v1.GetString("theme")
+ if theme == "" {
+ return "", nil
+ }
+
+ themesDir := helpers.AbsPathify(d.WorkingDir, v1.GetString("themesDir"))
+ configDir := filepath.Join(themesDir, theme)
+
+ var (
+ configPath string
+ exists bool
+ err error
+ )
+
+ // Viper supports more, but this is the sub-set supported by Hugo.
+ for _, configFormats := range []string{"toml", "yaml", "yml", "json"} {
+ configPath = filepath.Join(configDir, "config."+configFormats)
+ exists, err = helpers.Exists(configPath, d.Fs)
+ if err != nil {
+ return "", err
+ }
+ if exists {
+ break
+ }
+ }
+
+ if !exists {
+ // No theme config set.
+ return "", nil
+ }
+
+ v2 := viper.New()
+ v2.SetFs(d.Fs)
+ v2.AutomaticEnv()
+ v2.SetEnvPrefix("hugo")
+ v2.SetConfigFile(configPath)
+
+ err = v2.ReadInConfig()
+ if err != nil {
+ return "", err
+ }
+
+ const (
+ paramsKey = "params"
+ languagesKey = "languages"
+ menuKey = "menu"
+ )
+
+ for _, key := range []string{paramsKey, "outputformats", "mediatypes"} {
+ mergeStringMapKeepLeft("", key, v1, v2)
+ }
+
+ themeLower := strings.ToLower(theme)
+ themeParamsNamespace := paramsKey + "." + themeLower
+
+ // Set namespaced params
+ if v2.IsSet(paramsKey) && !v1.IsSet(themeParamsNamespace) {
+ // Set it in the default store to make sure it gets in the same or
+ // behind the others.
+ v1.SetDefault(themeParamsNamespace, v2.Get(paramsKey))
+ }
+
+ // 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
+ mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2)
+ }
+ v2Langs := v2.GetStringMap(languagesKey)
+ for k, _ := range v2Langs {
+ if k == "" {
+ continue
+ }
+ langParamsKey := languagesKey + "." + k + "." + paramsKey
+ langParamsThemeNamespace := langParamsKey + "." + themeLower
+ // Set namespaced params
+ if v2.IsSet(langParamsKey) && !v1.IsSet(langParamsThemeNamespace) {
+ v1.SetDefault(langParamsThemeNamespace, v2.Get(langParamsKey))
+ }
+
+ 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("menu") {
+ v2menus := v2.GetStringMap(menuKey)
+ for k, v := range v2menus {
+ menuEntry := menuKey + "." + k
+ if !v1.IsSet(menuEntry) {
+ v1.SetDefault(menuEntry, v)
+ }
+ }
+ }
+
+ return v2.ConfigFileUsed(), nil
+
+}
+
+func mergeStringMapKeepLeft(rootKey, key string, v1, v2 *viper.Viper) {
+ if !v2.IsSet(key) {
+ return
+ }
+
+ if !v1.IsSet(key) && !(rootKey != "" && rootKey != key && v1.IsSet(rootKey)) {
+ v1.Set(key, v2.Get(key))
+ return
+ }
+
+ m1 := v1.GetStringMap(key)
+ m2 := v2.GetStringMap(key)
+
+ for k, v := range m2 {
+ if _, found := m1[k]; !found {
+ if rootKey != "" && v1.IsSet(rootKey+"."+k) {
+ continue
+ }
+ m1[k] = v
+ }
+ }
+}
+
func loadDefaultSettingsFor(v *viper.Viper) error {
c, err := helpers.NewContentSpec(v)
@@ -281,5 +445,5 @@ lastmod = ["lastmod" ,":fileModTime", ":default"]
}
- return loadLanguageSettings(v, nil)
+ return nil
}