mirror of https://github.com/gohugoio/hugo
232 lines
5.8 KiB
Go
232 lines
5.8 KiB
Go
// 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)
|
|
}
|