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 }