2023-01-04 12:24:36 -05:00
// 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.
2023-05-18 16:51:11 -04:00
// <docsmeta>{ "name": "Configuration", "description": "This section holds all configuration options in Hugo." }</docsmeta>
2023-01-04 12:24:36 -05:00
package allconfig
import (
"errors"
"fmt"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
2023-05-17 12:45:23 -04:00
"sync"
2023-01-04 12:24:36 -05:00
"time"
"github.com/gohugoio/hugo/cache/filecache"
2023-05-21 08:25:16 -04:00
"github.com/gohugoio/hugo/common/loggers"
2023-01-04 12:24:36 -05:00
"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
}
2023-05-17 10:29:06 -04:00
// All non-params config keys for language.
var configLanguageKeys map [ string ] bool
func init ( ) {
skip := map [ string ] bool {
"internal" : true ,
"c" : true ,
"rootconfig" : true ,
}
configLanguageKeys = make ( map [ string ] bool )
addKeys := func ( v reflect . Value ) {
for i := 0 ; i < v . NumField ( ) ; i ++ {
name := strings . ToLower ( v . Type ( ) . Field ( i ) . Name )
if skip [ name ] {
continue
}
configLanguageKeys [ name ] = true
}
}
addKeys ( reflect . ValueOf ( Config { } ) )
addKeys ( reflect . ValueOf ( RootConfig { } ) )
addKeys ( reflect . ValueOf ( config . CommonDirs { } ) )
addKeys ( reflect . ValueOf ( langs . LanguageConfig { } ) )
}
2023-01-04 12:24:36 -05:00
type Config struct {
// For internal use only.
Internal InternalConfig ` mapstructure:"-" json:"-" `
// For internal use only.
2023-05-17 12:45:23 -04:00
C * ConfigCompiled ` mapstructure:"-" json:"-" `
2023-01-04 12:24:36 -05:00
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 {
2023-05-21 08:25:16 -04:00
CompileConfig ( logger loggers . Logger ) error
2023-01-04 12:24:36 -05:00
}
func ( c Config ) cloneForLang ( ) * Config {
x := c
2023-05-17 12:45:23 -04:00
x . C = nil
2023-01-04 12:24:36 -05:00
// 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
}
2023-05-21 08:25:16 -04:00
func ( c * Config ) CompileConfig ( logger loggers . Logger ) error {
2023-05-17 07:12:49 -04:00
var transientErr error
2023-01-04 12:24:36 -05:00
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 {
2023-05-19 03:17:55 -04:00
kind = strings . ToLower ( kind )
if kind == "taxonomyterm" {
// Legacy config.
kind = "term"
}
disabledKinds [ kind ] = true
2023-01-04 12:24:36 -05:00
}
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 {
2023-05-17 07:12:49 -04:00
transientErr = fmt . Errorf ( "unknown output format %q for kind %q" , format , kind )
continue
2023-01-04 12:24:36 -05:00
}
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 )
}
}
2023-05-17 12:45:23 -04:00
c . C = & ConfigCompiled {
2023-01-04 12:24:36 -05:00
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 ,
2023-05-17 07:12:49 -04:00
transientErr : transientErr ,
2023-01-04 12:24:36 -05:00
}
for _ , s := range allDecoderSetups {
if getCompiler := s . getCompiler ; getCompiler != nil {
2023-05-21 08:25:16 -04:00
if err := getCompiler ( c ) . CompileConfig ( logger ) ; err != nil {
2023-01-04 12:24:36 -05:00
return err
}
}
}
return nil
}
2023-05-17 12:45:23 -04:00
func ( c * Config ) IsKindEnabled ( kind string ) bool {
2023-01-04 12:24:36 -05:00
return ! c . C . DisabledKinds [ kind ]
}
2023-05-17 12:45:23 -04:00
func ( c * Config ) IsLangDisabled ( lang string ) bool {
2023-01-04 12:24:36 -05:00
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
2023-05-17 07:12:49 -04:00
// This is set to the last transient error found during config compilation.
2023-05-17 10:29:06 -04:00
// With themes/modules we compute the configuration in multiple passes, and
2023-05-17 07:12:49 -04:00
// errors with missing output format definitions may resolve itself.
transientErr error
2023-05-17 12:45:23 -04:00
mu sync . Mutex
2023-01-04 12:24:36 -05:00
}
// This may be set after the config is compiled.
2023-05-17 12:45:23 -04:00
func ( c * ConfigCompiled ) SetMainSectionsIfNotSet ( sections [ ] string ) {
c . mu . Lock ( )
defer c . mu . Unlock ( )
2023-05-18 09:50:48 -04:00
if c . MainSections != nil {
2023-05-17 12:45:23 -04:00
return
}
2023-01-04 12:24:36 -05:00
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
}
2023-05-17 07:12:49 -04:00
// transientErr returns the last transient error found during config compilation.
func ( c * Configs ) transientErr ( ) error {
for _ , l := range c . LanguageConfigSlice {
if l . C . transientErr != nil {
return l . C . transientErr
}
}
return nil
}
2023-01-04 12:24:36 -05:00
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
}
2023-05-21 08:25:16 -04:00
// fromLoadConfigResult creates a new Config from res.
func fromLoadConfigResult ( fs afero . Fs , logger loggers . Logger , res config . LoadConfigResult ) ( * Configs , error ) {
2023-01-04 12:24:36 -05:00
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
2023-05-21 08:25:16 -04:00
if err := all . CompileConfig ( logger ) ; err != nil {
2023-01-04 12:24:36 -05:00
return nil , err
}
for k , v := range languagesConfig {
mergedConfig := config . New ( )
var differentRootKeys [ ] string
switch x := v . ( type ) {
case maps . Params :
2023-05-17 10:29:06 -04:00
var params maps . Params
pv , found := x [ "params" ]
if found {
params = pv . ( maps . Params )
} else {
params = maps . Params {
maps . MergeStrategyKey : maps . ParamsMergeStrategyDeep ,
}
x [ "params" ] = params
}
2023-01-04 12:24:36 -05:00
for kk , vv := range x {
2023-05-17 10:29:06 -04:00
if kk == "_merge" {
continue
}
if kk != maps . MergeStrategyKey && ! configLanguageKeys [ kk ] {
// This should have been placed below params.
2023-05-18 16:51:11 -04:00
// We accidentally allowed it in the past, so we need to support it a little longer,
2023-05-17 10:29:06 -04:00
// But log a warning.
if _ , found := params [ kk ] ; ! found {
2023-05-18 16:51:11 -04:00
helpers . Deprecated ( fmt . Sprintf ( "config: languages.%s.%s: custom params on the language top level" , k , kk ) , fmt . Sprintf ( "Put the value below [languages.%s.params]. See https://gohugo.io/content-management/multilingual/#changes-in-hugo-01120" , k ) , false )
2023-05-17 10:29:06 -04:00
params [ kk ] = vv
}
}
2023-01-04 12:24:36 -05:00
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 {
2023-05-20 05:17:43 -04:00
switch vv . ( type ) {
case maps . Params :
differentRootKeys = append ( differentRootKeys , kk )
default :
// Apply new values to the root.
differentRootKeys = append ( differentRootKeys , "" )
}
2023-01-04 12:24:36 -05:00
}
}
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 )
}
2023-05-21 08:25:16 -04:00
if err := clone . CompileConfig ( logger ) ; err != nil {
2023-01-04 12:24:36 -05:00
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
}