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  }