hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

configLoader.go (5387B)

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