mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +02:00
Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code. Also, * Lower case the default output format names; this is in line with the custom ones (map keys) and how it's treated all the places. This avoids doing `stringds.EqualFold` everywhere. Closes #10896 Closes #10620
This commit is contained in:
813
config/allconfig/allconfig.go
Normal file
813
config/allconfig/allconfig.go
Normal file
@@ -0,0 +1,813 @@
|
||||
// Copyright 2023 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 allconfig contains the full configuration for Hugo.
|
||||
// <docsmeta>{ "name": "Configuration", "description": "This section holds all configiration options in Hugo." }</docsmeta>
|
||||
package allconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/privacy"
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
"github.com/gohugoio/hugo/config/services"
|
||||
"github.com/gohugoio/hugo/deploy"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/markup/markup_config"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/minifiers"
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
"github.com/gohugoio/hugo/navigation"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/related"
|
||||
"github.com/gohugoio/hugo/resources/images"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/page/pagemeta"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// InternalConfig is the internal configuration for Hugo, not read from any user provided config file.
|
||||
type InternalConfig struct {
|
||||
// Server mode?
|
||||
Running bool
|
||||
|
||||
Quiet bool
|
||||
Verbose bool
|
||||
Clock string
|
||||
Watch bool
|
||||
DisableLiveReload bool
|
||||
LiveReloadPort int
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// For internal use only.
|
||||
Internal InternalConfig `mapstructure:"-" json:"-"`
|
||||
// For internal use only.
|
||||
C ConfigCompiled `mapstructure:"-" json:"-"`
|
||||
|
||||
RootConfig
|
||||
|
||||
// Author information.
|
||||
Author map[string]any
|
||||
|
||||
// Social links.
|
||||
Social map[string]string
|
||||
|
||||
// The build configuration section contains build-related configuration options.
|
||||
// <docsmeta>{"identifiers": ["build"] }</docsmeta>
|
||||
Build config.BuildConfig `mapstructure:"-"`
|
||||
|
||||
// The caches configuration section contains cache-related configuration options.
|
||||
// <docsmeta>{"identifiers": ["caches"] }</docsmeta>
|
||||
Caches filecache.Configs `mapstructure:"-"`
|
||||
|
||||
// The markup configuration section contains markup-related configuration options.
|
||||
// <docsmeta>{"identifiers": ["markup"] }</docsmeta>
|
||||
Markup markup_config.Config `mapstructure:"-"`
|
||||
|
||||
// The mediatypes configuration section maps the MIME type (a string) to a configuration object for that type.
|
||||
// <docsmeta>{"identifiers": ["mediatypes"], "refs": ["types:media:type"] }</docsmeta>
|
||||
MediaTypes *config.ConfigNamespace[map[string]media.MediaTypeConfig, media.Types] `mapstructure:"-"`
|
||||
|
||||
Imaging *config.ConfigNamespace[images.ImagingConfig, images.ImagingConfigInternal] `mapstructure:"-"`
|
||||
|
||||
// The outputformats configuration sections maps a format name (a string) to a configuration object for that format.
|
||||
OutputFormats *config.ConfigNamespace[map[string]output.OutputFormatConfig, output.Formats] `mapstructure:"-"`
|
||||
|
||||
// The outputs configuration section maps a Page Kind (a string) to a slice of output formats.
|
||||
// This can be overridden in the front matter.
|
||||
Outputs map[string][]string `mapstructure:"-"`
|
||||
|
||||
// The cascade configuration section contains the top level front matter cascade configuration options,
|
||||
// a slice of page matcher and params to apply to those pages.
|
||||
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, map[page.PageMatcher]maps.Params] `mapstructure:"-"`
|
||||
|
||||
// Menu configuration.
|
||||
// <docsmeta>{"refs": ["config:languages:menus"] }</docsmeta>
|
||||
Menus *config.ConfigNamespace[map[string]navigation.MenuConfig, navigation.Menus] `mapstructure:"-"`
|
||||
|
||||
// The deployment configuration section contains for hugo deploy.
|
||||
Deployment deploy.DeployConfig `mapstructure:"-"`
|
||||
|
||||
// Module configuration.
|
||||
Module modules.Config `mapstructure:"-"`
|
||||
|
||||
// Front matter configuration.
|
||||
Frontmatter pagemeta.FrontmatterConfig `mapstructure:"-"`
|
||||
|
||||
// Minification configuration.
|
||||
Minify minifiers.MinifyConfig `mapstructure:"-"`
|
||||
|
||||
// Permalink configuration.
|
||||
Permalinks map[string]string `mapstructure:"-"`
|
||||
|
||||
// Taxonomy configuration.
|
||||
Taxonomies map[string]string `mapstructure:"-"`
|
||||
|
||||
// Sitemap configuration.
|
||||
Sitemap config.SitemapConfig `mapstructure:"-"`
|
||||
|
||||
// Related content configuration.
|
||||
Related related.Config `mapstructure:"-"`
|
||||
|
||||
// Server configuration.
|
||||
Server config.Server `mapstructure:"-"`
|
||||
|
||||
// Privacy configuration.
|
||||
Privacy privacy.Config `mapstructure:"-"`
|
||||
|
||||
// Security configuration.
|
||||
Security security.Config `mapstructure:"-"`
|
||||
|
||||
// Services configuration.
|
||||
Services services.Config `mapstructure:"-"`
|
||||
|
||||
// User provided parameters.
|
||||
// <docsmeta>{"refs": ["config:languages:params"] }</docsmeta>
|
||||
Params maps.Params `mapstructure:"-"`
|
||||
|
||||
// The languages configuration sections maps a language code (a string) to a configuration object for that language.
|
||||
Languages map[string]langs.LanguageConfig `mapstructure:"-"`
|
||||
|
||||
// UglyURLs configuration. Either a boolean or a sections map.
|
||||
UglyURLs any `mapstructure:"-"`
|
||||
}
|
||||
|
||||
type configCompiler interface {
|
||||
CompileConfig() error
|
||||
}
|
||||
|
||||
func (c Config) cloneForLang() *Config {
|
||||
x := c
|
||||
// Collapse all static dirs to one.
|
||||
x.StaticDir = x.staticDirs()
|
||||
// These will go away soon ...
|
||||
x.StaticDir0 = nil
|
||||
x.StaticDir1 = nil
|
||||
x.StaticDir2 = nil
|
||||
x.StaticDir3 = nil
|
||||
x.StaticDir4 = nil
|
||||
x.StaticDir5 = nil
|
||||
x.StaticDir6 = nil
|
||||
x.StaticDir7 = nil
|
||||
x.StaticDir8 = nil
|
||||
x.StaticDir9 = nil
|
||||
x.StaticDir10 = nil
|
||||
|
||||
return &x
|
||||
}
|
||||
|
||||
func (c *Config) CompileConfig() error {
|
||||
s := c.Timeout
|
||||
if _, err := strconv.Atoi(s); err == nil {
|
||||
// A number, assume seconds.
|
||||
s = s + "s"
|
||||
}
|
||||
timeout, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse timeout: %s", err)
|
||||
}
|
||||
disabledKinds := make(map[string]bool)
|
||||
for _, kind := range c.DisableKinds {
|
||||
disabledKinds[strings.ToLower(kind)] = true
|
||||
}
|
||||
kindOutputFormats := make(map[string]output.Formats)
|
||||
isRssDisabled := disabledKinds["rss"]
|
||||
outputFormats := c.OutputFormats.Config
|
||||
for kind, formats := range c.Outputs {
|
||||
if disabledKinds[kind] {
|
||||
continue
|
||||
}
|
||||
for _, format := range formats {
|
||||
if isRssDisabled && format == "rss" {
|
||||
// Legacy config.
|
||||
continue
|
||||
}
|
||||
f, found := outputFormats.GetByName(format)
|
||||
if !found {
|
||||
return fmt.Errorf("unknown output format %q for kind %q", format, kind)
|
||||
}
|
||||
kindOutputFormats[kind] = append(kindOutputFormats[kind], f)
|
||||
}
|
||||
}
|
||||
|
||||
disabledLangs := make(map[string]bool)
|
||||
for _, lang := range c.DisableLanguages {
|
||||
if lang == c.DefaultContentLanguage {
|
||||
return fmt.Errorf("cannot disable default content language %q", lang)
|
||||
}
|
||||
disabledLangs[lang] = true
|
||||
}
|
||||
|
||||
ignoredErrors := make(map[string]bool)
|
||||
for _, err := range c.IgnoreErrors {
|
||||
ignoredErrors[strings.ToLower(err)] = true
|
||||
}
|
||||
|
||||
baseURL, err := urls.NewBaseURLFromString(c.BaseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isUglyURL := func(section string) bool {
|
||||
switch v := c.UglyURLs.(type) {
|
||||
case bool:
|
||||
return v
|
||||
case map[string]bool:
|
||||
return v[section]
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
ignoreFile := func(s string) bool {
|
||||
return false
|
||||
}
|
||||
if len(c.IgnoreFiles) > 0 {
|
||||
regexps := make([]*regexp.Regexp, len(c.IgnoreFiles))
|
||||
for i, pattern := range c.IgnoreFiles {
|
||||
var err error
|
||||
regexps[i], err = regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile ignoreFiles pattern %q: %s", pattern, err)
|
||||
}
|
||||
}
|
||||
ignoreFile = func(s string) bool {
|
||||
for _, r := range regexps {
|
||||
if r.MatchString(s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var clock time.Time
|
||||
if c.Internal.Clock != "" {
|
||||
var err error
|
||||
clock, err = time.Parse(time.RFC3339, c.Internal.Clock)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse clock: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.C = ConfigCompiled{
|
||||
Timeout: timeout,
|
||||
BaseURL: baseURL,
|
||||
BaseURLLiveReload: baseURL,
|
||||
DisabledKinds: disabledKinds,
|
||||
DisabledLanguages: disabledLangs,
|
||||
IgnoredErrors: ignoredErrors,
|
||||
KindOutputFormats: kindOutputFormats,
|
||||
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
|
||||
IsUglyURLSection: isUglyURL,
|
||||
IgnoreFile: ignoreFile,
|
||||
MainSections: c.MainSections,
|
||||
Clock: clock,
|
||||
}
|
||||
|
||||
for _, s := range allDecoderSetups {
|
||||
if getCompiler := s.getCompiler; getCompiler != nil {
|
||||
if err := getCompiler(c).CompileConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Config) IsKindEnabled(kind string) bool {
|
||||
return !c.C.DisabledKinds[kind]
|
||||
}
|
||||
|
||||
func (c Config) IsLangDisabled(lang string) bool {
|
||||
return c.C.DisabledLanguages[lang]
|
||||
}
|
||||
|
||||
// ConfigCompiled holds values and functions that are derived from the config.
|
||||
type ConfigCompiled struct {
|
||||
Timeout time.Duration
|
||||
BaseURL urls.BaseURL
|
||||
BaseURLLiveReload urls.BaseURL
|
||||
KindOutputFormats map[string]output.Formats
|
||||
DisabledKinds map[string]bool
|
||||
DisabledLanguages map[string]bool
|
||||
IgnoredErrors map[string]bool
|
||||
CreateTitle func(s string) string
|
||||
IsUglyURLSection func(section string) bool
|
||||
IgnoreFile func(filename string) bool
|
||||
MainSections []string
|
||||
Clock time.Time
|
||||
}
|
||||
|
||||
// This may be set after the config is compiled.
|
||||
func (c *ConfigCompiled) SetMainSections(sections []string) {
|
||||
c.MainSections = sections
|
||||
}
|
||||
|
||||
// This is set after the config is compiled by the server command.
|
||||
func (c *ConfigCompiled) SetBaseURL(baseURL, baseURLLiveReload urls.BaseURL) {
|
||||
c.BaseURL = baseURL
|
||||
c.BaseURLLiveReload = baseURLLiveReload
|
||||
}
|
||||
|
||||
// RootConfig holds all the top-level configuration options in Hugo
|
||||
type RootConfig struct {
|
||||
|
||||
// The base URL of the site.
|
||||
// Note that the default value is empty, but Hugo requires a valid URL (e.g. "https://example.com/") to work properly.
|
||||
// <docsmeta>{"identifiers": ["URL"] }</docsmeta>
|
||||
BaseURL string
|
||||
|
||||
// Whether to build content marked as draft.X
|
||||
// <docsmeta>{"identifiers": ["draft"] }</docsmeta>
|
||||
BuildDrafts bool
|
||||
|
||||
// Whether to build content with expiryDate in the past.
|
||||
// <docsmeta>{"identifiers": ["expiryDate"] }</docsmeta>
|
||||
BuildExpired bool
|
||||
|
||||
// Whether to build content with publishDate in the future.
|
||||
// <docsmeta>{"identifiers": ["publishDate"] }</docsmeta>
|
||||
BuildFuture bool
|
||||
|
||||
// Copyright information.
|
||||
Copyright string
|
||||
|
||||
// The language to apply to content without any Clolanguage indicator.
|
||||
DefaultContentLanguage string
|
||||
|
||||
// By defefault, we put the default content language in the root and the others below their language ID, e.g. /no/.
|
||||
// Set this to true to put all languages below their language ID.
|
||||
DefaultContentLanguageInSubdir bool
|
||||
|
||||
// Disable creation of alias redirect pages.
|
||||
DisableAliases bool
|
||||
|
||||
// Disable lower casing of path segments.
|
||||
DisablePathToLower bool
|
||||
|
||||
// Disable page kinds from build.
|
||||
DisableKinds []string
|
||||
|
||||
// A list of languages to disable.
|
||||
DisableLanguages []string
|
||||
|
||||
// Disable the injection of the Hugo generator tag on the home page.
|
||||
DisableHugoGeneratorInject bool
|
||||
|
||||
// Enable replacement in Pages' Content of Emoji shortcodes with their equivalent Unicode characters.
|
||||
// <docsmeta>{"identifiers": ["Content", "Unicode"] }</docsmeta>
|
||||
EnableEmoji bool
|
||||
|
||||
// THe main section(s) of the site.
|
||||
// If not set, Hugo will try to guess this from the content.
|
||||
MainSections []string
|
||||
|
||||
// Enable robots.txt generation.
|
||||
EnableRobotsTXT bool
|
||||
|
||||
// When enabled, Hugo will apply Git version information to each Page if possible, which
|
||||
// can be used to keep lastUpdated in synch and to print version information.
|
||||
// <docsmeta>{"identifiers": ["Page"] }</docsmeta>
|
||||
EnableGitInfo bool
|
||||
|
||||
// Enable to track, calculate and print metrics.
|
||||
TemplateMetrics bool
|
||||
|
||||
// Enable to track, print and calculate metric hints.
|
||||
TemplateMetricsHints bool
|
||||
|
||||
// Enable to disable the build lock file.
|
||||
NoBuildLock bool
|
||||
|
||||
// A list of error IDs to ignore.
|
||||
IgnoreErrors []string
|
||||
|
||||
// A list of regexps that match paths to ignore.
|
||||
// Deprecated: Use the settings on module imports.
|
||||
IgnoreFiles []string
|
||||
|
||||
// Ignore cache.
|
||||
IgnoreCache bool
|
||||
|
||||
// Enable to print greppable placeholders (on the form "[i18n] TRANSLATIONID") for missing translation strings.
|
||||
EnableMissingTranslationPlaceholders bool
|
||||
|
||||
// Enable to print warnings for missing translation strings.
|
||||
LogI18nWarnings bool
|
||||
|
||||
// ENable to print warnings for multiple files published to the same destination.
|
||||
LogPathWarnings bool
|
||||
|
||||
// The configured environment. Default is "development" for server and "production" for build.
|
||||
Environment string
|
||||
|
||||
// The default language code.
|
||||
LanguageCode string
|
||||
|
||||
// Enable if the site content has CJK language (Chinese, Japanese, or Korean). This affects how Hugo counts words.
|
||||
HasCJKLanguage bool
|
||||
|
||||
// The default number of pages per page when paginating.
|
||||
Paginate int
|
||||
|
||||
// The path to use when creating pagination URLs, e.g. "page" in /page/2/.
|
||||
PaginatePath string
|
||||
|
||||
// Whether to pluralize default list titles.
|
||||
// Note that this currently only works for English, but you can provide your own title in the content file's front matter.
|
||||
PluralizeListTitles bool
|
||||
|
||||
// Make all relative URLs absolute using the baseURL.
|
||||
// <docsmeta>{"identifiers": ["baseURL"] }</docsmeta>
|
||||
CanonifyURLs bool
|
||||
|
||||
// Enable this to make all relative URLs relative to content root. Note that this does not affect absolute URLs.
|
||||
RelativeURLs bool
|
||||
|
||||
// Removes non-spacing marks from composite characters in content paths.
|
||||
RemovePathAccents bool
|
||||
|
||||
// Whether to track and print unused templates during the build.
|
||||
PrintUnusedTemplates bool
|
||||
|
||||
// URL to be used as a placeholder when a page reference cannot be found in ref or relref. Is used as-is.
|
||||
RefLinksNotFoundURL string
|
||||
|
||||
// When using ref or relref to resolve page links and a link cannot be resolved, it will be logged with this log level.
|
||||
// Valid values are ERROR (default) or WARNING. Any ERROR will fail the build (exit -1).
|
||||
RefLinksErrorLevel string
|
||||
|
||||
// This will create a menu with all the sections as menu items and all the sections’ pages as “shadow-members”.
|
||||
SectionPagesMenu string
|
||||
|
||||
// The length of text in words to show in a .Summary.
|
||||
SummaryLength int
|
||||
|
||||
// The site title.
|
||||
Title string
|
||||
|
||||
// The theme(s) to use.
|
||||
// See Modules for more a more flexible way to load themes.
|
||||
Theme []string
|
||||
|
||||
// Timeout for generating page contents, specified as a duration or in milliseconds.
|
||||
Timeout string
|
||||
|
||||
// The time zone (or location), e.g. Europe/Oslo, used to parse front matter dates without such information and in the time function.
|
||||
TimeZone string
|
||||
|
||||
// Set titleCaseStyle to specify the title style used by the title template function and the automatic section titles in Hugo.
|
||||
// It defaults to AP Stylebook for title casing, but you can also set it to Chicago or Go (every word starts with a capital letter).
|
||||
TitleCaseStyle string
|
||||
|
||||
// The editor used for opening up new content.
|
||||
NewContentEditor string
|
||||
|
||||
// Don't sync modification time of files for the static mounts.
|
||||
NoTimes bool
|
||||
|
||||
// Don't sync modification time of files for the static mounts.
|
||||
NoChmod bool
|
||||
|
||||
// Clean the destination folder before a new build.
|
||||
// This currently only handles static files.
|
||||
CleanDestinationDir bool
|
||||
|
||||
// A Glob pattern of module paths to ignore in the _vendor folder.
|
||||
IgnoreVendorPaths string
|
||||
|
||||
config.CommonDirs `mapstructure:",squash"`
|
||||
|
||||
// The odd constructs below are kept for backwards compatibility.
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir []string
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir0 []string
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir1 []string
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir2 []string
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir3 []string
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir4 []string
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir5 []string
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir6 []string
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir7 []string
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir8 []string
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir9 []string
|
||||
// Deprecated: Use module mount config instead.
|
||||
StaticDir10 []string
|
||||
}
|
||||
|
||||
func (c RootConfig) staticDirs() []string {
|
||||
var dirs []string
|
||||
dirs = append(dirs, c.StaticDir...)
|
||||
dirs = append(dirs, c.StaticDir0...)
|
||||
dirs = append(dirs, c.StaticDir1...)
|
||||
dirs = append(dirs, c.StaticDir2...)
|
||||
dirs = append(dirs, c.StaticDir3...)
|
||||
dirs = append(dirs, c.StaticDir4...)
|
||||
dirs = append(dirs, c.StaticDir5...)
|
||||
dirs = append(dirs, c.StaticDir6...)
|
||||
dirs = append(dirs, c.StaticDir7...)
|
||||
dirs = append(dirs, c.StaticDir8...)
|
||||
dirs = append(dirs, c.StaticDir9...)
|
||||
dirs = append(dirs, c.StaticDir10...)
|
||||
return helpers.UniqueStringsReuse(dirs)
|
||||
}
|
||||
|
||||
type Configs struct {
|
||||
Base *Config
|
||||
LoadingInfo config.LoadConfigResult
|
||||
LanguageConfigMap map[string]*Config
|
||||
LanguageConfigSlice []*Config
|
||||
|
||||
IsMultihost bool
|
||||
Languages langs.Languages
|
||||
LanguagesDefaultFirst langs.Languages
|
||||
|
||||
Modules modules.Modules
|
||||
ModulesClient *modules.Client
|
||||
|
||||
configLangs []config.AllProvider
|
||||
}
|
||||
|
||||
func (c *Configs) IsZero() bool {
|
||||
// A config always has at least one language.
|
||||
return c == nil || len(c.Languages) == 0
|
||||
}
|
||||
|
||||
func (c *Configs) Init() error {
|
||||
c.configLangs = make([]config.AllProvider, len(c.Languages))
|
||||
for i, l := range c.LanguagesDefaultFirst {
|
||||
c.configLangs[i] = ConfigLanguage{
|
||||
m: c,
|
||||
config: c.LanguageConfigMap[l.Lang],
|
||||
baseConfig: c.LoadingInfo.BaseConfig,
|
||||
language: l,
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Modules) == 0 {
|
||||
return errors.New("no modules loaded (ned at least the main module)")
|
||||
}
|
||||
|
||||
// Apply default project mounts.
|
||||
if err := modules.ApplyProjectConfigDefaults(c.Modules[0], c.configLangs...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Configs) ConfigLangs() []config.AllProvider {
|
||||
return c.configLangs
|
||||
}
|
||||
|
||||
func (c Configs) GetFirstLanguageConfig() config.AllProvider {
|
||||
return c.configLangs[0]
|
||||
}
|
||||
|
||||
func (c Configs) GetByLang(lang string) config.AllProvider {
|
||||
for _, l := range c.configLangs {
|
||||
if l.Language().Lang == lang {
|
||||
return l
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FromLoadConfigResult creates a new Config from res.
|
||||
func FromLoadConfigResult(fs afero.Fs, res config.LoadConfigResult) (*Configs, error) {
|
||||
if !res.Cfg.IsSet("languages") {
|
||||
// We need at least one
|
||||
lang := res.Cfg.GetString("defaultContentLanguage")
|
||||
res.Cfg.Set("languages", maps.Params{lang: maps.Params{}})
|
||||
}
|
||||
bcfg := res.BaseConfig
|
||||
cfg := res.Cfg
|
||||
|
||||
all := &Config{}
|
||||
err := decodeConfigFromParams(fs, bcfg, cfg, all, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
langConfigMap := make(map[string]*Config)
|
||||
var langConfigs []*Config
|
||||
|
||||
languagesConfig := cfg.GetStringMap("languages")
|
||||
var isMultiHost bool
|
||||
|
||||
if err := all.CompileConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range languagesConfig {
|
||||
mergedConfig := config.New()
|
||||
var differentRootKeys []string
|
||||
switch x := v.(type) {
|
||||
case maps.Params:
|
||||
for kk, vv := range x {
|
||||
if kk == "baseurl" {
|
||||
// baseURL configure don the language level is a multihost setup.
|
||||
isMultiHost = true
|
||||
}
|
||||
mergedConfig.Set(kk, vv)
|
||||
if cfg.IsSet(kk) {
|
||||
rootv := cfg.Get(kk)
|
||||
// This overrides a root key and potentially needs a merge.
|
||||
if !reflect.DeepEqual(rootv, vv) {
|
||||
switch vvv := vv.(type) {
|
||||
case maps.Params:
|
||||
differentRootKeys = append(differentRootKeys, kk)
|
||||
|
||||
// Use the language value as base.
|
||||
mergedConfigEntry := xmaps.Clone(vvv)
|
||||
// Merge in the root value.
|
||||
maps.MergeParams(mergedConfigEntry, rootv.(maps.Params))
|
||||
|
||||
mergedConfig.Set(kk, mergedConfigEntry)
|
||||
default:
|
||||
// Apply new values to the root.
|
||||
differentRootKeys = append(differentRootKeys, "")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Apply new values to the root.
|
||||
differentRootKeys = append(differentRootKeys, "")
|
||||
}
|
||||
}
|
||||
differentRootKeys = helpers.UniqueStringsSorted(differentRootKeys)
|
||||
|
||||
if len(differentRootKeys) == 0 {
|
||||
langConfigMap[k] = all
|
||||
continue
|
||||
}
|
||||
|
||||
// Create a copy of the complete config and replace the root keys with the language specific ones.
|
||||
clone := all.cloneForLang()
|
||||
if err := decodeConfigFromParams(fs, bcfg, mergedConfig, clone, differentRootKeys); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode config for language %q: %w", k, err)
|
||||
}
|
||||
if err := clone.CompileConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
langConfigMap[k] = clone
|
||||
case maps.ParamsMergeStrategy:
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type in languages config: %T", v))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var languages langs.Languages
|
||||
defaultContentLanguage := all.DefaultContentLanguage
|
||||
for k, v := range langConfigMap {
|
||||
languageConf := v.Languages[k]
|
||||
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
languages = append(languages, language)
|
||||
}
|
||||
|
||||
// Sort the sites by language weight (if set) or lang.
|
||||
sort.Slice(languages, func(i, j int) bool {
|
||||
li := languages[i]
|
||||
lj := languages[j]
|
||||
if li.Weight != lj.Weight {
|
||||
return li.Weight < lj.Weight
|
||||
}
|
||||
return li.Lang < lj.Lang
|
||||
})
|
||||
|
||||
for _, l := range languages {
|
||||
langConfigs = append(langConfigs, langConfigMap[l.Lang])
|
||||
}
|
||||
|
||||
var languagesDefaultFirst langs.Languages
|
||||
for _, l := range languages {
|
||||
if l.Lang == defaultContentLanguage {
|
||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||
}
|
||||
}
|
||||
for _, l := range languages {
|
||||
if l.Lang != defaultContentLanguage {
|
||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||
}
|
||||
}
|
||||
|
||||
bcfg.PublishDir = all.PublishDir
|
||||
res.BaseConfig = bcfg
|
||||
|
||||
cm := &Configs{
|
||||
Base: all,
|
||||
LanguageConfigMap: langConfigMap,
|
||||
LanguageConfigSlice: langConfigs,
|
||||
LoadingInfo: res,
|
||||
IsMultihost: isMultiHost,
|
||||
Languages: languages,
|
||||
LanguagesDefaultFirst: languagesDefaultFirst,
|
||||
}
|
||||
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
func decodeConfigFromParams(fs afero.Fs, bcfg config.BaseConfig, p config.Provider, target *Config, keys []string) error {
|
||||
|
||||
var decoderSetups []decodeWeight
|
||||
|
||||
if len(keys) == 0 {
|
||||
for _, v := range allDecoderSetups {
|
||||
decoderSetups = append(decoderSetups, v)
|
||||
}
|
||||
} else {
|
||||
for _, key := range keys {
|
||||
if v, found := allDecoderSetups[key]; found {
|
||||
decoderSetups = append(decoderSetups, v)
|
||||
} else {
|
||||
return fmt.Errorf("unknown config key %q", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort them to get the dependency order right.
|
||||
sort.Slice(decoderSetups, func(i, j int) bool {
|
||||
ki, kj := decoderSetups[i], decoderSetups[j]
|
||||
if ki.weight == kj.weight {
|
||||
return ki.key < kj.key
|
||||
}
|
||||
return ki.weight < kj.weight
|
||||
})
|
||||
|
||||
for _, v := range decoderSetups {
|
||||
p := decodeConfig{p: p, c: target, fs: fs, bcfg: bcfg}
|
||||
if err := v.decode(v, p); err != nil {
|
||||
return fmt.Errorf("failed to decode %q: %w", v.key, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createDefaultOutputFormats(allFormats output.Formats) map[string][]string {
|
||||
if len(allFormats) == 0 {
|
||||
panic("no output formats")
|
||||
}
|
||||
rssOut, rssFound := allFormats.GetByName(output.RSSFormat.Name)
|
||||
htmlOut, _ := allFormats.GetByName(output.HTMLFormat.Name)
|
||||
|
||||
defaultListTypes := []string{htmlOut.Name}
|
||||
if rssFound {
|
||||
defaultListTypes = append(defaultListTypes, rssOut.Name)
|
||||
}
|
||||
|
||||
m := map[string][]string{
|
||||
page.KindPage: {htmlOut.Name},
|
||||
page.KindHome: defaultListTypes,
|
||||
page.KindSection: defaultListTypes,
|
||||
page.KindTerm: defaultListTypes,
|
||||
page.KindTaxonomy: defaultListTypes,
|
||||
}
|
||||
|
||||
// May be disabled
|
||||
if rssFound {
|
||||
m["rss"] = []string{rssOut.Name}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
325
config/allconfig/alldecoders.go
Normal file
325
config/allconfig/alldecoders.go
Normal file
@@ -0,0 +1,325 @@
|
||||
// Copyright 2023 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 allconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/privacy"
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
"github.com/gohugoio/hugo/config/services"
|
||||
"github.com/gohugoio/hugo/deploy"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/markup/markup_config"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/minifiers"
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
"github.com/gohugoio/hugo/navigation"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/related"
|
||||
"github.com/gohugoio/hugo/resources/images"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/page/pagemeta"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type decodeConfig struct {
|
||||
p config.Provider
|
||||
c *Config
|
||||
fs afero.Fs
|
||||
bcfg config.BaseConfig
|
||||
}
|
||||
|
||||
type decodeWeight struct {
|
||||
key string
|
||||
decode func(decodeWeight, decodeConfig) error
|
||||
getCompiler func(c *Config) configCompiler
|
||||
weight int
|
||||
}
|
||||
|
||||
var allDecoderSetups = map[string]decodeWeight{
|
||||
"": {
|
||||
key: "",
|
||||
weight: -100, // Always first.
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
return mapstructure.WeakDecode(p.p.Get(""), &p.c.RootConfig)
|
||||
},
|
||||
},
|
||||
"imaging": {
|
||||
key: "imaging",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Imaging, err = images.DecodeConfig(p.p.GetStringMap(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"caches": {
|
||||
key: "caches",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Caches, err = filecache.DecodeConfig(p.fs, p.bcfg, p.p.GetStringMap(d.key))
|
||||
if p.c.IgnoreCache {
|
||||
// Set MaxAge in all caches to 0.
|
||||
for k, cache := range p.c.Caches {
|
||||
cache.MaxAge = 0
|
||||
p.c.Caches[k] = cache
|
||||
}
|
||||
}
|
||||
return err
|
||||
},
|
||||
},
|
||||
"build": {
|
||||
key: "build",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Build = config.DecodeBuildConfig(p.p)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"frontmatter": {
|
||||
key: "frontmatter",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Frontmatter, err = pagemeta.DecodeFrontMatterConfig(p.p)
|
||||
return err
|
||||
},
|
||||
},
|
||||
"markup": {
|
||||
key: "markup",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Markup, err = markup_config.Decode(p.p)
|
||||
return err
|
||||
},
|
||||
},
|
||||
"server": {
|
||||
key: "server",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Server, err = config.DecodeServer(p.p)
|
||||
return err
|
||||
},
|
||||
getCompiler: func(c *Config) configCompiler {
|
||||
return &c.Server
|
||||
},
|
||||
},
|
||||
"minify": {
|
||||
key: "minify",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Minify, err = minifiers.DecodeConfig(p.p.Get(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"mediaTypes": {
|
||||
key: "mediaTypes",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.MediaTypes, err = media.DecodeTypes(p.p.GetStringMap(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"outputs": {
|
||||
key: "outputs",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
defaults := createDefaultOutputFormats(p.c.OutputFormats.Config)
|
||||
m := p.p.GetStringMap("outputs")
|
||||
p.c.Outputs = make(map[string][]string)
|
||||
for k, v := range m {
|
||||
s := types.ToStringSlicePreserveString(v)
|
||||
for i, v := range s {
|
||||
s[i] = strings.ToLower(v)
|
||||
}
|
||||
p.c.Outputs[k] = s
|
||||
}
|
||||
// Apply defaults.
|
||||
for k, v := range defaults {
|
||||
if _, found := p.c.Outputs[k]; !found {
|
||||
p.c.Outputs[k] = v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"outputFormats": {
|
||||
key: "outputFormats",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.OutputFormats, err = output.DecodeConfig(p.c.MediaTypes.Config, p.p.Get(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"params": {
|
||||
key: "params",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Params = maps.CleanConfigStringMap(p.p.GetStringMap("params"))
|
||||
if p.c.Params == nil {
|
||||
p.c.Params = make(map[string]any)
|
||||
}
|
||||
|
||||
// Before Hugo 0.112.0 this was configured via site Params.
|
||||
if mainSections, found := p.c.Params["mainsections"]; found {
|
||||
p.c.MainSections = types.ToStringSlicePreserveString(mainSections)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"module": {
|
||||
key: "module",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Module, err = modules.DecodeConfig(p.p)
|
||||
return err
|
||||
},
|
||||
},
|
||||
"permalinks": {
|
||||
key: "permalinks",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Permalinks = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"sitemap": {
|
||||
key: "sitemap",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Sitemap, err = config.DecodeSitemap(config.SitemapConfig{Priority: -1, Filename: "sitemap.xml"}, p.p.GetStringMap(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"taxonomies": {
|
||||
key: "taxonomies",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"related": {
|
||||
key: "related",
|
||||
weight: 100, // This needs to be decoded after taxonomies.
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
if p.p.IsSet(d.key) {
|
||||
var err error
|
||||
p.c.Related, err = related.DecodeConfig(p.p.GetParams(d.key))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode related config: %w", err)
|
||||
}
|
||||
} else {
|
||||
p.c.Related = related.DefaultConfig
|
||||
if _, found := p.c.Taxonomies["tag"]; found {
|
||||
p.c.Related.Add(related.IndexConfig{Name: "tags", Weight: 80})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"languages": {
|
||||
key: "languages",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Languages, err = langs.DecodeConfig(p.p.GetStringMap(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"cascade": {
|
||||
key: "cascade",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Cascade, err = page.DecodeCascadeConfig(p.p.Get(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"menus": {
|
||||
key: "menus",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Menus, err = navigation.DecodeConfig(p.p.Get(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"privacy": {
|
||||
key: "privacy",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Privacy, err = privacy.DecodeConfig(p.p)
|
||||
return err
|
||||
},
|
||||
},
|
||||
"security": {
|
||||
key: "security",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Security, err = security.DecodeConfig(p.p)
|
||||
return err
|
||||
},
|
||||
},
|
||||
"services": {
|
||||
key: "services",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Services, err = services.DecodeConfig(p.p)
|
||||
return err
|
||||
},
|
||||
},
|
||||
"deployment": {
|
||||
key: "deployment",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Deployment, err = deploy.DecodeConfig(p.p)
|
||||
return err
|
||||
},
|
||||
},
|
||||
"author": {
|
||||
key: "author",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Author = p.p.GetStringMap(d.key)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"social": {
|
||||
key: "social",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Social = p.p.GetStringMapString(d.key)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"uglyurls": {
|
||||
key: "uglyurls",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
v := p.p.Get(d.key)
|
||||
switch vv := v.(type) {
|
||||
case bool:
|
||||
p.c.UglyURLs = vv
|
||||
case string:
|
||||
p.c.UglyURLs = vv == "true"
|
||||
default:
|
||||
p.c.UglyURLs = cast.ToStringMapBool(v)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"internal": {
|
||||
key: "internal",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
return mapstructure.WeakDecode(p.p.GetStringMap(d.key), &p.c.Internal)
|
||||
},
|
||||
},
|
||||
}
|
216
config/allconfig/configlanguage.go
Normal file
216
config/allconfig/configlanguage.go
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright 2023 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 allconfig
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
)
|
||||
|
||||
type ConfigLanguage struct {
|
||||
config *Config
|
||||
baseConfig config.BaseConfig
|
||||
|
||||
m *Configs
|
||||
language *langs.Language
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) Language() *langs.Language {
|
||||
return c.language
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) Languages() langs.Languages {
|
||||
return c.m.Languages
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) LanguagesDefaultFirst() langs.Languages {
|
||||
return c.m.LanguagesDefaultFirst
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) BaseURL() urls.BaseURL {
|
||||
return c.config.C.BaseURL
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) BaseURLLiveReload() urls.BaseURL {
|
||||
return c.config.C.BaseURLLiveReload
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) Environment() string {
|
||||
return c.config.Environment
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) IsMultihost() bool {
|
||||
return c.m.IsMultihost
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) IsMultiLingual() bool {
|
||||
return len(c.m.Languages) > 1
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) TemplateMetrics() bool {
|
||||
return c.config.TemplateMetrics
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) TemplateMetricsHints() bool {
|
||||
return c.config.TemplateMetricsHints
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) IsLangDisabled(lang string) bool {
|
||||
return c.config.C.DisabledLanguages[lang]
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) IgnoredErrors() map[string]bool {
|
||||
return c.config.C.IgnoredErrors
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) NoBuildLock() bool {
|
||||
return c.config.NoBuildLock
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) NewContentEditor() string {
|
||||
return c.config.NewContentEditor
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) Timeout() time.Duration {
|
||||
return c.config.C.Timeout
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) BaseConfig() config.BaseConfig {
|
||||
return c.baseConfig
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) Dirs() config.CommonDirs {
|
||||
return c.config.CommonDirs
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) DirsBase() config.CommonDirs {
|
||||
return c.m.Base.CommonDirs
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) Quiet() bool {
|
||||
return c.m.Base.Internal.Quiet
|
||||
}
|
||||
|
||||
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
|
||||
func (c ConfigLanguage) GetConfigSection(s string) any {
|
||||
switch s {
|
||||
case "security":
|
||||
return c.config.Security
|
||||
case "build":
|
||||
return c.config.Build
|
||||
case "frontmatter":
|
||||
return c.config.Frontmatter
|
||||
case "caches":
|
||||
return c.config.Caches
|
||||
case "markup":
|
||||
return c.config.Markup
|
||||
case "mediaTypes":
|
||||
return c.config.MediaTypes.Config
|
||||
case "outputFormats":
|
||||
return c.config.OutputFormats.Config
|
||||
case "permalinks":
|
||||
return c.config.Permalinks
|
||||
case "minify":
|
||||
return c.config.Minify
|
||||
case "activeModules":
|
||||
return c.m.Modules
|
||||
case "deployment":
|
||||
return c.config.Deployment
|
||||
default:
|
||||
panic("not implemented: " + s)
|
||||
}
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) GetConfig() any {
|
||||
return c.config
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) CanonifyURLs() bool {
|
||||
return c.config.CanonifyURLs
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) IsUglyURLs(section string) bool {
|
||||
return c.config.C.IsUglyURLSection(section)
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) IgnoreFile(s string) bool {
|
||||
return c.config.C.IgnoreFile(s)
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) DisablePathToLower() bool {
|
||||
return c.config.DisablePathToLower
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) RemovePathAccents() bool {
|
||||
return c.config.RemovePathAccents
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) DefaultContentLanguage() string {
|
||||
return c.config.DefaultContentLanguage
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) DefaultContentLanguageInSubdir() bool {
|
||||
return c.config.DefaultContentLanguageInSubdir
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) SummaryLength() int {
|
||||
return c.config.SummaryLength
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) BuildExpired() bool {
|
||||
return c.config.BuildExpired
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) BuildFuture() bool {
|
||||
return c.config.BuildFuture
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) BuildDrafts() bool {
|
||||
return c.config.BuildDrafts
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) Running() bool {
|
||||
return c.config.Internal.Running
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) PrintUnusedTemplates() bool {
|
||||
return c.config.PrintUnusedTemplates
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) EnableMissingTranslationPlaceholders() bool {
|
||||
return c.config.EnableMissingTranslationPlaceholders
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) LogI18nWarnings() bool {
|
||||
return c.config.LogI18nWarnings
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) CreateTitle(s string) string {
|
||||
return c.config.C.CreateTitle(s)
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) Paginate() int {
|
||||
return c.config.Paginate
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) PaginatePath() string {
|
||||
return c.config.PaginatePath
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) StaticDirs() []string {
|
||||
return c.config.staticDirs()
|
||||
}
|
71
config/allconfig/integration_test.go
Normal file
71
config/allconfig/integration_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package allconfig_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/config/allconfig"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestDirsMount(t *testing.T) {
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ["taxonomy", "term"]
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.sv]
|
||||
weight = 2
|
||||
[[module.mounts]]
|
||||
source = 'content/en'
|
||||
target = 'content'
|
||||
lang = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'content/sv'
|
||||
target = 'content'
|
||||
lang = 'sv'
|
||||
-- content/en/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
-- content/sv/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
Title: {{ .Title }}
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{T: t, TxtarString: files},
|
||||
).Build()
|
||||
|
||||
//b.AssertFileContent("public/p1/index.html", "Title: p1")
|
||||
|
||||
sites := b.H.Sites
|
||||
b.Assert(len(sites), qt.Equals, 2)
|
||||
|
||||
configs := b.H.Configs
|
||||
mods := configs.Modules
|
||||
b.Assert(len(mods), qt.Equals, 1)
|
||||
mod := mods[0]
|
||||
b.Assert(mod.Mounts(), qt.HasLen, 8)
|
||||
|
||||
enConcp := sites[0].Conf
|
||||
enConf := enConcp.GetConfig().(*allconfig.Config)
|
||||
|
||||
b.Assert(enConcp.BaseURL().String(), qt.Equals, "https://example.com")
|
||||
modConf := enConf.Module
|
||||
b.Assert(modConf.Mounts, qt.HasLen, 2)
|
||||
b.Assert(modConf.Mounts[0].Source, qt.Equals, filepath.FromSlash("content/en"))
|
||||
b.Assert(modConf.Mounts[0].Target, qt.Equals, "content")
|
||||
b.Assert(modConf.Mounts[0].Lang, qt.Equals, "en")
|
||||
b.Assert(modConf.Mounts[1].Source, qt.Equals, filepath.FromSlash("content/sv"))
|
||||
b.Assert(modConf.Mounts[1].Target, qt.Equals, "content")
|
||||
b.Assert(modConf.Mounts[1].Lang, qt.Equals, "sv")
|
||||
|
||||
}
|
559
config/allconfig/load.go
Normal file
559
config/allconfig/load.go
Normal file
@@ -0,0 +1,559 @@
|
||||
// Copyright 2023 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 allconfig contains the full configuration for Hugo.
|
||||
package allconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
hglob "github.com/gohugoio/hugo/hugofs/glob"
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
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")
|
||||
|
||||
func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
|
||||
if len(d.Environ) == 0 && !hugo.IsRunningAsTest() {
|
||||
d.Environ = os.Environ()
|
||||
}
|
||||
|
||||
l := &configLoader{ConfigSourceDescriptor: d, cfg: config.New()}
|
||||
// Make sure we always do this, even in error situations,
|
||||
// as we have commands (e.g. "hugo mod init") that will
|
||||
// use a partial configuration to do its job.
|
||||
defer l.deleteMergeStrategies()
|
||||
res, _, err := l.loadConfigMain(d)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
|
||||
configs, err := FromLoadConfigResult(d.Fs, res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create config from result: %w", err)
|
||||
}
|
||||
|
||||
moduleConfig, modulesClient, err := l.loadModules(configs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load modules: %w", err)
|
||||
}
|
||||
if len(l.ModulesConfigFiles) > 0 {
|
||||
// Config merged in from modules.
|
||||
// Re-read the config.
|
||||
configs, err = FromLoadConfigResult(d.Fs, res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
configs.Modules = moduleConfig.ActiveModules
|
||||
configs.ModulesClient = modulesClient
|
||||
|
||||
if err := configs.Init(); err != nil {
|
||||
return nil, fmt.Errorf("failed to init config: %w", err)
|
||||
}
|
||||
|
||||
return configs, nil
|
||||
|
||||
}
|
||||
|
||||
// ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.).
|
||||
type ConfigSourceDescriptor struct {
|
||||
Fs afero.Fs
|
||||
Logger loggers.Logger
|
||||
|
||||
// Config received from the command line.
|
||||
// These will override any config file settings.
|
||||
Flags config.Provider
|
||||
|
||||
// Path to the config file to use, e.g. /my/project/config.toml
|
||||
Filename string
|
||||
|
||||
// The (optional) directory for additional configuration files.
|
||||
ConfigDir string
|
||||
|
||||
// production, development
|
||||
Environment string
|
||||
|
||||
// Defaults to os.Environ if not set.
|
||||
Environ []string
|
||||
}
|
||||
|
||||
func (d ConfigSourceDescriptor) configFilenames() []string {
|
||||
if d.Filename == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(d.Filename, ",")
|
||||
}
|
||||
|
||||
type configLoader struct {
|
||||
cfg config.Provider
|
||||
BaseConfig config.BaseConfig
|
||||
ConfigSourceDescriptor
|
||||
|
||||
// collected
|
||||
ModulesConfig modules.ModulesConfig
|
||||
ModulesConfigFiles []string
|
||||
}
|
||||
|
||||
// Handle some legacy values.
|
||||
func (l configLoader) applyConfigAliases() error {
|
||||
aliases := []types.KeyValueStr{{Key: "taxonomies", Value: "indexes"}}
|
||||
|
||||
for _, alias := range aliases {
|
||||
if l.cfg.IsSet(alias.Key) {
|
||||
vv := l.cfg.Get(alias.Key)
|
||||
l.cfg.Set(alias.Value, vv)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l configLoader) applyDefaultConfig() error {
|
||||
defaultSettings := maps.Params{
|
||||
"baseURL": "",
|
||||
"cleanDestinationDir": false,
|
||||
"watch": false,
|
||||
"contentDir": "content",
|
||||
"resourceDir": "resources",
|
||||
"publishDir": "public",
|
||||
"publishDirOrig": "public",
|
||||
"themesDir": "themes",
|
||||
"assetDir": "assets",
|
||||
"layoutDir": "layouts",
|
||||
"i18nDir": "i18n",
|
||||
"dataDir": "data",
|
||||
"archetypeDir": "archetypes",
|
||||
"configDir": "config",
|
||||
"staticDir": "static",
|
||||
"buildDrafts": false,
|
||||
"buildFuture": false,
|
||||
"buildExpired": false,
|
||||
"params": maps.Params{},
|
||||
"environment": hugo.EnvironmentProduction,
|
||||
"uglyURLs": false,
|
||||
"verbose": false,
|
||||
"ignoreCache": false,
|
||||
"canonifyURLs": false,
|
||||
"relativeURLs": false,
|
||||
"removePathAccents": false,
|
||||
"titleCaseStyle": "AP",
|
||||
"taxonomies": maps.Params{"tag": "tags", "category": "categories"},
|
||||
"permalinks": maps.Params{},
|
||||
"sitemap": maps.Params{"priority": -1, "filename": "sitemap.xml"},
|
||||
"menus": maps.Params{},
|
||||
"disableLiveReload": false,
|
||||
"pluralizeListTitles": true,
|
||||
"forceSyncStatic": false,
|
||||
"footnoteAnchorPrefix": "",
|
||||
"footnoteReturnLinkContents": "",
|
||||
"newContentEditor": "",
|
||||
"paginate": 10,
|
||||
"paginatePath": "page",
|
||||
"summaryLength": 70,
|
||||
"rssLimit": -1,
|
||||
"sectionPagesMenu": "",
|
||||
"disablePathToLower": false,
|
||||
"hasCJKLanguage": false,
|
||||
"enableEmoji": false,
|
||||
"defaultContentLanguage": "en",
|
||||
"defaultContentLanguageInSubdir": false,
|
||||
"enableMissingTranslationPlaceholders": false,
|
||||
"enableGitInfo": false,
|
||||
"ignoreFiles": make([]string, 0),
|
||||
"disableAliases": false,
|
||||
"debug": false,
|
||||
"disableFastRender": false,
|
||||
"timeout": "30s",
|
||||
"timeZone": "",
|
||||
"enableInlineShortcodes": false,
|
||||
}
|
||||
|
||||
l.cfg.SetDefaults(defaultSettings)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l configLoader) normalizeCfg(cfg config.Provider) error {
|
||||
minify := cfg.Get("minify")
|
||||
if b, ok := minify.(bool); ok && b {
|
||||
cfg.Set("minify", maps.Params{"minifyOutput": true})
|
||||
}
|
||||
|
||||
// Simplify later merge.
|
||||
languages := cfg.GetStringMap("languages")
|
||||
for _, v := range languages {
|
||||
switch m := v.(type) {
|
||||
case maps.Params:
|
||||
// params have merge strategy deep by default.
|
||||
// The languages config key has strategy none by default.
|
||||
// This means that if these two sections does not exist on the left side,
|
||||
// they will not get merged in, so just create some empty maps.
|
||||
if _, ok := m["params"]; !ok {
|
||||
m["params"] = maps.Params{}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l configLoader) cleanExternalConfig(cfg config.Provider) error {
|
||||
if cfg.IsSet("internal") {
|
||||
cfg.Set("internal", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l configLoader) applyFlagsOverrides(cfg config.Provider) error {
|
||||
for _, k := range cfg.Keys() {
|
||||
l.cfg.Set(k, cfg.Get(k))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l configLoader) applyOsEnvOverrides(environ []string) error {
|
||||
if len(environ) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
const delim = "__env__delim"
|
||||
|
||||
// Extract all that start with the HUGO prefix.
|
||||
// The delimiter is the following rune, usually "_".
|
||||
const hugoEnvPrefix = "HUGO"
|
||||
var hugoEnv []types.KeyValueStr
|
||||
for _, v := range environ {
|
||||
key, val := config.SplitEnvVar(v)
|
||||
if strings.HasPrefix(key, hugoEnvPrefix) {
|
||||
delimiterAndKey := strings.TrimPrefix(key, hugoEnvPrefix)
|
||||
if len(delimiterAndKey) < 2 {
|
||||
continue
|
||||
}
|
||||
// Allow delimiters to be case sensitive.
|
||||
// It turns out there isn't that many allowed special
|
||||
// chars in environment variables when used in Bash and similar,
|
||||
// so variables on the form HUGOxPARAMSxFOO=bar is one option.
|
||||
key := strings.ReplaceAll(delimiterAndKey[1:], delimiterAndKey[:1], delim)
|
||||
key = strings.ToLower(key)
|
||||
hugoEnv = append(hugoEnv, types.KeyValueStr{
|
||||
Key: key,
|
||||
Value: val,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for _, env := range hugoEnv {
|
||||
existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, l.cfg.Get)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if existing != nil {
|
||||
val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if owner != nil {
|
||||
owner[nestedKey] = val
|
||||
} else {
|
||||
l.cfg.Set(env.Key, val)
|
||||
}
|
||||
} else if nestedKey != "" {
|
||||
owner[nestedKey] = env.Value
|
||||
} else {
|
||||
// The container does not exist yet.
|
||||
l.cfg.Set(strings.ReplaceAll(env.Key, delim, "."), env.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *configLoader) loadConfigMain(d ConfigSourceDescriptor) (config.LoadConfigResult, modules.ModulesConfig, error) {
|
||||
var res config.LoadConfigResult
|
||||
|
||||
if d.Flags != nil {
|
||||
if err := l.normalizeCfg(d.Flags); err != nil {
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
}
|
||||
|
||||
if d.Fs == nil {
|
||||
return res, l.ModulesConfig, errors.New("no filesystem provided")
|
||||
}
|
||||
|
||||
if d.Flags != nil {
|
||||
if err := l.applyFlagsOverrides(d.Flags); err != nil {
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
workingDir := filepath.Clean(l.cfg.GetString("workingDir"))
|
||||
|
||||
l.BaseConfig = config.BaseConfig{
|
||||
WorkingDir: workingDir,
|
||||
ThemesDir: paths.AbsPathify(workingDir, l.cfg.GetString("themesDir")),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
names := d.configFilenames()
|
||||
|
||||
if names != nil {
|
||||
for _, name := range names {
|
||||
var filename string
|
||||
filename, err := l.loadConfig(name)
|
||||
if err == nil {
|
||||
res.ConfigFiles = append(res.ConfigFiles, filename)
|
||||
} else if err != ErrNoConfigFile {
|
||||
return res, l.ModulesConfig, l.wrapFileError(err, filename)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, name := range config.DefaultConfigNames {
|
||||
var filename string
|
||||
filename, err := l.loadConfig(name)
|
||||
if err == nil {
|
||||
res.ConfigFiles = append(res.ConfigFiles, filename)
|
||||
break
|
||||
} else if err != ErrNoConfigFile {
|
||||
return res, l.ModulesConfig, l.wrapFileError(err, filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if d.ConfigDir != "" {
|
||||
absConfigDir := paths.AbsPathify(l.BaseConfig.WorkingDir, d.ConfigDir)
|
||||
dcfg, dirnames, err := config.LoadConfigFromDir(l.Fs, absConfigDir, l.Environment)
|
||||
if err == nil {
|
||||
if len(dirnames) > 0 {
|
||||
if err := l.normalizeCfg(dcfg); err != nil {
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
if err := l.cleanExternalConfig(dcfg); err != nil {
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
l.cfg.Set("", dcfg.Get(""))
|
||||
res.ConfigFiles = append(res.ConfigFiles, dirnames...)
|
||||
}
|
||||
} else if err != ErrNoConfigFile {
|
||||
if len(dirnames) > 0 {
|
||||
return res, l.ModulesConfig, l.wrapFileError(err, dirnames[0])
|
||||
}
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
}
|
||||
|
||||
res.Cfg = l.cfg
|
||||
|
||||
if err := l.applyDefaultConfig(); err != nil {
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
|
||||
// Some settings are used before we're done collecting all settings,
|
||||
// so apply OS environment both before and after.
|
||||
if err := l.applyOsEnvOverrides(d.Environ); err != nil {
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
|
||||
workingDir := filepath.Clean(l.cfg.GetString("workingDir"))
|
||||
|
||||
l.BaseConfig = config.BaseConfig{
|
||||
WorkingDir: workingDir,
|
||||
CacheDir: l.cfg.GetString("cacheDir"),
|
||||
ThemesDir: paths.AbsPathify(workingDir, l.cfg.GetString("themesDir")),
|
||||
}
|
||||
|
||||
var err error
|
||||
l.BaseConfig.CacheDir, err = helpers.GetCacheDir(l.Fs, l.BaseConfig.CacheDir)
|
||||
if err != nil {
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
|
||||
res.BaseConfig = l.BaseConfig
|
||||
|
||||
l.cfg.SetDefaultMergeStrategy()
|
||||
|
||||
res.ConfigFiles = append(res.ConfigFiles, l.ModulesConfigFiles...)
|
||||
|
||||
if d.Flags != nil {
|
||||
if err := l.applyFlagsOverrides(d.Flags); err != nil {
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := l.applyOsEnvOverrides(d.Environ); err != nil {
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
|
||||
if err = l.applyConfigAliases(); err != nil {
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
|
||||
func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *modules.Client, error) {
|
||||
bcfg := configs.LoadingInfo.BaseConfig
|
||||
conf := configs.Base
|
||||
workingDir := bcfg.WorkingDir
|
||||
themesDir := bcfg.ThemesDir
|
||||
|
||||
cfg := configs.LoadingInfo.Cfg
|
||||
|
||||
var ignoreVendor glob.Glob
|
||||
if s := conf.IgnoreVendorPaths; s != "" {
|
||||
ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
|
||||
}
|
||||
|
||||
ex := hexec.New(conf.Security)
|
||||
|
||||
hook := func(m *modules.ModulesConfig) error {
|
||||
for _, tc := range m.ActiveModules {
|
||||
if len(tc.ConfigFilenames()) > 0 {
|
||||
if tc.Watch() {
|
||||
l.ModulesConfigFiles = append(l.ModulesConfigFiles, tc.ConfigFilenames()...)
|
||||
}
|
||||
|
||||
// Merge in the theme config using the configured
|
||||
// merge strategy.
|
||||
cfg.Merge("", tc.Cfg().Get(""))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
modulesClient := modules.NewClient(modules.ClientConfig{
|
||||
Fs: l.Fs,
|
||||
Logger: l.Logger,
|
||||
Exec: ex,
|
||||
HookBeforeFinalize: hook,
|
||||
WorkingDir: workingDir,
|
||||
ThemesDir: themesDir,
|
||||
Environment: l.Environment,
|
||||
CacheDir: conf.Caches.CacheDirModules(),
|
||||
ModuleConfig: conf.Module,
|
||||
IgnoreVendor: ignoreVendor,
|
||||
})
|
||||
|
||||
moduleConfig, err := modulesClient.Collect()
|
||||
|
||||
// We want to watch these for changes and trigger rebuild on version
|
||||
// changes etc.
|
||||
if moduleConfig.GoModulesFilename != "" {
|
||||
l.ModulesConfigFiles = append(l.ModulesConfigFiles, moduleConfig.GoModulesFilename)
|
||||
}
|
||||
|
||||
if moduleConfig.GoWorkspaceFilename != "" {
|
||||
l.ModulesConfigFiles = append(l.ModulesConfigFiles, moduleConfig.GoWorkspaceFilename)
|
||||
}
|
||||
|
||||
return moduleConfig, modulesClient, err
|
||||
}
|
||||
|
||||
func (l configLoader) loadConfig(configName string) (string, error) {
|
||||
baseDir := l.BaseConfig.WorkingDir
|
||||
var baseFilename string
|
||||
if filepath.IsAbs(configName) {
|
||||
baseFilename = configName
|
||||
} else {
|
||||
baseFilename = filepath.Join(baseDir, configName)
|
||||
}
|
||||
|
||||
var filename string
|
||||
if paths.ExtNoDelimiter(configName) != "" {
|
||||
exists, _ := helpers.Exists(baseFilename, l.Fs)
|
||||
if exists {
|
||||
filename = baseFilename
|
||||
}
|
||||
} else {
|
||||
for _, ext := range config.ValidConfigFileExtensions {
|
||||
filenameToCheck := baseFilename + "." + ext
|
||||
exists, _ := helpers.Exists(filenameToCheck, l.Fs)
|
||||
if exists {
|
||||
filename = filenameToCheck
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if filename == "" {
|
||||
return "", ErrNoConfigFile
|
||||
}
|
||||
|
||||
m, err := config.FromFileToMap(l.Fs, filename)
|
||||
if err != nil {
|
||||
return filename, err
|
||||
}
|
||||
|
||||
// Set overwrites keys of the same name, recursively.
|
||||
l.cfg.Set("", m)
|
||||
|
||||
if err := l.normalizeCfg(l.cfg); err != nil {
|
||||
return filename, err
|
||||
}
|
||||
|
||||
if err := l.cleanExternalConfig(l.cfg); err != nil {
|
||||
return filename, err
|
||||
}
|
||||
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
func (l configLoader) deleteMergeStrategies() {
|
||||
l.cfg.WalkParams(func(params ...maps.KeyParams) bool {
|
||||
params[len(params)-1].Params.DeleteMergeStrategy()
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func (l configLoader) loadModulesConfig() (modules.Config, error) {
|
||||
modConfig, err := modules.DecodeConfig(l.cfg)
|
||||
if err != nil {
|
||||
return modules.Config{}, err
|
||||
}
|
||||
|
||||
return modConfig, nil
|
||||
}
|
||||
|
||||
func (l configLoader) wrapFileError(err error, filename string) error {
|
||||
fe := herrors.UnwrapFileError(err)
|
||||
if fe != nil {
|
||||
pos := fe.Position()
|
||||
pos.Filename = filename
|
||||
fe.UpdatePosition(pos)
|
||||
return err
|
||||
}
|
||||
return herrors.NewFileErrorFromFile(err, filename, l.Fs, nil)
|
||||
}
|
67
config/allconfig/load_test.go
Normal file
67
config/allconfig/load_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package allconfig
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
func BenchmarkLoad(b *testing.B) {
|
||||
tempDir := b.TempDir()
|
||||
configFilename := filepath.Join(tempDir, "hugo.toml")
|
||||
config := `
|
||||
baseURL = "https://example.com"
|
||||
defaultContentLanguage = 'en'
|
||||
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = 'content/en'
|
||||
target = 'content/en'
|
||||
lang = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'content/nn'
|
||||
target = 'content/nn'
|
||||
lang = 'nn'
|
||||
[[module.mounts]]
|
||||
source = 'content/no'
|
||||
target = 'content/no'
|
||||
lang = 'no'
|
||||
[[module.mounts]]
|
||||
source = 'content/sv'
|
||||
target = 'content/sv'
|
||||
lang = 'sv'
|
||||
[[module.mounts]]
|
||||
source = 'layouts'
|
||||
target = 'layouts'
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
title = "English"
|
||||
weight = 1
|
||||
[languages.nn]
|
||||
title = "Nynorsk"
|
||||
weight = 2
|
||||
[languages.no]
|
||||
title = "Norsk"
|
||||
weight = 3
|
||||
[languages.sv]
|
||||
title = "Svenska"
|
||||
weight = 4
|
||||
`
|
||||
if err := os.WriteFile(configFilename, []byte(config), 0666); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
d := ConfigSourceDescriptor{
|
||||
Fs: afero.NewOsFs(),
|
||||
Filename: configFilename,
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := LoadConfig(d)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user