summaryrefslogtreecommitdiffstats
path: root/hugolib
diff options
context:
space:
mode:
Diffstat (limited to 'hugolib')
-rw-r--r--hugolib/config.go314
-rw-r--r--hugolib/config_test.go161
-rwxr-xr-xhugolib/data/hugo.toml1
-rw-r--r--hugolib/disableKinds_test.go89
-rw-r--r--hugolib/fileInfo.go76
-rw-r--r--hugolib/fileInfo_test.go1
-rw-r--r--hugolib/filesystems/basefs.go704
-rw-r--r--hugolib/filesystems/basefs_test.go187
-rw-r--r--hugolib/hugo_modules_test.go492
-rw-r--r--hugolib/hugo_sites.go198
-rw-r--r--hugolib/hugo_sites_build.go8
-rw-r--r--hugolib/hugo_sites_build_errors_test.go3
-rw-r--r--hugolib/hugo_sites_build_test.go54
-rw-r--r--hugolib/hugo_themes_test.go268
-rw-r--r--hugolib/language_content_dir_test.go11
-rw-r--r--hugolib/menu_test.go44
-rw-r--r--hugolib/multilingual.go56
-rw-r--r--hugolib/page.go18
-rw-r--r--hugolib/page__meta.go13
-rw-r--r--hugolib/page_permalink_test.go3
-rw-r--r--hugolib/page_test.go52
-rw-r--r--hugolib/pagebundler.go206
-rw-r--r--hugolib/pagebundler_capture.go773
-rw-r--r--hugolib/pagebundler_capture_test.go272
-rw-r--r--hugolib/pagebundler_handlers.go305
-rw-r--r--hugolib/pagebundler_test.go384
-rw-r--r--hugolib/pagecollections.go1
-rw-r--r--hugolib/pages_capture.go779
-rw-r--r--hugolib/pages_capture_test.go88
-rw-r--r--hugolib/paths/paths.go66
-rw-r--r--hugolib/paths/paths_test.go9
-rw-r--r--hugolib/paths/themes.go154
-rw-r--r--hugolib/resource_chain_test.go66
-rw-r--r--hugolib/shortcode_test.go70
-rw-r--r--hugolib/site.go104
-rw-r--r--hugolib/site_output_test.go47
-rw-r--r--hugolib/site_sections_test.go3
-rw-r--r--hugolib/site_stats_test.go23
-rw-r--r--hugolib/site_test.go5
-rw-r--r--hugolib/taxonomy_test.go54
-rw-r--r--hugolib/template_engines_test.go3
-rw-r--r--hugolib/template_test.go23
-rw-r--r--hugolib/testhelpers_test.go268
43 files changed, 3009 insertions, 3447 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
}
diff --git a/hugolib/config_test.go b/hugolib/config_test.go
index 885a07ee9..bd980235f 100644
--- a/hugolib/config_test.go
+++ b/hugolib/config_test.go
@@ -14,6 +14,9 @@
package hugolib
import (
+ "bytes"
+ "fmt"
+ "path/filepath"
"testing"
"github.com/spf13/afero"
@@ -40,10 +43,7 @@ func TestLoadConfig(t *testing.T) {
require.NoError(t, err)
assert.Equal("side", cfg.GetString("paginatePath"))
- // default
- assert.Equal("layouts", cfg.GetString("layoutDir"))
- // no themes
- assert.False(cfg.IsSet("allThemes"))
+
}
func TestLoadMultiConfig(t *testing.T) {
@@ -188,11 +188,6 @@ map[string]interface {}{
"p1": "p1 main",
"p2": "p2 main",
"p3": "p3 theme",
- "test-theme": map[string]interface {}{
- "p1": "p1 theme",
- "p2": "p2 theme",
- "p3": "p3 theme",
- },
"top": "top",
}`, got["params"])
@@ -257,10 +252,6 @@ map[string]interface {}{
"params": map[string]interface {}{
"pl1": "p1-en-main",
"pl2": "p2-en-theme",
- "test-theme": map[string]interface {}{
- "pl1": "p1-en-theme",
- "pl2": "p2-en-theme",
- },
},
},
"nb": map[string]interface {}{
@@ -275,11 +266,6 @@ map[string]interface {}{
"params": map[string]interface {}{
"pl1": "p1-nb-main",
"pl2": "p2-nb-theme",
- "test-theme": map[string]interface {}{
- "pl1": "p1-nb-theme",
- "pl2": "p2-nb-theme",
- "top": "top-nb-theme",
- },
},
},
}
@@ -397,3 +383,142 @@ privacyEnhanced = true
assert.True(b.H.Sites[0].Info.Config().Privacy.YouTube.PrivacyEnhanced)
}
+
+func TestLoadConfigModules(t *testing.T) {
+ t.Parallel()
+
+ assert := require.New(t)
+
+ // https://github.com/gohugoio/hugoThemes#themetoml
+
+ const (
+ // Before Hugo 0.56 each theme/component could have its own theme.toml
+ // with some settings, mostly used on the Hugo themes site.
+ // To preserve combability we read these files into the new "modules"
+ // section in config.toml.
+ o1t = `
+name = "Component o1"
+license = "MIT"
+min_version = 0.38
+`
+ // This is the component's config.toml, using the old theme syntax.
+ o1c = `
+theme = ["n2"]
+`
+
+ n1 = `
+title = "Component n1"
+
+[module]
+description = "Component n1 description"
+[module.hugoVersion]
+min = "0.40.0"
+max = "0.50.0"
+extended = true
+[[module.imports]]
+path="o1"
+[[module.imports]]
+path="n3"
+
+
+`
+
+ n2 = `
+title = "Component n2"
+`
+
+ n3 = `
+title = "Component n3"
+`
+
+ n4 = `
+title = "Component n4"
+`
+ )
+
+ b := newTestSitesBuilder(t)
+
+ writeThemeFiles := func(name, configTOML, themeTOML string) {
+ b.WithSourceFile(filepath.Join("themes", name, "data", "module.toml"), fmt.Sprintf("name=%q", name))
+ if configTOML != "" {
+ b.WithSourceFile(filepath.Join("themes", name, "config.toml"), configTOML)
+ }
+ if themeTOML != "" {
+ b.WithSourceFile(filepath.Join("themes", name, "theme.toml"), themeTOML)
+ }
+ }
+
+ writeThemeFiles("n1", n1, "")
+ writeThemeFiles("n2", n2, "")
+ writeThemeFiles("n3", n3, "")
+ writeThemeFiles("n4", n4, "")
+ writeThemeFiles("o1", o1c, o1t)
+
+ b.WithConfigFile("toml", `
+[module]
+[[module.imports]]
+path="n1"
+[[module.imports]]
+path="n4"
+
+`)
+
+ b.Build(BuildCfg{})
+
+ modulesClient := b.H.Paths.ModulesClient
+ var graphb bytes.Buffer
+ modulesClient.Graph(&graphb)
+
+ assert.Equal(`project n1
+n1 o1
+o1 n2
+n1 n3
+project n4
+`, graphb.String())
+
+}
+
+func TestLoadConfigWithOsEnvOverrides(t *testing.T) {
+
+ assert := require.New(t)
+
+ baseConfig := `
+
+environment = "production"
+enableGitInfo = true
+intSlice = [5,7,9]
+floatSlice = [3.14, 5.19]
+stringSlice = ["a", "b"]
+
+[imaging]
+anchor = "smart"
+quality = 75
+resamplefilter = "CatmullRom"
+`
+
+ b := newTestSitesBuilder(t).WithConfigFile("toml", baseConfig)
+
+ b.WithEnviron(
+ "HUGO_ENVIRONMENT", "test",
+ "HUGO_NEW", "new", // key not in config.toml
+ "HUGO_ENABLEGITINFO", "false",
+ "HUGO_IMAGING_ANCHOR", "top",
+ "HUGO_STRINGSLICE", `["c", "d"]`,
+ "HUGO_INTSLICE", `[5, 8, 9]`,
+ "HUGO_FLOATSLICE", `[5.32]`,
+ )
+
+ b.Build(BuildCfg{})
+
+ cfg := b.H.Cfg
+
+ assert.Equal("test", cfg.Get("environment"))
+ assert.Equal(false, cfg.GetBool("enablegitinfo"))
+ assert.Equal("new", cfg.Get("new"))
+ assert.Equal("top", cfg.Get("imaging.anchor"))
+ assert.Equal(int64(75), cfg.Get("imaging.quality"))
+ assert.Equal([]interface{}{"c", "d"}, cfg.Get("stringSlice"))
+ assert.Equal([]interface{}{5.32}, cfg.Get("floatSlice"))
+ assert.Equal([]interface{}{5, 8, 9}, cfg.Get("intSlice"))
+
+}
diff --git a/hugolib/data/hugo.toml b/hugolib/data/hugo.toml
new file mo