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 }