github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/config/configLoader.go (about)

     1  // Copyright 2018 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 config
    15  
    16  import (
    17  	"fmt"
    18  	"os"
    19  	"path/filepath"
    20  	"strings"
    21  
    22  	"github.com/gohugoio/hugo/common/herrors"
    23  
    24  	"github.com/gohugoio/hugo/common/paths"
    25  
    26  	"github.com/gohugoio/hugo/common/maps"
    27  	"github.com/gohugoio/hugo/parser/metadecoders"
    28  	"github.com/spf13/afero"
    29  )
    30  
    31  var (
    32  	// See issue #8979 for context.
    33  	// Hugo has always used config.toml etc. as the default config file name.
    34  	// But hugo.toml is a more descriptive name, but we need to check for both.
    35  	DefaultConfigNames = []string{"hugo", "config"}
    36  
    37  	DefaultConfigNamesSet = make(map[string]bool)
    38  
    39  	ValidConfigFileExtensions                    = []string{"toml", "yaml", "yml", "json"}
    40  	validConfigFileExtensionsMap map[string]bool = make(map[string]bool)
    41  )
    42  
    43  func init() {
    44  	for _, name := range DefaultConfigNames {
    45  		DefaultConfigNamesSet[name] = true
    46  	}
    47  
    48  	for _, ext := range ValidConfigFileExtensions {
    49  		validConfigFileExtensionsMap[ext] = true
    50  	}
    51  }
    52  
    53  // IsValidConfigFilename returns whether filename is one of the supported
    54  // config formats in Hugo.
    55  func IsValidConfigFilename(filename string) bool {
    56  	ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), "."))
    57  	return validConfigFileExtensionsMap[ext]
    58  }
    59  
    60  func FromTOMLConfigString(config string) Provider {
    61  	cfg, err := FromConfigString(config, "toml")
    62  	if err != nil {
    63  		panic(err)
    64  	}
    65  	return cfg
    66  }
    67  
    68  // FromConfigString creates a config from the given YAML, JSON or TOML config. This is useful in tests.
    69  func FromConfigString(config, configType string) (Provider, error) {
    70  	m, err := readConfig(metadecoders.FormatFromString(configType), []byte(config))
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	return NewFrom(m), nil
    75  }
    76  
    77  // FromFile loads the configuration from the given filename.
    78  func FromFile(fs afero.Fs, filename string) (Provider, error) {
    79  	m, err := loadConfigFromFile(fs, filename)
    80  	if err != nil {
    81  		fe := herrors.UnwrapFileError(err)
    82  		if fe != nil {
    83  			pos := fe.Position()
    84  			pos.Filename = filename
    85  			fe.UpdatePosition(pos)
    86  			return nil, err
    87  		}
    88  		return nil, herrors.NewFileErrorFromFile(err, filename, fs, nil)
    89  	}
    90  	return NewFrom(m), nil
    91  }
    92  
    93  // FromFileToMap is the same as FromFile, but it returns the config values
    94  // as a simple map.
    95  func FromFileToMap(fs afero.Fs, filename string) (map[string]any, error) {
    96  	return loadConfigFromFile(fs, filename)
    97  }
    98  
    99  func readConfig(format metadecoders.Format, data []byte) (map[string]any, error) {
   100  	m, err := metadecoders.Default.UnmarshalToMap(data, format)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	RenameKeys(m)
   106  
   107  	return m, nil
   108  }
   109  
   110  func loadConfigFromFile(fs afero.Fs, filename string) (map[string]any, error) {
   111  	m, err := metadecoders.Default.UnmarshalFileToMap(fs, filename)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	RenameKeys(m)
   116  	return m, nil
   117  }
   118  
   119  func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provider, []string, error) {
   120  	defaultConfigDir := filepath.Join(configDir, "_default")
   121  	environmentConfigDir := filepath.Join(configDir, environment)
   122  	cfg := New()
   123  
   124  	var configDirs []string
   125  	// Merge from least to most specific.
   126  	for _, dir := range []string{defaultConfigDir, environmentConfigDir} {
   127  		if _, err := sourceFs.Stat(dir); err == nil {
   128  			configDirs = append(configDirs, dir)
   129  		}
   130  	}
   131  
   132  	if len(configDirs) == 0 {
   133  		return nil, nil, nil
   134  	}
   135  
   136  	// Keep track of these so we can watch them for changes.
   137  	var dirnames []string
   138  
   139  	for _, configDir := range configDirs {
   140  		err := afero.Walk(sourceFs, configDir, func(path string, fi os.FileInfo, err error) error {
   141  			if fi == nil || err != nil {
   142  				return nil
   143  			}
   144  
   145  			if fi.IsDir() {
   146  				dirnames = append(dirnames, path)
   147  				return nil
   148  			}
   149  
   150  			if !IsValidConfigFilename(path) {
   151  				return nil
   152  			}
   153  
   154  			name := paths.Filename(filepath.Base(path))
   155  
   156  			item, err := metadecoders.Default.UnmarshalFileToMap(sourceFs, path)
   157  			if err != nil {
   158  				// This will be used in error reporting, use the most specific value.
   159  				dirnames = []string{path}
   160  				return fmt.Errorf("failed to unmarshal config for path %q: %w", path, err)
   161  			}
   162  
   163  			var keyPath []string
   164  			if !DefaultConfigNamesSet[name] {
   165  				// Can be params.jp, menus.en etc.
   166  				name, lang := paths.FileAndExtNoDelimiter(name)
   167  
   168  				keyPath = []string{name}
   169  
   170  				if lang != "" {
   171  					keyPath = []string{"languages", lang}
   172  					switch name {
   173  					case "menu", "menus":
   174  						keyPath = append(keyPath, "menus")
   175  					case "params":
   176  						keyPath = append(keyPath, "params")
   177  					}
   178  				}
   179  			}
   180  
   181  			root := item
   182  			if len(keyPath) > 0 {
   183  				root = make(map[string]any)
   184  				m := root
   185  				for i, key := range keyPath {
   186  					if i >= len(keyPath)-1 {
   187  						m[key] = item
   188  					} else {
   189  						nm := make(map[string]any)
   190  						m[key] = nm
   191  						m = nm
   192  					}
   193  				}
   194  			}
   195  
   196  			// Migrate menu => menus etc.
   197  			RenameKeys(root)
   198  
   199  			// Set will overwrite keys with the same name, recursively.
   200  			cfg.Set("", root)
   201  
   202  			return nil
   203  		})
   204  		if err != nil {
   205  			return nil, dirnames, err
   206  		}
   207  
   208  	}
   209  
   210  	return cfg, dirnames, nil
   211  
   212  }
   213  
   214  var keyAliases maps.KeyRenamer
   215  
   216  func init() {
   217  	var err error
   218  	keyAliases, err = maps.NewKeyRenamer(
   219  		// Before 0.53 we used singular for "menu".
   220  		"{menu,languages/*/menu}", "menus",
   221  	)
   222  
   223  	if err != nil {
   224  		panic(err)
   225  	}
   226  }
   227  
   228  // RenameKeys renames config keys in m recursively according to a global Hugo
   229  // alias definition.
   230  func RenameKeys(m map[string]any) {
   231  	keyAliases.Rename(m)
   232  }