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