github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/config/allconfig/load.go (about) 1 // Copyright 2023 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 package allconfig 16 17 import ( 18 "errors" 19 "fmt" 20 "os" 21 "path/filepath" 22 "strings" 23 24 "github.com/gobwas/glob" 25 "github.com/gohugoio/hugo/common/herrors" 26 "github.com/gohugoio/hugo/common/hexec" 27 "github.com/gohugoio/hugo/common/hugo" 28 "github.com/gohugoio/hugo/common/loggers" 29 "github.com/gohugoio/hugo/common/maps" 30 "github.com/gohugoio/hugo/common/paths" 31 "github.com/gohugoio/hugo/common/types" 32 "github.com/gohugoio/hugo/config" 33 "github.com/gohugoio/hugo/helpers" 34 hglob "github.com/gohugoio/hugo/hugofs/glob" 35 "github.com/gohugoio/hugo/modules" 36 "github.com/gohugoio/hugo/parser/metadecoders" 37 "github.com/gohugoio/hugo/tpl" 38 "github.com/spf13/afero" 39 ) 40 41 var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\n Run `hugo help new` for details.\n") 42 43 func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) { 44 if len(d.Environ) == 0 && !hugo.IsRunningAsTest() { 45 d.Environ = os.Environ() 46 } 47 48 if d.Logger == nil { 49 d.Logger = loggers.NewDefault() 50 } 51 52 l := &configLoader{ConfigSourceDescriptor: d, cfg: config.New()} 53 // Make sure we always do this, even in error situations, 54 // as we have commands (e.g. "hugo mod init") that will 55 // use a partial configuration to do its job. 56 defer l.deleteMergeStrategies() 57 res, _, err := l.loadConfigMain(d) 58 if err != nil { 59 return nil, fmt.Errorf("failed to load config: %w", err) 60 } 61 62 configs, err := fromLoadConfigResult(d.Fs, d.Logger, res) 63 if err != nil { 64 return nil, fmt.Errorf("failed to create config from result: %w", err) 65 } 66 67 moduleConfig, modulesClient, err := l.loadModules(configs) 68 if err != nil { 69 return nil, fmt.Errorf("failed to load modules: %w", err) 70 } 71 72 if len(l.ModulesConfigFiles) > 0 { 73 // Config merged in from modules. 74 // Re-read the config. 75 configs, err = fromLoadConfigResult(d.Fs, d.Logger, res) 76 if err != nil { 77 return nil, fmt.Errorf("failed to create config from modules config: %w", err) 78 } 79 if err := configs.transientErr(); err != nil { 80 return nil, fmt.Errorf("failed to create config from modules config: %w", err) 81 } 82 configs.LoadingInfo.ConfigFiles = append(configs.LoadingInfo.ConfigFiles, l.ModulesConfigFiles...) 83 } else if err := configs.transientErr(); err != nil { 84 return nil, fmt.Errorf("failed to create config: %w", err) 85 } 86 87 configs.Modules = moduleConfig.AllModules 88 configs.ModulesClient = modulesClient 89 90 if err := configs.Init(); err != nil { 91 return nil, fmt.Errorf("failed to init config: %w", err) 92 } 93 94 // This is unfortunate, but these are global settings. 95 tpl.SetSecurityAllowActionJSTmpl(configs.Base.Security.GoTemplates.AllowActionJSTmpl) 96 loggers.InitGlobalLogger(configs.Base.PanicOnWarning) 97 98 return configs, nil 99 100 } 101 102 // ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.). 103 type ConfigSourceDescriptor struct { 104 Fs afero.Fs 105 Logger loggers.Logger 106 107 // Config received from the command line. 108 // These will override any config file settings. 109 Flags config.Provider 110 111 // Path to the config file to use, e.g. /my/project/config.toml 112 Filename string 113 114 // The (optional) directory for additional configuration files. 115 ConfigDir string 116 117 // production, development 118 Environment string 119 120 // Defaults to os.Environ if not set. 121 Environ []string 122 } 123 124 func (d ConfigSourceDescriptor) configFilenames() []string { 125 if d.Filename == "" { 126 return nil 127 } 128 return strings.Split(d.Filename, ",") 129 } 130 131 type configLoader struct { 132 cfg config.Provider 133 BaseConfig config.BaseConfig 134 ConfigSourceDescriptor 135 136 // collected 137 ModulesConfig modules.ModulesConfig 138 ModulesConfigFiles []string 139 } 140 141 // Handle some legacy values. 142 func (l configLoader) applyConfigAliases() error { 143 aliases := []types.KeyValueStr{ 144 {Key: "indexes", Value: "taxonomies"}, 145 {Key: "logI18nWarnings", Value: "printI18nWarnings"}, 146 {Key: "logPathWarnings", Value: "printPathWarnings"}, 147 } 148 149 for _, alias := range aliases { 150 if l.cfg.IsSet(alias.Key) { 151 vv := l.cfg.Get(alias.Key) 152 l.cfg.Set(alias.Value, vv) 153 } 154 } 155 156 return nil 157 } 158 159 func (l configLoader) applyDefaultConfig() error { 160 defaultSettings := maps.Params{ 161 "baseURL": "", 162 "cleanDestinationDir": false, 163 "watch": false, 164 "contentDir": "content", 165 "resourceDir": "resources", 166 "publishDir": "public", 167 "publishDirOrig": "public", 168 "themesDir": "themes", 169 "assetDir": "assets", 170 "layoutDir": "layouts", 171 "i18nDir": "i18n", 172 "dataDir": "data", 173 "archetypeDir": "archetypes", 174 "configDir": "config", 175 "staticDir": "static", 176 "buildDrafts": false, 177 "buildFuture": false, 178 "buildExpired": false, 179 "params": maps.Params{}, 180 "environment": hugo.EnvironmentProduction, 181 "uglyURLs": false, 182 "verbose": false, 183 "ignoreCache": false, 184 "canonifyURLs": false, 185 "relativeURLs": false, 186 "removePathAccents": false, 187 "titleCaseStyle": "AP", 188 "taxonomies": maps.Params{"tag": "tags", "category": "categories"}, 189 "permalinks": maps.Params{}, 190 "sitemap": maps.Params{"priority": -1, "filename": "sitemap.xml"}, 191 "menus": maps.Params{}, 192 "disableLiveReload": false, 193 "pluralizeListTitles": true, 194 "forceSyncStatic": false, 195 "footnoteAnchorPrefix": "", 196 "footnoteReturnLinkContents": "", 197 "newContentEditor": "", 198 "paginate": 10, 199 "paginatePath": "page", 200 "summaryLength": 70, 201 "rssLimit": -1, 202 "sectionPagesMenu": "", 203 "disablePathToLower": false, 204 "hasCJKLanguage": false, 205 "enableEmoji": false, 206 "defaultContentLanguage": "en", 207 "defaultContentLanguageInSubdir": false, 208 "enableMissingTranslationPlaceholders": false, 209 "enableGitInfo": false, 210 "ignoreFiles": make([]string, 0), 211 "disableAliases": false, 212 "debug": false, 213 "disableFastRender": false, 214 "timeout": "30s", 215 "timeZone": "", 216 "enableInlineShortcodes": false, 217 } 218 219 l.cfg.SetDefaults(defaultSettings) 220 221 return nil 222 } 223 224 func (l configLoader) normalizeCfg(cfg config.Provider) error { 225 if b, ok := cfg.Get("minifyOutput").(bool); ok && b { 226 cfg.Set("minify.minifyOutput", true) 227 } else if b, ok := cfg.Get("minify").(bool); ok && b { 228 cfg.Set("minify", maps.Params{"minifyOutput": true}) 229 } 230 231 return nil 232 } 233 234 func (l configLoader) cleanExternalConfig(cfg config.Provider) error { 235 if cfg.IsSet("internal") { 236 cfg.Set("internal", nil) 237 } 238 return nil 239 } 240 241 func (l configLoader) applyFlagsOverrides(cfg config.Provider) error { 242 for _, k := range cfg.Keys() { 243 l.cfg.Set(k, cfg.Get(k)) 244 } 245 return nil 246 } 247 248 func (l configLoader) applyOsEnvOverrides(environ []string) error { 249 if len(environ) == 0 { 250 return nil 251 } 252 253 const delim = "__env__delim" 254 255 // Extract all that start with the HUGO prefix. 256 // The delimiter is the following rune, usually "_". 257 const hugoEnvPrefix = "HUGO" 258 var hugoEnv []types.KeyValueStr 259 for _, v := range environ { 260 key, val := config.SplitEnvVar(v) 261 if strings.HasPrefix(key, hugoEnvPrefix) { 262 delimiterAndKey := strings.TrimPrefix(key, hugoEnvPrefix) 263 if len(delimiterAndKey) < 2 { 264 continue 265 } 266 // Allow delimiters to be case sensitive. 267 // It turns out there isn't that many allowed special 268 // chars in environment variables when used in Bash and similar, 269 // so variables on the form HUGOxPARAMSxFOO=bar is one option. 270 key := strings.ReplaceAll(delimiterAndKey[1:], delimiterAndKey[:1], delim) 271 key = strings.ToLower(key) 272 hugoEnv = append(hugoEnv, types.KeyValueStr{ 273 Key: key, 274 Value: val, 275 }) 276 277 } 278 } 279 280 for _, env := range hugoEnv { 281 existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, l.cfg.Get) 282 if err != nil { 283 return err 284 } 285 286 if existing != nil { 287 val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing) 288 if err != nil { 289 continue 290 } 291 292 if owner != nil { 293 owner[nestedKey] = val 294 } else { 295 l.cfg.Set(env.Key, val) 296 } 297 } else { 298 if nestedKey != "" { 299 owner[nestedKey] = env.Value 300 } else { 301 var val any 302 key := strings.ReplaceAll(env.Key, delim, ".") 303 _, ok := allDecoderSetups[key] 304 if ok { 305 // A map. 306 if v, err := metadecoders.Default.UnmarshalStringTo(env.Value, map[string]interface{}{}); err == nil { 307 val = v 308 } 309 } 310 if val == nil { 311 // A string. 312 val = l.envStringToVal(key, env.Value) 313 } 314 l.cfg.Set(key, val) 315 } 316 } 317 } 318 319 return nil 320 } 321 322 func (l *configLoader) envStringToVal(k, v string) any { 323 switch k { 324 case "disablekinds", "disablelanguages": 325 if strings.Contains(v, ",") { 326 return strings.Split(v, ",") 327 } else { 328 return strings.Fields(v) 329 } 330 default: 331 return v 332 } 333 334 } 335 336 func (l *configLoader) loadConfigMain(d ConfigSourceDescriptor) (config.LoadConfigResult, modules.ModulesConfig, error) { 337 var res config.LoadConfigResult 338 339 if d.Flags != nil { 340 if err := l.normalizeCfg(d.Flags); err != nil { 341 return res, l.ModulesConfig, err 342 } 343 } 344 345 if d.Fs == nil { 346 return res, l.ModulesConfig, errors.New("no filesystem provided") 347 } 348 349 if d.Flags != nil { 350 if err := l.applyFlagsOverrides(d.Flags); err != nil { 351 return res, l.ModulesConfig, err 352 } 353 workingDir := filepath.Clean(l.cfg.GetString("workingDir")) 354 355 l.BaseConfig = config.BaseConfig{ 356 WorkingDir: workingDir, 357 ThemesDir: paths.AbsPathify(workingDir, l.cfg.GetString("themesDir")), 358 } 359 360 } 361 362 names := d.configFilenames() 363 364 if names != nil { 365 for _, name := range names { 366 var filename string 367 filename, err := l.loadConfig(name) 368 if err == nil { 369 res.ConfigFiles = append(res.ConfigFiles, filename) 370 } else if err != ErrNoConfigFile { 371 return res, l.ModulesConfig, l.wrapFileError(err, filename) 372 } 373 } 374 } else { 375 for _, name := range config.DefaultConfigNames { 376 var filename string 377 filename, err := l.loadConfig(name) 378 if err == nil { 379 res.ConfigFiles = append(res.ConfigFiles, filename) 380 break 381 } else if err != ErrNoConfigFile { 382 return res, l.ModulesConfig, l.wrapFileError(err, filename) 383 } 384 } 385 } 386 387 if d.ConfigDir != "" { 388 absConfigDir := paths.AbsPathify(l.BaseConfig.WorkingDir, d.ConfigDir) 389 dcfg, dirnames, err := config.LoadConfigFromDir(l.Fs, absConfigDir, l.Environment) 390 if err == nil { 391 if len(dirnames) > 0 { 392 if err := l.normalizeCfg(dcfg); err != nil { 393 return res, l.ModulesConfig, err 394 } 395 if err := l.cleanExternalConfig(dcfg); err != nil { 396 return res, l.ModulesConfig, err 397 } 398 l.cfg.Set("", dcfg.Get("")) 399 res.ConfigFiles = append(res.ConfigFiles, dirnames...) 400 } 401 } else if err != ErrNoConfigFile { 402 if len(dirnames) > 0 { 403 return res, l.ModulesConfig, l.wrapFileError(err, dirnames[0]) 404 } 405 return res, l.ModulesConfig, err 406 } 407 } 408 409 res.Cfg = l.cfg 410 411 if err := l.applyDefaultConfig(); err != nil { 412 return res, l.ModulesConfig, err 413 } 414 415 // Some settings are used before we're done collecting all settings, 416 // so apply OS environment both before and after. 417 if err := l.applyOsEnvOverrides(d.Environ); err != nil { 418 return res, l.ModulesConfig, err 419 } 420 421 workingDir := filepath.Clean(l.cfg.GetString("workingDir")) 422 423 l.BaseConfig = config.BaseConfig{ 424 WorkingDir: workingDir, 425 CacheDir: l.cfg.GetString("cacheDir"), 426 ThemesDir: paths.AbsPathify(workingDir, l.cfg.GetString("themesDir")), 427 } 428 429 var err error 430 l.BaseConfig.CacheDir, err = helpers.GetCacheDir(l.Fs, l.BaseConfig.CacheDir) 431 if err != nil { 432 return res, l.ModulesConfig, err 433 } 434 435 res.BaseConfig = l.BaseConfig 436 437 l.cfg.SetDefaultMergeStrategy() 438 439 res.ConfigFiles = append(res.ConfigFiles, l.ModulesConfigFiles...) 440 441 if d.Flags != nil { 442 if err := l.applyFlagsOverrides(d.Flags); err != nil { 443 return res, l.ModulesConfig, err 444 } 445 } 446 447 if err := l.applyOsEnvOverrides(d.Environ); err != nil { 448 return res, l.ModulesConfig, err 449 } 450 451 if err = l.applyConfigAliases(); err != nil { 452 return res, l.ModulesConfig, err 453 } 454 455 return res, l.ModulesConfig, err 456 } 457 458 func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *modules.Client, error) { 459 bcfg := configs.LoadingInfo.BaseConfig 460 conf := configs.Base 461 workingDir := bcfg.WorkingDir 462 themesDir := bcfg.ThemesDir 463 464 cfg := configs.LoadingInfo.Cfg 465 466 var ignoreVendor glob.Glob 467 if s := conf.IgnoreVendorPaths; s != "" { 468 ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s)) 469 } 470 471 ex := hexec.New(conf.Security) 472 473 hook := func(m *modules.ModulesConfig) error { 474 for _, tc := range m.AllModules { 475 if len(tc.ConfigFilenames()) > 0 { 476 if tc.Watch() { 477 l.ModulesConfigFiles = append(l.ModulesConfigFiles, tc.ConfigFilenames()...) 478 } 479 480 // Merge in the theme config using the configured 481 // merge strategy. 482 cfg.Merge("", tc.Cfg().Get("")) 483 484 } 485 } 486 487 return nil 488 } 489 490 modulesClient := modules.NewClient(modules.ClientConfig{ 491 Fs: l.Fs, 492 Logger: l.Logger, 493 Exec: ex, 494 HookBeforeFinalize: hook, 495 WorkingDir: workingDir, 496 ThemesDir: themesDir, 497 Environment: l.Environment, 498 CacheDir: conf.Caches.CacheDirModules(), 499 ModuleConfig: conf.Module, 500 IgnoreVendor: ignoreVendor, 501 }) 502 503 moduleConfig, err := modulesClient.Collect() 504 505 // We want to watch these for changes and trigger rebuild on version 506 // changes etc. 507 if moduleConfig.GoModulesFilename != "" { 508 l.ModulesConfigFiles = append(l.ModulesConfigFiles, moduleConfig.GoModulesFilename) 509 } 510 511 if moduleConfig.GoWorkspaceFilename != "" { 512 l.ModulesConfigFiles = append(l.ModulesConfigFiles, moduleConfig.GoWorkspaceFilename) 513 } 514 515 return moduleConfig, modulesClient, err 516 } 517 518 func (l configLoader) loadConfig(configName string) (string, error) { 519 baseDir := l.BaseConfig.WorkingDir 520 var baseFilename string 521 if filepath.IsAbs(configName) { 522 baseFilename = configName 523 } else { 524 baseFilename = filepath.Join(baseDir, configName) 525 } 526 527 var filename string 528 if paths.ExtNoDelimiter(configName) != "" { 529 exists, _ := helpers.Exists(baseFilename, l.Fs) 530 if exists { 531 filename = baseFilename 532 } 533 } else { 534 for _, ext := range config.ValidConfigFileExtensions { 535 filenameToCheck := baseFilename + "." + ext 536 exists, _ := helpers.Exists(filenameToCheck, l.Fs) 537 if exists { 538 filename = filenameToCheck 539 break 540 } 541 } 542 } 543 544 if filename == "" { 545 return "", ErrNoConfigFile 546 } 547 548 m, err := config.FromFileToMap(l.Fs, filename) 549 if err != nil { 550 return filename, err 551 } 552 553 // Set overwrites keys of the same name, recursively. 554 l.cfg.Set("", m) 555 556 if err := l.normalizeCfg(l.cfg); err != nil { 557 return filename, err 558 } 559 560 if err := l.cleanExternalConfig(l.cfg); err != nil { 561 return filename, err 562 } 563 564 return filename, nil 565 } 566 567 func (l configLoader) deleteMergeStrategies() { 568 l.cfg.WalkParams(func(params ...maps.KeyParams) bool { 569 params[len(params)-1].Params.DeleteMergeStrategy() 570 return false 571 }) 572 } 573 574 func (l configLoader) loadModulesConfig() (modules.Config, error) { 575 modConfig, err := modules.DecodeConfig(l.cfg) 576 if err != nil { 577 return modules.Config{}, err 578 } 579 580 return modConfig, nil 581 } 582 583 func (l configLoader) wrapFileError(err error, filename string) error { 584 fe := herrors.UnwrapFileError(err) 585 if fe != nil { 586 pos := fe.Position() 587 pos.Filename = filename 588 fe.UpdatePosition(pos) 589 return err 590 } 591 return herrors.NewFileErrorFromFile(err, filename, l.Fs, nil) 592 }