github.com/neohugo/neohugo@v0.123.8/config/allconfig/allconfig.go (about)

     1  // Copyright 2024 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  // Package allconfig contains the full configuration for Hugo.
    15  // <docsmeta>{ "name": "Configuration", "description": "This section holds all configuration options in Hugo." }</docsmeta>
    16  package allconfig
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  	"reflect"
    22  	"regexp"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/neohugo/neohugo/cache/filecache"
    30  	"github.com/neohugo/neohugo/common/loggers"
    31  	"github.com/neohugo/neohugo/common/maps"
    32  	"github.com/neohugo/neohugo/common/neohugo"
    33  	"github.com/neohugo/neohugo/common/paths"
    34  	"github.com/neohugo/neohugo/common/types"
    35  	"github.com/neohugo/neohugo/common/urls"
    36  	"github.com/neohugo/neohugo/config"
    37  	"github.com/neohugo/neohugo/config/privacy"
    38  	"github.com/neohugo/neohugo/config/security"
    39  	"github.com/neohugo/neohugo/config/services"
    40  	"github.com/neohugo/neohugo/deploy/deployconfig"
    41  	"github.com/neohugo/neohugo/helpers"
    42  	"github.com/neohugo/neohugo/langs"
    43  	"github.com/neohugo/neohugo/markup/markup_config"
    44  	"github.com/neohugo/neohugo/media"
    45  	"github.com/neohugo/neohugo/minifiers"
    46  	"github.com/neohugo/neohugo/modules"
    47  	"github.com/neohugo/neohugo/navigation"
    48  	"github.com/neohugo/neohugo/output"
    49  	"github.com/neohugo/neohugo/related"
    50  	"github.com/neohugo/neohugo/resources/images"
    51  	"github.com/neohugo/neohugo/resources/kinds"
    52  	"github.com/neohugo/neohugo/resources/page"
    53  	"github.com/neohugo/neohugo/resources/page/pagemeta"
    54  	"github.com/spf13/afero"
    55  
    56  	xmaps "golang.org/x/exp/maps"
    57  )
    58  
    59  // InternalConfig is the internal configuration for Hugo, not read from any user provided config file.
    60  type InternalConfig struct {
    61  	// Server mode?
    62  	Running bool
    63  
    64  	Quiet          bool
    65  	Verbose        bool
    66  	Clock          string
    67  	Watch          bool
    68  	FastRenderMode bool
    69  	LiveReloadPort int
    70  }
    71  
    72  // All non-params config keys for language.
    73  var configLanguageKeys map[string]bool
    74  
    75  func init() {
    76  	skip := map[string]bool{
    77  		"internal":   true,
    78  		"c":          true,
    79  		"rootconfig": true,
    80  	}
    81  	configLanguageKeys = make(map[string]bool)
    82  	addKeys := func(v reflect.Value) {
    83  		for i := 0; i < v.NumField(); i++ {
    84  			name := strings.ToLower(v.Type().Field(i).Name)
    85  			if skip[name] {
    86  				continue
    87  			}
    88  			configLanguageKeys[name] = true
    89  		}
    90  	}
    91  	addKeys(reflect.ValueOf(Config{}))
    92  	addKeys(reflect.ValueOf(RootConfig{}))
    93  	addKeys(reflect.ValueOf(config.CommonDirs{}))
    94  	addKeys(reflect.ValueOf(langs.LanguageConfig{}))
    95  }
    96  
    97  type Config struct {
    98  	// For internal use only.
    99  	Internal InternalConfig `mapstructure:"-" json:"-"`
   100  	// For internal use only.
   101  	C *ConfigCompiled `mapstructure:"-" json:"-"`
   102  
   103  	RootConfig
   104  
   105  	// Author information.
   106  	Author map[string]any
   107  
   108  	// Social links.
   109  	Social map[string]string
   110  
   111  	// The build configuration section contains build-related configuration options.
   112  	// <docsmeta>{"identifiers": ["build"] }</docsmeta>
   113  	Build config.BuildConfig `mapstructure:"-"`
   114  
   115  	// The caches configuration section contains cache-related configuration options.
   116  	// <docsmeta>{"identifiers": ["caches"] }</docsmeta>
   117  	Caches filecache.Configs `mapstructure:"-"`
   118  
   119  	// The markup configuration section contains markup-related configuration options.
   120  	// <docsmeta>{"identifiers": ["markup"] }</docsmeta>
   121  	Markup markup_config.Config `mapstructure:"-"`
   122  
   123  	// The mediatypes configuration section maps the MIME type (a string) to a configuration object for that type.
   124  	// <docsmeta>{"identifiers": ["mediatypes"], "refs": ["types:media:type"] }</docsmeta>
   125  	MediaTypes *config.ConfigNamespace[map[string]media.MediaTypeConfig, media.Types] `mapstructure:"-"`
   126  
   127  	Imaging *config.ConfigNamespace[images.ImagingConfig, images.ImagingConfigInternal] `mapstructure:"-"`
   128  
   129  	// The outputformats configuration sections maps a format name (a string) to a configuration object for that format.
   130  	OutputFormats *config.ConfigNamespace[map[string]output.OutputFormatConfig, output.Formats] `mapstructure:"-"`
   131  
   132  	// The outputs configuration section maps a Page Kind (a string) to a slice of output formats.
   133  	// This can be overridden in the front matter.
   134  	Outputs map[string][]string `mapstructure:"-"`
   135  
   136  	// The cascade configuration section contains the top level front matter cascade configuration options,
   137  	// a slice of page matcher and params to apply to those pages.
   138  	Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, map[page.PageMatcher]maps.Params] `mapstructure:"-"`
   139  
   140  	// Menu configuration.
   141  	// <docsmeta>{"refs": ["config:languages:menus"] }</docsmeta>
   142  	Menus *config.ConfigNamespace[map[string]navigation.MenuConfig, navigation.Menus] `mapstructure:"-"`
   143  
   144  	// The deployment configuration section contains for hugo deployconfig.
   145  	Deployment deployconfig.DeployConfig `mapstructure:"-"`
   146  
   147  	// Module configuration.
   148  	Module modules.Config `mapstructure:"-"`
   149  
   150  	// Front matter configuration.
   151  	Frontmatter pagemeta.FrontmatterConfig `mapstructure:"-"`
   152  
   153  	// Minification configuration.
   154  	Minify minifiers.MinifyConfig `mapstructure:"-"`
   155  
   156  	// Permalink configuration.
   157  	Permalinks map[string]map[string]string `mapstructure:"-"`
   158  
   159  	// Taxonomy configuration.
   160  	Taxonomies map[string]string `mapstructure:"-"`
   161  
   162  	// Sitemap configuration.
   163  	Sitemap config.SitemapConfig `mapstructure:"-"`
   164  
   165  	// Related content configuration.
   166  	Related related.Config `mapstructure:"-"`
   167  
   168  	// Server configuration.
   169  	Server config.Server `mapstructure:"-"`
   170  
   171  	// Privacy configuration.
   172  	Privacy privacy.Config `mapstructure:"-"`
   173  
   174  	// Security configuration.
   175  	Security security.Config `mapstructure:"-"`
   176  
   177  	// Services configuration.
   178  	Services services.Config `mapstructure:"-"`
   179  
   180  	// User provided parameters.
   181  	// <docsmeta>{"refs": ["config:languages:params"] }</docsmeta>
   182  	Params maps.Params `mapstructure:"-"`
   183  
   184  	// The languages configuration sections maps a language code (a string) to a configuration object for that language.
   185  	Languages map[string]langs.LanguageConfig `mapstructure:"-"`
   186  
   187  	// UglyURLs configuration. Either a boolean or a sections map.
   188  	UglyURLs any `mapstructure:"-"`
   189  }
   190  
   191  type configCompiler interface {
   192  	CompileConfig(logger loggers.Logger) error
   193  }
   194  
   195  func (c Config) cloneForLang() *Config {
   196  	x := c
   197  	x.C = nil
   198  	copyStringSlice := func(in []string) []string {
   199  		if in == nil {
   200  			return nil
   201  		}
   202  		out := make([]string, len(in))
   203  		copy(out, in)
   204  		return out
   205  	}
   206  
   207  	// Copy all the slices to avoid sharing.
   208  	x.DisableKinds = copyStringSlice(x.DisableKinds)
   209  	x.DisableLanguages = copyStringSlice(x.DisableLanguages)
   210  	x.MainSections = copyStringSlice(x.MainSections)
   211  	x.IgnoreLogs = copyStringSlice(x.IgnoreLogs)
   212  	x.IgnoreFiles = copyStringSlice(x.IgnoreFiles)
   213  	x.Theme = copyStringSlice(x.Theme)
   214  
   215  	// Collapse all static dirs to one.
   216  	x.StaticDir = x.staticDirs()
   217  	// These will go away soon ...
   218  	x.StaticDir0 = nil
   219  	x.StaticDir1 = nil
   220  	x.StaticDir2 = nil
   221  	x.StaticDir3 = nil
   222  	x.StaticDir4 = nil
   223  	x.StaticDir5 = nil
   224  	x.StaticDir6 = nil
   225  	x.StaticDir7 = nil
   226  	x.StaticDir8 = nil
   227  	x.StaticDir9 = nil
   228  	x.StaticDir10 = nil
   229  
   230  	return &x
   231  }
   232  
   233  func (c *Config) CompileConfig(logger loggers.Logger) error {
   234  	var transientErr error
   235  	s := c.Timeout
   236  	if _, err := strconv.Atoi(s); err == nil {
   237  		// A number, assume seconds.
   238  		s = s + "s"
   239  	}
   240  	timeout, err := time.ParseDuration(s)
   241  	if err != nil {
   242  		return fmt.Errorf("failed to parse timeout: %s", err)
   243  	}
   244  	disabledKinds := make(map[string]bool)
   245  	for _, kind := range c.DisableKinds {
   246  		kind = strings.ToLower(kind)
   247  		if newKind := kinds.IsDeprecatedAndReplacedWith(kind); newKind != "" {
   248  			logger.Deprecatef(false, "Kind %q used in disableKinds is deprecated, use %q instead.", kind, newKind)
   249  			// Legacy config.
   250  			kind = newKind
   251  		}
   252  		if kinds.GetKindAny(kind) == "" {
   253  			logger.Warnf("Unknown kind %q in disableKinds configuration.", kind)
   254  			continue
   255  		}
   256  		disabledKinds[kind] = true
   257  	}
   258  	kindOutputFormats := make(map[string]output.Formats)
   259  	isRssDisabled := disabledKinds["rss"]
   260  	outputFormats := c.OutputFormats.Config
   261  	for kind, formats := range c.Outputs {
   262  		if newKind := kinds.IsDeprecatedAndReplacedWith(kind); newKind != "" {
   263  			logger.Deprecatef(false, "Kind %q used in outputs configuration is deprecated, use %q instead.", kind, newKind)
   264  			kind = newKind
   265  		}
   266  		if disabledKinds[kind] {
   267  			continue
   268  		}
   269  		if kinds.GetKindAny(kind) == "" {
   270  			logger.Warnf("Unknown kind %q in outputs configuration.", kind)
   271  			continue
   272  		}
   273  		for _, format := range formats {
   274  			if isRssDisabled && format == "rss" {
   275  				// Legacy config.
   276  				continue
   277  			}
   278  			f, found := outputFormats.GetByName(format)
   279  			if !found {
   280  				transientErr = fmt.Errorf("unknown output format %q for kind %q", format, kind)
   281  				continue
   282  			}
   283  			kindOutputFormats[kind] = append(kindOutputFormats[kind], f)
   284  		}
   285  	}
   286  
   287  	disabledLangs := make(map[string]bool)
   288  	for _, lang := range c.DisableLanguages {
   289  		disabledLangs[lang] = true
   290  	}
   291  	for lang, language := range c.Languages {
   292  		if !language.Disabled && disabledLangs[lang] {
   293  			language.Disabled = true
   294  			c.Languages[lang] = language
   295  		}
   296  		if language.Disabled {
   297  			disabledLangs[lang] = true
   298  			if lang == c.DefaultContentLanguage {
   299  				return fmt.Errorf("cannot disable default content language %q", lang)
   300  			}
   301  		}
   302  	}
   303  
   304  	ignoredLogIDs := make(map[string]bool)
   305  	for _, err := range c.IgnoreLogs {
   306  		ignoredLogIDs[strings.ToLower(err)] = true
   307  	}
   308  
   309  	baseURL, err := urls.NewBaseURLFromString(c.BaseURL)
   310  	if err != nil {
   311  		return err
   312  	}
   313  
   314  	isUglyURL := func(section string) bool {
   315  		switch v := c.UglyURLs.(type) {
   316  		case bool:
   317  			return v
   318  		case map[string]bool:
   319  			return v[section]
   320  		default:
   321  			return false
   322  		}
   323  	}
   324  
   325  	ignoreFile := func(s string) bool {
   326  		return false
   327  	}
   328  	if len(c.IgnoreFiles) > 0 {
   329  		regexps := make([]*regexp.Regexp, len(c.IgnoreFiles))
   330  		for i, pattern := range c.IgnoreFiles {
   331  			var err error
   332  			regexps[i], err = regexp.Compile(pattern)
   333  			if err != nil {
   334  				return fmt.Errorf("failed to compile ignoreFiles pattern %q: %s", pattern, err)
   335  			}
   336  		}
   337  		ignoreFile = func(s string) bool {
   338  			for _, r := range regexps {
   339  				if r.MatchString(s) {
   340  					return true
   341  				}
   342  			}
   343  			return false
   344  		}
   345  	}
   346  
   347  	var clock time.Time
   348  	if c.Internal.Clock != "" {
   349  		var err error
   350  		clock, err = time.Parse(time.RFC3339, c.Internal.Clock)
   351  		if err != nil {
   352  			return fmt.Errorf("failed to parse clock: %s", err)
   353  		}
   354  	}
   355  
   356  	c.C = &ConfigCompiled{
   357  		Timeout:           timeout,
   358  		BaseURL:           baseURL,
   359  		BaseURLLiveReload: baseURL,
   360  		DisabledKinds:     disabledKinds,
   361  		DisabledLanguages: disabledLangs,
   362  		IgnoredLogs:       ignoredLogIDs,
   363  		KindOutputFormats: kindOutputFormats,
   364  		CreateTitle:       helpers.GetTitleFunc(c.TitleCaseStyle),
   365  		IsUglyURLSection:  isUglyURL,
   366  		IgnoreFile:        ignoreFile,
   367  		MainSections:      c.MainSections,
   368  		Clock:             clock,
   369  		transientErr:      transientErr,
   370  	}
   371  
   372  	for _, s := range allDecoderSetups {
   373  		if getCompiler := s.getCompiler; getCompiler != nil {
   374  			if err := getCompiler(c).CompileConfig(logger); err != nil {
   375  				return err
   376  			}
   377  		}
   378  	}
   379  
   380  	return nil
   381  }
   382  
   383  func (c *Config) IsKindEnabled(kind string) bool {
   384  	return !c.C.DisabledKinds[kind]
   385  }
   386  
   387  func (c *Config) IsLangDisabled(lang string) bool {
   388  	return c.C.DisabledLanguages[lang]
   389  }
   390  
   391  // ConfigCompiled holds values and functions that are derived from the config.
   392  type ConfigCompiled struct {
   393  	Timeout           time.Duration
   394  	BaseURL           urls.BaseURL
   395  	BaseURLLiveReload urls.BaseURL
   396  	KindOutputFormats map[string]output.Formats
   397  	DisabledKinds     map[string]bool
   398  	DisabledLanguages map[string]bool
   399  	IgnoredLogs       map[string]bool
   400  	CreateTitle       func(s string) string
   401  	IsUglyURLSection  func(section string) bool
   402  	IgnoreFile        func(filename string) bool
   403  	MainSections      []string
   404  	Clock             time.Time
   405  
   406  	// This is set to the last transient error found during config compilation.
   407  	// With themes/modules we compute the configuration in multiple passes, and
   408  	// errors with missing output format definitions may resolve itself.
   409  	transientErr error
   410  
   411  	mu sync.Mutex
   412  }
   413  
   414  // This may be set after the config is compiled.
   415  func (c *ConfigCompiled) SetMainSections(sections []string) {
   416  	c.mu.Lock()
   417  	defer c.mu.Unlock()
   418  	c.MainSections = sections
   419  }
   420  
   421  // IsMainSectionsSet returns whether the main sections have been set.
   422  func (c *ConfigCompiled) IsMainSectionsSet() bool {
   423  	c.mu.Lock()
   424  	defer c.mu.Unlock()
   425  	return c.MainSections != nil
   426  }
   427  
   428  // This is set after the config is compiled by the server command.
   429  func (c *ConfigCompiled) SetBaseURL(baseURL, baseURLLiveReload urls.BaseURL) {
   430  	c.BaseURL = baseURL
   431  	c.BaseURLLiveReload = baseURLLiveReload
   432  }
   433  
   434  // RootConfig holds all the top-level configuration options in Hugo
   435  type RootConfig struct {
   436  	// The base URL of the site.
   437  	// Note that the default value is empty, but Hugo requires a valid URL (e.g. "https://example.com/") to work properly.
   438  	// <docsmeta>{"identifiers": ["URL"] }</docsmeta>
   439  	BaseURL string
   440  
   441  	// Whether to build content marked as draft.X
   442  	// <docsmeta>{"identifiers": ["draft"] }</docsmeta>
   443  	BuildDrafts bool
   444  
   445  	// Whether to build content with expiryDate in the past.
   446  	// <docsmeta>{"identifiers": ["expiryDate"] }</docsmeta>
   447  	BuildExpired bool
   448  
   449  	// Whether to build content with publishDate in the future.
   450  	// <docsmeta>{"identifiers": ["publishDate"] }</docsmeta>
   451  	BuildFuture bool
   452  
   453  	// Copyright information.
   454  	Copyright string
   455  
   456  	// The language to apply to content without any language indicator.
   457  	DefaultContentLanguage string
   458  
   459  	// By default, we put the default content language in the root and the others below their language ID, e.g. /no/.
   460  	// Set this to true to put all languages below their language ID.
   461  	DefaultContentLanguageInSubdir bool
   462  
   463  	// Disable creation of alias redirect pages.
   464  	DisableAliases bool
   465  
   466  	// Disable lower casing of path segments.
   467  	DisablePathToLower bool
   468  
   469  	// Disable page kinds from build.
   470  	DisableKinds []string
   471  
   472  	// A list of languages to disable.
   473  	DisableLanguages []string
   474  
   475  	// Disable the injection of the Hugo generator tag on the home page.
   476  	DisableHugoGeneratorInject bool
   477  
   478  	// Disable live reloading in server mode.
   479  	DisableLiveReload bool
   480  
   481  	// Enable replacement in Pages' Content of Emoji shortcodes with their equivalent Unicode characters.
   482  	// <docsmeta>{"identifiers": ["Content", "Unicode"] }</docsmeta>
   483  	EnableEmoji bool
   484  
   485  	// THe main section(s) of the site.
   486  	// If not set, Hugo will try to guess this from the content.
   487  	MainSections []string
   488  
   489  	// Enable robots.txt generation.
   490  	EnableRobotsTXT bool
   491  
   492  	// When enabled, Hugo will apply Git version information to each Page if possible, which
   493  	// can be used to keep lastUpdated in synch and to print version information.
   494  	// <docsmeta>{"identifiers": ["Page"] }</docsmeta>
   495  	EnableGitInfo bool
   496  
   497  	// Enable to track, calculate and print metrics.
   498  	TemplateMetrics bool
   499  
   500  	// Enable to track, print and calculate metric hints.
   501  	TemplateMetricsHints bool
   502  
   503  	// Enable to disable the build lock file.
   504  	NoBuildLock bool
   505  
   506  	// A list of log IDs to ignore.
   507  	IgnoreLogs []string
   508  
   509  	// A list of regexps that match paths to ignore.
   510  	// Deprecated: Use the settings on module imports.
   511  	IgnoreFiles []string
   512  
   513  	// Ignore cache.
   514  	IgnoreCache bool
   515  
   516  	// Enable to print greppable placeholders (on the form "[i18n] TRANSLATIONID") for missing translation strings.
   517  	EnableMissingTranslationPlaceholders bool
   518  
   519  	// Enable to panic on warning log entries. This may make it easier to detect the source.
   520  	PanicOnWarning bool
   521  
   522  	// The configured environment. Default is "development" for server and "production" for build.
   523  	Environment string
   524  
   525  	// The default language code.
   526  	LanguageCode string
   527  
   528  	// Enable if the site content has CJK language (Chinese, Japanese, or Korean). This affects how Hugo counts words.
   529  	HasCJKLanguage bool
   530  
   531  	// The default number of pages per page when paginating.
   532  	Paginate int
   533  
   534  	// The path to use when creating pagination URLs, e.g. "page" in /page/2/.
   535  	PaginatePath string
   536  
   537  	// Whether to pluralize default list titles.
   538  	// Note that this currently only works for English, but you can provide your own title in the content file's front matter.
   539  	PluralizeListTitles bool
   540  
   541  	// Whether to capitalize automatic page titles, applicable to section, taxonomy, and term pages.
   542  	CapitalizeListTitles bool
   543  
   544  	// Make all relative URLs absolute using the baseURL.
   545  	// <docsmeta>{"identifiers": ["baseURL"] }</docsmeta>
   546  	CanonifyURLs bool
   547  
   548  	// Enable this to make all relative URLs relative to content root. Note that this does not affect absolute URLs.
   549  	RelativeURLs bool
   550  
   551  	// Removes non-spacing marks from composite characters in content paths.
   552  	RemovePathAccents bool
   553  
   554  	// Whether to track and print unused templates during the build.
   555  	PrintUnusedTemplates bool
   556  
   557  	// Enable to print warnings for missing translation strings.
   558  	PrintI18nWarnings bool
   559  
   560  	// ENable to print warnings for multiple files published to the same destination.
   561  	PrintPathWarnings bool
   562  
   563  	// URL to be used as a placeholder when a page reference cannot be found in ref or relref. Is used as-is.
   564  	RefLinksNotFoundURL string
   565  
   566  	// When using ref or relref to resolve page links and a link cannot be resolved, it will be logged with this log level.
   567  	// Valid values are ERROR (default) or WARNING. Any ERROR will fail the build (exit -1).
   568  	RefLinksErrorLevel string
   569  
   570  	// This will create a menu with all the sections as menu items and all the sections’ pages as “shadow-members”.
   571  	SectionPagesMenu string
   572  
   573  	// The length of text in words to show in a .Summary.
   574  	SummaryLength int
   575  
   576  	// The site title.
   577  	Title string
   578  
   579  	// The theme(s) to use.
   580  	// See Modules for more a more flexible way to load themes.
   581  	Theme []string
   582  
   583  	// Timeout for generating page contents, specified as a duration or in seconds.
   584  	Timeout string
   585  
   586  	// The time zone (or location), e.g. Europe/Oslo, used to parse front matter dates without such information and in the time function.
   587  	TimeZone string
   588  
   589  	// Set titleCaseStyle to specify the title style used by the title template function and the automatic section titles in Hugo.
   590  	// 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).
   591  	TitleCaseStyle string
   592  
   593  	// The editor used for opening up new content.
   594  	NewContentEditor string
   595  
   596  	// Don't sync modification time of files for the static mounts.
   597  	NoTimes bool
   598  
   599  	// Don't sync modification time of files for the static mounts.
   600  	NoChmod bool
   601  
   602  	// Clean the destination folder before a new build.
   603  	// This currently only handles static files.
   604  	CleanDestinationDir bool
   605  
   606  	// A Glob pattern of module paths to ignore in the _vendor folder.
   607  	IgnoreVendorPaths string
   608  
   609  	config.CommonDirs `mapstructure:",squash"`
   610  
   611  	// The odd constructs below are kept for backwards compatibility.
   612  	// Deprecated: Use module mount config instead.
   613  	StaticDir []string
   614  	// Deprecated: Use module mount config instead.
   615  	StaticDir0 []string
   616  	// Deprecated: Use module mount config instead.
   617  	StaticDir1 []string
   618  	// Deprecated: Use module mount config instead.
   619  	StaticDir2 []string
   620  	// Deprecated: Use module mount config instead.
   621  	StaticDir3 []string
   622  	// Deprecated: Use module mount config instead.
   623  	StaticDir4 []string
   624  	// Deprecated: Use module mount config instead.
   625  	StaticDir5 []string
   626  	// Deprecated: Use module mount config instead.
   627  	StaticDir6 []string
   628  	// Deprecated: Use module mount config instead.
   629  	StaticDir7 []string
   630  	// Deprecated: Use module mount config instead.
   631  	StaticDir8 []string
   632  	// Deprecated: Use module mount config instead.
   633  	StaticDir9 []string
   634  	// Deprecated: Use module mount config instead.
   635  	StaticDir10 []string
   636  }
   637  
   638  func (c RootConfig) staticDirs() []string {
   639  	var dirs []string
   640  	dirs = append(dirs, c.StaticDir...)
   641  	dirs = append(dirs, c.StaticDir0...)
   642  	dirs = append(dirs, c.StaticDir1...)
   643  	dirs = append(dirs, c.StaticDir2...)
   644  	dirs = append(dirs, c.StaticDir3...)
   645  	dirs = append(dirs, c.StaticDir4...)
   646  	dirs = append(dirs, c.StaticDir5...)
   647  	dirs = append(dirs, c.StaticDir6...)
   648  	dirs = append(dirs, c.StaticDir7...)
   649  	dirs = append(dirs, c.StaticDir8...)
   650  	dirs = append(dirs, c.StaticDir9...)
   651  	dirs = append(dirs, c.StaticDir10...)
   652  	return helpers.UniqueStringsReuse(dirs)
   653  }
   654  
   655  type Configs struct {
   656  	Base                *Config
   657  	LoadingInfo         config.LoadConfigResult
   658  	LanguageConfigMap   map[string]*Config
   659  	LanguageConfigSlice []*Config
   660  
   661  	IsMultihost bool
   662  
   663  	Modules       modules.Modules
   664  	ModulesClient *modules.Client
   665  
   666  	// All below is set in Init.
   667  	Languages             langs.Languages
   668  	LanguagesDefaultFirst langs.Languages
   669  	ContentPathParser     *paths.PathParser
   670  
   671  	configLangs []config.AllProvider
   672  }
   673  
   674  func (c *Configs) Validate(logger loggers.Logger) error {
   675  	for p := range c.Base.Cascade.Config {
   676  		page.CheckCascadePattern(logger, p)
   677  	}
   678  	return nil
   679  }
   680  
   681  // transientErr returns the last transient error found during config compilation.
   682  func (c *Configs) transientErr() error {
   683  	for _, l := range c.LanguageConfigSlice {
   684  		if l.C.transientErr != nil {
   685  			return l.C.transientErr
   686  		}
   687  	}
   688  	return nil
   689  }
   690  
   691  func (c *Configs) IsZero() bool {
   692  	// A config always has at least one language.
   693  	return c == nil || len(c.Languages) == 0
   694  }
   695  
   696  func (c *Configs) Init() error {
   697  	var languages langs.Languages
   698  	defaultContentLanguage := c.Base.DefaultContentLanguage
   699  	for k, v := range c.LanguageConfigMap {
   700  		c.LanguageConfigSlice = append(c.LanguageConfigSlice, v)
   701  		languageConf := v.Languages[k]
   702  		language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
   703  		if err != nil {
   704  			return err
   705  		}
   706  		languages = append(languages, language)
   707  	}
   708  
   709  	// Sort the sites by language weight (if set) or lang.
   710  	sort.Slice(languages, func(i, j int) bool {
   711  		li := languages[i]
   712  		lj := languages[j]
   713  		if li.Weight != lj.Weight {
   714  			return li.Weight < lj.Weight
   715  		}
   716  		return li.Lang < lj.Lang
   717  	})
   718  
   719  	for _, l := range languages {
   720  		c.LanguageConfigSlice = append(c.LanguageConfigSlice, c.LanguageConfigMap[l.Lang])
   721  	}
   722  
   723  	// Filter out disabled languages.
   724  	var n int
   725  	for _, l := range languages {
   726  		if !l.Disabled {
   727  			languages[n] = l
   728  			n++
   729  		}
   730  	}
   731  	languages = languages[:n]
   732  
   733  	var languagesDefaultFirst langs.Languages
   734  	for _, l := range languages {
   735  		if l.Lang == defaultContentLanguage {
   736  			languagesDefaultFirst = append(languagesDefaultFirst, l)
   737  		}
   738  	}
   739  	for _, l := range languages {
   740  		if l.Lang != defaultContentLanguage {
   741  			languagesDefaultFirst = append(languagesDefaultFirst, l)
   742  		}
   743  	}
   744  
   745  	c.Languages = languages
   746  	c.LanguagesDefaultFirst = languagesDefaultFirst
   747  
   748  	c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled}
   749  
   750  	c.configLangs = make([]config.AllProvider, len(c.Languages))
   751  	for i, l := range c.LanguagesDefaultFirst {
   752  		c.configLangs[i] = ConfigLanguage{
   753  			m:          c,
   754  			config:     c.LanguageConfigMap[l.Lang],
   755  			baseConfig: c.LoadingInfo.BaseConfig,
   756  			language:   l,
   757  		}
   758  	}
   759  
   760  	if len(c.Modules) == 0 {
   761  		return errors.New("no modules loaded (ned at least the main module)")
   762  	}
   763  
   764  	// Apply default project mounts.
   765  	if err := modules.ApplyProjectConfigDefaults(c.Modules[0], c.configLangs...); err != nil {
   766  		return err
   767  	}
   768  
   769  	// We should consolidate this, but to get a full view of the mounts in e.g. "hugo config" we need to
   770  	// transfer any default mounts added above to the config used to print the config.
   771  	for _, m := range c.Modules[0].Mounts() {
   772  		var found bool
   773  		for _, cm := range c.Base.Module.Mounts {
   774  			if cm.Source == m.Source && cm.Target == m.Target && cm.Lang == m.Lang {
   775  				found = true
   776  				break
   777  			}
   778  		}
   779  		if !found {
   780  			c.Base.Module.Mounts = append(c.Base.Module.Mounts, m)
   781  		}
   782  	}
   783  
   784  	// Transfer the changed mounts to the language versions (all share the same mount set, but can be displayed in different languages).
   785  	for _, l := range c.LanguageConfigSlice {
   786  		l.Module.Mounts = c.Base.Module.Mounts
   787  	}
   788  
   789  	return nil
   790  }
   791  
   792  func (c Configs) ConfigLangs() []config.AllProvider {
   793  	return c.configLangs
   794  }
   795  
   796  func (c Configs) GetFirstLanguageConfig() config.AllProvider {
   797  	return c.configLangs[0]
   798  }
   799  
   800  func (c Configs) GetByLang(lang string) config.AllProvider {
   801  	for _, l := range c.configLangs {
   802  		if l.Language().Lang == lang {
   803  			return l
   804  		}
   805  	}
   806  	return nil
   807  }
   808  
   809  // fromLoadConfigResult creates a new Config from res.
   810  func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadConfigResult) (*Configs, error) {
   811  	if !res.Cfg.IsSet("languages") {
   812  		// We need at least one
   813  		lang := res.Cfg.GetString("defaultContentLanguage")
   814  		res.Cfg.Set("languages", maps.Params{lang: maps.Params{}})
   815  	}
   816  	bcfg := res.BaseConfig
   817  	cfg := res.Cfg
   818  
   819  	all := &Config{}
   820  
   821  	err := decodeConfigFromParams(fs, logger, bcfg, cfg, all, nil)
   822  	if err != nil {
   823  		return nil, err
   824  	}
   825  
   826  	langConfigMap := make(map[string]*Config)
   827  
   828  	languagesConfig := cfg.GetStringMap("languages")
   829  	var isMultiHost bool
   830  
   831  	if err := all.CompileConfig(logger); err != nil {
   832  		return nil, err
   833  	}
   834  
   835  	for k, v := range languagesConfig {
   836  		mergedConfig := config.New()
   837  		var differentRootKeys []string
   838  		switch x := v.(type) {
   839  		case maps.Params:
   840  			var params maps.Params
   841  			pv, found := x["params"]
   842  			if found {
   843  				params = pv.(maps.Params)
   844  			} else {
   845  				params = maps.Params{
   846  					maps.MergeStrategyKey: maps.ParamsMergeStrategyDeep,
   847  				}
   848  				x["params"] = params
   849  			}
   850  
   851  			for kk, vv := range x {
   852  				if kk == "_merge" {
   853  					continue
   854  				}
   855  				if kk != maps.MergeStrategyKey && !configLanguageKeys[kk] {
   856  					// This should have been placed below params.
   857  					// We accidentally allowed it in the past, so we need to support it a little longer,
   858  					// But log a warning.
   859  					if _, found := params[kk]; !found {
   860  						neohugo.Deprecate(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), "v0.112.0")
   861  						params[kk] = vv
   862  					}
   863  				}
   864  				if kk == "baseurl" {
   865  					// baseURL configure don the language level is a multihost setup.
   866  					isMultiHost = true
   867  				}
   868  				mergedConfig.Set(kk, vv)
   869  				rootv := cfg.Get(kk)
   870  				if rootv != nil && cfg.IsSet(kk) {
   871  					// This overrides a root key and potentially needs a merge.
   872  					if !reflect.DeepEqual(rootv, vv) {
   873  						switch vvv := vv.(type) {
   874  						case maps.Params:
   875  							differentRootKeys = append(differentRootKeys, kk)
   876  
   877  							// Use the language value as base.
   878  							mergedConfigEntry := xmaps.Clone(vvv)
   879  							// Merge in the root value.
   880  							maps.MergeParams(mergedConfigEntry, rootv.(maps.Params))
   881  
   882  							mergedConfig.Set(kk, mergedConfigEntry)
   883  						default:
   884  							// Apply new values to the root.
   885  							differentRootKeys = append(differentRootKeys, "")
   886  						}
   887  					}
   888  				} else {
   889  					switch vv.(type) {
   890  					case maps.Params:
   891  						differentRootKeys = append(differentRootKeys, kk)
   892  					default:
   893  						// Apply new values to the root.
   894  						differentRootKeys = append(differentRootKeys, "")
   895  					}
   896  				}
   897  			}
   898  			differentRootKeys = helpers.UniqueStringsSorted(differentRootKeys)
   899  
   900  			if len(differentRootKeys) == 0 {
   901  				langConfigMap[k] = all
   902  				continue
   903  			}
   904  
   905  			// Create a copy of the complete config and replace the root keys with the language specific ones.
   906  			clone := all.cloneForLang()
   907  
   908  			if err := decodeConfigFromParams(fs, logger, bcfg, mergedConfig, clone, differentRootKeys); err != nil {
   909  				return nil, fmt.Errorf("failed to decode config for language %q: %w", k, err)
   910  			}
   911  			if err := clone.CompileConfig(logger); err != nil {
   912  				return nil, err
   913  			}
   914  
   915  			// Adjust Goldmark config defaults for multilingual, single-host sites.
   916  			if len(languagesConfig) > 1 && !isMultiHost && !clone.Markup.Goldmark.DuplicateResourceFiles {
   917  				if !clone.Markup.Goldmark.DuplicateResourceFiles {
   918  					if clone.Markup.Goldmark.RenderHooks.Link.EnableDefault == nil {
   919  						clone.Markup.Goldmark.RenderHooks.Link.EnableDefault = types.NewBool(true)
   920  					}
   921  					if clone.Markup.Goldmark.RenderHooks.Image.EnableDefault == nil {
   922  						clone.Markup.Goldmark.RenderHooks.Image.EnableDefault = types.NewBool(true)
   923  					}
   924  				}
   925  			}
   926  
   927  			langConfigMap[k] = clone
   928  		case maps.ParamsMergeStrategy:
   929  		default:
   930  			panic(fmt.Sprintf("unknown type in languages config: %T", v))
   931  
   932  		}
   933  	}
   934  
   935  	bcfg.PublishDir = all.PublishDir
   936  	res.BaseConfig = bcfg
   937  	all.CommonDirs.CacheDir = bcfg.CacheDir
   938  	for _, l := range langConfigMap {
   939  		l.CommonDirs.CacheDir = bcfg.CacheDir
   940  	}
   941  
   942  	cm := &Configs{
   943  		Base:              all,
   944  		LanguageConfigMap: langConfigMap,
   945  		LoadingInfo:       res,
   946  		IsMultihost:       isMultiHost,
   947  	}
   948  
   949  	return cm, nil
   950  }
   951  
   952  func decodeConfigFromParams(fs afero.Fs, logger loggers.Logger, bcfg config.BaseConfig, p config.Provider, target *Config, keys []string) error {
   953  	var decoderSetups []decodeWeight
   954  
   955  	if len(keys) == 0 {
   956  		for _, v := range allDecoderSetups {
   957  			decoderSetups = append(decoderSetups, v)
   958  		}
   959  	} else {
   960  		for _, key := range keys {
   961  			if v, found := allDecoderSetups[key]; found {
   962  				decoderSetups = append(decoderSetups, v)
   963  			} else {
   964  				logger.Warnf("Skip unknown config key %q", key)
   965  			}
   966  		}
   967  	}
   968  
   969  	// Sort them to get the dependency order right.
   970  	sort.Slice(decoderSetups, func(i, j int) bool {
   971  		ki, kj := decoderSetups[i], decoderSetups[j]
   972  		if ki.weight == kj.weight {
   973  			return ki.key < kj.key
   974  		}
   975  		return ki.weight < kj.weight
   976  	})
   977  
   978  	for _, v := range decoderSetups {
   979  		p := decodeConfig{p: p, c: target, fs: fs, bcfg: bcfg}
   980  		if err := v.decode(v, p); err != nil {
   981  			return fmt.Errorf("failed to decode %q: %w", v.key, err)
   982  		}
   983  	}
   984  
   985  	return nil
   986  }
   987  
   988  func createDefaultOutputFormats(allFormats output.Formats) map[string][]string {
   989  	if len(allFormats) == 0 {
   990  		panic("no output formats")
   991  	}
   992  	rssOut, rssFound := allFormats.GetByName(output.RSSFormat.Name)
   993  	htmlOut, _ := allFormats.GetByName(output.HTMLFormat.Name)
   994  
   995  	defaultListTypes := []string{htmlOut.Name}
   996  	if rssFound {
   997  		defaultListTypes = append(defaultListTypes, rssOut.Name)
   998  	}
   999  
  1000  	m := map[string][]string{
  1001  		kinds.KindPage:     {htmlOut.Name},
  1002  		kinds.KindHome:     defaultListTypes,
  1003  		kinds.KindSection:  defaultListTypes,
  1004  		kinds.KindTerm:     defaultListTypes,
  1005  		kinds.KindTaxonomy: defaultListTypes,
  1006  	}
  1007  
  1008  	// May be disabled
  1009  	if rssFound {
  1010  		m["rss"] = []string{rssOut.Name}
  1011  	}
  1012  
  1013  	return m
  1014  }