diff options
Diffstat (limited to 'hugolib/config.go')
-rw-r--r-- | hugolib/config.go | 314 |
1 files changed, 150 insertions, 164 deletions
diff --git a/hugolib/config.go b/hugolib/config.go index 50e4ca6ec..b7ac46171 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -14,21 +14,24 @@ package hugolib import ( - "fmt" - "os" "path/filepath" "strings" + "github.com/gohugoio/hugo/common/loggers" + + "github.com/gohugoio/hugo/cache/filecache" + + "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/parser/metadecoders" "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/hugolib/paths" - "github.com/pkg/errors" - _errors "github.com/pkg/errors" - "github.com/gohugoio/hugo/langs" + "github.com/gohugoio/hugo/modules" + "github.com/pkg/errors" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config/privacy" @@ -67,7 +70,8 @@ func loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err error) { // ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.). type ConfigSourceDescriptor struct { - Fs afero.Fs + Fs afero.Fs + Logger *loggers.Logger // Path to the config file to use, e.g. /my/project/config.toml Filename string @@ -84,6 +88,9 @@ type ConfigSourceDescriptor struct { // production, development Environment string + + // Defaults to os.Environ if not set. + Environ []string } func (d ConfigSourceDescriptor) configFilenames() []string { @@ -111,51 +118,43 @@ var ErrNoConfigFile = errors.New("Unable to locate config file or config directo // 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} - v.AutomaticEnv() - v.SetEnvPrefix("hugo") - - var cerr error - for _, name := range d.configFilenames() { var filename string - if filename, cerr = l.loadConfig(name, v); cerr != nil && cerr != ErrNoConfigFile { - return nil, nil, cerr + filename, err := l.loadConfig(name, v) + if err == nil { + configFiles = append(configFiles, filename) + } else if err != ErrNoConfigFile { + return nil, nil, err } - configFiles = append(configFiles, filename) } if d.AbsConfigDir != "" { dirnames, err := l.loadConfigFromConfigDir(v) if err == nil { configFiles = append(configFiles, dirnames...) + } else if err != ErrNoConfigFile { + return nil, nil, err } - cerr = err } if err := loadDefaultSettingsFor(v); err != nil { return v, configFiles, err } - if cerr == nil { - themeConfigFiles, err := l.loadThemeConfig(v) - if err != nil { - return v, configFiles, err - } - - if len(themeConfigFiles) > 0 { - configFiles = append(configFiles, themeConfigFiles...) - } - } - // 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 { @@ -164,12 +163,75 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid } } + // Apply environment overrides + if len(d.Environ) > 0 { + // Extract all that start with the HUGO_ prefix + const hugoEnvPrefix = "HUGO_" + var hugoEnv []string + for _, v := range d.Environ { + key, val := config.SplitEnvVar(v) + if strings.HasPrefix(key, hugoEnvPrefix) { + hugoEnv = append(hugoEnv, strings.ToLower(strings.TrimPrefix(key, hugoEnvPrefix)), val) + } + } + + if len(hugoEnv) > 0 { + for i := 0; i < len(hugoEnv); i += 2 { + key, valStr := strings.ToLower(hugoEnv[i]), hugoEnv[i+1] + + existing, nestedKey, owner, err := maps.GetNestedParamFn(key, "_", v.Get) + if err != nil { + return v, configFiles, err + } + + if existing != nil { + val, err := metadecoders.Default.UnmarshalStringTo(valStr, existing) + if err != nil { + continue + } + + if owner != nil { + owner[nestedKey] = val + } else { + v.Set(key, val) + } + } else { + v.Set(key, valStr) + } + } + } + } + + modulesConfig, err := l.loadModulesConfig(v) + if err != nil { + return v, configFiles, err + } + + mods, modulesConfigFiles, err := l.collectModules(modulesConfig, v) + if err != nil { + return v, configFiles, err + } + if err := loadLanguageSettings(v, nil); err != nil { return v, configFiles, err } - return v, configFiles, cerr + // Apply default project mounts. + if err := modules.ApplyProjectConfigDefaults(v, mods[len(mods)-1]); err != nil { + return v, configFiles, err + } + + if len(modulesConfigFiles) > 0 { + configFiles = append(configFiles, modulesConfigFiles...) + } + return v, configFiles, nil + +} + +func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error { + _, err := langs.LoadLanguageSettings(cfg, oldLangs) + return err } type configLoader struct { @@ -334,145 +396,79 @@ func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]string, error) return dirnames, nil } -func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error { +func (l configLoader) loadModulesConfig(v1 *viper.Viper) (modules.Config, error) { - defaultLang := cfg.GetString("defaultContentLanguage") - - var languages map[string]interface{} - - languagesFromConfig := cfg.GetStringMap("languages") - disableLanguages := cfg.GetStringSlice("disableLanguages") - - if len(disableLanguages) == 0 { - languages = languagesFromConfig - } else { - languages = make(map[string]interface{}) - for k, v := range languagesFromConfig { - for _, disabled := range disableLanguages { - if disabled == defaultLang { - return fmt.Errorf("cannot disable default language %q", defaultLang) - } - - if strings.EqualFold(k, disabled) { - v.(map[string]interface{})["disabled"] = true - break - } - } - languages[k] = v - } - } - - var ( - languages2 langs.Languages - err error - ) - - if len(languages) == 0 { - languages2 = append(languages2, langs.NewDefaultLanguage(cfg)) - } else { - languages2, err = toSortedLanguages(cfg, languages) - if err != nil { - return _errors.Wrap(err, "Failed to parse multilingual config") - } - } - - if oldLangs != nil { - // When in multihost mode, the languages are mapped to a server, so - // some structural language changes will need a restart of the dev server. - // The validation below isn't complete, but should cover the most - // important cases. - var invalid bool - if languages2.IsMultihost() != oldLangs.IsMultihost() { - invalid = true - } else { - if languages2.IsMultihost() && len(languages2) != len(oldLangs) { - invalid = true - } - } - - if invalid { - return errors.New("language change needing a server restart detected") - } - - if languages2.IsMultihost() { - // We need to transfer any server baseURL to the new language - for i, ol := range oldLangs { - nl := languages2[i] - nl.Set("baseURL", ol.GetString("baseURL")) - } - } + modConfig, err := modules.DecodeConfig(v1) + if err != nil { + return modules.Config{}, err } - // The defaultContentLanguage is something the user has to decide, but it needs - // to match a language in the language definition list. - langExists := false - for _, lang := range languages2 { - if lang.Lang == defaultLang { - langExists = true - break - } - } + return modConfig, nil +} - if !langExists { - return fmt.Errorf("site config value %q for defaultContentLanguage does not match any language definition", defaultLang) +func (l configLoader) collectModules(modConfig modules.Config, v1 *viper.Viper) (modules.Modules, []string, error) { + workingDir := l.WorkingDir + if workingDir == "" { + workingDir = v1.GetString("workingDir") } - cfg.Set("languagesSorted", languages2) - cfg.Set("multilingual", len(languages2) > 1) + themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir")) - multihost := languages2.IsMultihost() - - if multihost { - cfg.Set("defaultContentLanguageInSubdir", true) - cfg.Set("multihost", true) - } - - if multihost { - // The baseURL may be provided at the language level. If that is true, - // then every language must have a baseURL. In this case we always render - // to a language sub folder, which is then stripped from all the Permalink URLs etc. - for _, l := range languages2 { - burl := l.GetLocal("baseURL") - if burl == nil { - return errors.New("baseURL must be set on all or none of the languages") - } - } + ignoreVendor := v1.GetBool("ignoreVendor") + filecacheConfigs, err := filecache.DecodeConfig(l.Fs, v1) + if err != nil { + return nil, nil, err } + v1.Set("filecacheConfigs", filecacheConfigs) - return nil -} + modulesClient := modules.NewClient(modules.ClientConfig{ + Fs: l.Fs, + Logger: l.Logger, + WorkingDir: workingDir, + ThemesDir: themesDir, + CacheDir: filecacheConfigs.CacheDirModules(), + ModuleConfig: modConfig, + IgnoreVendor: ignoreVendor, + }) -func (l configLoader) loadThemeConfig(v1 *viper.Viper) ([]string, error) { - themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir")) - themes := config.GetStringSlicePreserveString(v1, "theme") + v1.Set("modulesClient", modulesClient) - themeConfigs, err := paths.CollectThemes(l.Fs, themesDir, themes) + moduleConfig, err := modulesClient.Collect() if err != nil { - return nil, err + return nil, nil, err } - if len(themeConfigs) == 0 { - return nil, nil - } + // Avoid recreating these later. + v1.Set("allModules", moduleConfig.ActiveModules) - v1.Set("allThemes", themeConfigs) + if len(moduleConfig.ActiveModules) == 0 { + return nil, nil, nil + } var configFilenames []string - for _, tc := range themeConfigs { - if tc.ConfigFilename != "" { - configFilenames = append(configFilenames, tc.ConfigFilename) + for _, tc := range moduleConfig.ActiveModules { + if tc.ConfigFilename() != "" { + if tc.Watch() { + configFilenames = append(configFilenames, tc.ConfigFilename()) + } if err := l.applyThemeConfig(v1, tc); err != nil { - return nil, err + return nil, nil, err } } } - return configFilenames, nil + 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, nil } -func (l configLoader) applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error { +func (l configLoader) applyThemeConfig(v1 *viper.Viper, theme modules.Module) error { const ( paramsKey = "params" @@ -480,22 +476,12 @@ func (l configLoader) applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) menuKey = "menus" ) - v2 := theme.Cfg + v2 := theme.Cfg() for _, key := range []string{paramsKey, "outputformats", "mediatypes"} { l.mergeStringMapKeepLeft("", key, v1, v2) } - themeLower := strings.ToLower(theme.Name) - 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) @@ -508,12 +494,6 @@ func (l configLoader) applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) 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) { @@ -577,18 +557,23 @@ 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("metaDataFormat", "toml") - v.SetDefault("contentDir", "content") - v.SetDefault("layoutDir", "layouts") - v.SetDefault("assetDir", "assets") - v.SetDefault("staticDir", "static") v.SetDefault("resourceDir", "resources") - v.SetDefault("archetypeDir", "archetypes") v.SetDefault("publishDir", "public") - v.SetDefault("dataDir", "data") - v.SetDefault("i18nDir", "i18n") v.SetDefault("themesDir", "themes") v.SetDefault("buildDrafts", false) v.SetDefault("buildFuture", false) @@ -635,5 +620,6 @@ func loadDefaultSettingsFor(v *viper.Viper) error { v.SetDefault("disableFastRender", false) v.SetDefault("timeout", 10000) // 10 seconds v.SetDefault("enableInlineShortcodes", false) + return nil } |