|
|
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import ( "fmt" "os" "path/filepath" "strings"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/parser/metadecoders" "github.com/spf13/afero" )
var ( // See issue #8979 for context.
// Hugo has always used config.toml etc. as the default config file name.
// But hugo.toml is a more descriptive name, but we need to check for both.
DefaultConfigNames = []string{"hugo", "config"}
DefaultConfigNamesSet = make(map[string]bool)
ValidConfigFileExtensions = []string{"toml", "yaml", "yml", "json"} validConfigFileExtensionsMap map[string]bool = make(map[string]bool) )
func init() { for _, name := range DefaultConfigNames { DefaultConfigNamesSet[name] = true }
for _, ext := range ValidConfigFileExtensions { validConfigFileExtensionsMap[ext] = true } }
// IsValidConfigFilename returns whether filename is one of the supported
// config formats in Hugo.
func IsValidConfigFilename(filename string) bool { ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), ".")) return validConfigFileExtensionsMap[ext] }
func FromTOMLConfigString(config string) Provider { cfg, err := FromConfigString(config, "toml") if err != nil { panic(err) } return cfg }
// FromConfigString creates a config from the given YAML, JSON or TOML config. This is useful in tests.
func FromConfigString(config, configType string) (Provider, error) { m, err := readConfig(metadecoders.FormatFromString(configType), []byte(config)) if err != nil { return nil, err } return NewFrom(m), nil }
// FromFile loads the configuration from the given filename.
func FromFile(fs afero.Fs, filename string) (Provider, error) { m, err := loadConfigFromFile(fs, filename) if err != nil { fe := herrors.UnwrapFileError(err) if fe != nil { pos := fe.Position() pos.Filename = filename fe.UpdatePosition(pos) return nil, err } return nil, herrors.NewFileErrorFromFile(err, filename, fs, nil) } return NewFrom(m), nil }
// FromFileToMap is the same as FromFile, but it returns the config values
// as a simple map.
func FromFileToMap(fs afero.Fs, filename string) (map[string]any, error) { return loadConfigFromFile(fs, filename) }
func readConfig(format metadecoders.Format, data []byte) (map[string]any, error) { m, err := metadecoders.Default.UnmarshalToMap(data, format) if err != nil { return nil, err }
RenameKeys(m)
return m, nil }
func loadConfigFromFile(fs afero.Fs, filename string) (map[string]any, error) { m, err := metadecoders.Default.UnmarshalFileToMap(fs, filename) if err != nil { return nil, err } RenameKeys(m) return m, nil }
func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provider, []string, error) { defaultConfigDir := filepath.Join(configDir, "_default") environmentConfigDir := filepath.Join(configDir, environment) cfg := New()
var configDirs []string // Merge from least to most specific.
for _, dir := range []string{defaultConfigDir, environmentConfigDir} { if _, err := sourceFs.Stat(dir); err == nil { configDirs = append(configDirs, dir) } }
if len(configDirs) == 0 { return nil, nil, nil }
// Keep track of these so we can watch them for changes.
var dirnames []string
for _, configDir := range configDirs { err := afero.Walk(sourceFs, configDir, func(path string, fi os.FileInfo, err error) error { if fi == nil || err != nil { return nil }
if fi.IsDir() { dirnames = append(dirnames, path) return nil }
if !IsValidConfigFilename(path) { return nil }
name := paths.Filename(filepath.Base(path))
item, err := metadecoders.Default.UnmarshalFileToMap(sourceFs, path) if err != nil { // This will be used in error reporting, use the most specific value.
dirnames = []string{path} return fmt.Errorf("failed to unmarshal config for path %q: %w", path, err) }
var keyPath []string if !DefaultConfigNamesSet[name] { // Can be params.jp, menus.en etc.
name, lang := paths.FileAndExtNoDelimiter(name)
keyPath = []string{name}
if lang != "" { keyPath = []string{"languages", lang} switch name { case "menu", "menus": keyPath = append(keyPath, "menus") case "params": keyPath = append(keyPath, "params") } } }
root := item if len(keyPath) > 0 { root = make(map[string]any) m := root for i, key := range keyPath { if i >= len(keyPath)-1 { m[key] = item } else { nm := make(map[string]any) m[key] = nm m = nm } } }
// Migrate menu => menus etc.
RenameKeys(root)
// Set will overwrite keys with the same name, recursively.
cfg.Set("", root)
return nil }) if err != nil { return nil, dirnames, err }
}
return cfg, dirnames, nil }
var keyAliases maps.KeyRenamer
func init() { var err error keyAliases, err = maps.NewKeyRenamer( // Before 0.53 we used singular for "menu".
"{menu,languages/*/menu}", "menus", )
if err != nil { panic(err) } }
// RenameKeys renames config keys in m recursively according to a global Hugo
// alias definition.
func RenameKeys(m map[string]any) { keyAliases.Rename(m) }
|