mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-28 21:21:40 -05:00
Add /config dir support
This commit adds support for a configuration directory (default `config`). The different pieces in this puzzle are: * A new `--environment` (or `-e`) flag. This can also be set with the `HUGO_ENVIRONMENT` OS environment variable. The value for `environment` defaults to `production` when running `hugo` and `development` when running `hugo server`. You can set it to any value you want (e.g. `hugo server -e "Sensible Environment"`), but as it is used to load configuration from the file system, the letter case may be important. You can get this value in your templates with `{{ hugo.Environment }}`. * A new `--configDir` flag (defaults to `config` below your project). This can also be set with `HUGO_CONFIGDIR` OS environment variable. If the `configDir` exists, the configuration files will be read and merged on top of each other from left to right; the right-most value will win on duplicates. Given the example tree below: If `environment` is `production`, the left-most `config.toml` would be the one directly below the project (this can now be omitted if you want), and then `_default/config.toml` and finally `production/config.toml`. And since these will be merged, you can just provide the environment specific configuration setting in you production config, e.g. `enableGitInfo = true`. The order within the directories will be lexical (`config.toml` and then `params.toml`). ```bash config ├── _default │ ├── config.toml │ ├── languages.toml │ ├── menus │ │ ├── menus.en.toml │ │ └── menus.zh.toml │ └── params.toml ├── development │ └── params.toml └── production ├── config.toml └── params.toml ``` Some configuration maps support the language code in the filename (e.g. `menus.en.toml`): `menus` (`menu` also works) and `params`. Also note that the only folders with "a meaning" in the above listing is the top level directories below `config`. The `menus` sub folder is just added for better organization. We use `TOML` in the example above, but Hugo also supports `JSON` and `YAML` as configuration formats. These can be mixed. Fixes #5422
This commit is contained in:
parent
256418917c
commit
7829474088
36 changed files with 904 additions and 187 deletions
|
@ -249,6 +249,8 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
|
|||
sourceFs = c.DepsCfg.Fs.Source
|
||||
}
|
||||
|
||||
environment := c.h.getEnvironment(running)
|
||||
|
||||
doWithConfig := func(cfg config.Provider) error {
|
||||
|
||||
if c.ftch != nil {
|
||||
|
@ -256,7 +258,7 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
|
|||
}
|
||||
|
||||
cfg.Set("workingDir", dir)
|
||||
|
||||
cfg.Set("environment", environment)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -269,8 +271,18 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
|
|||
return err
|
||||
}
|
||||
|
||||
configPath := c.h.source
|
||||
if configPath == "" {
|
||||
configPath = dir
|
||||
}
|
||||
config, configFiles, err := hugolib.LoadConfig(
|
||||
hugolib.ConfigSourceDescriptor{Fs: sourceFs, Path: c.h.source, WorkingDir: dir, Filename: c.h.cfgFile},
|
||||
hugolib.ConfigSourceDescriptor{
|
||||
Fs: sourceFs,
|
||||
Path: configPath,
|
||||
WorkingDir: dir,
|
||||
Filename: c.h.cfgFile,
|
||||
AbsConfigDir: c.h.getConfigDir(dir),
|
||||
Environment: environment},
|
||||
doWithCommandeer,
|
||||
doWithConfig)
|
||||
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib/paths"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
|
@ -159,6 +164,7 @@ Complete documentation is available at http://gohugo.io/.`,
|
|||
})
|
||||
|
||||
cc.cmd.PersistentFlags().StringVar(&cc.cfgFile, "config", "", "config file (default is path/config.yaml|json|toml)")
|
||||
cc.cmd.PersistentFlags().StringVar(&cc.cfgDir, "configDir", "config", "config dir")
|
||||
cc.cmd.PersistentFlags().BoolVar(&cc.quiet, "quiet", false, "build in quiet mode")
|
||||
|
||||
// Set bash-completion
|
||||
|
@ -185,8 +191,9 @@ Complete documentation is available at http://gohugo.io/.`,
|
|||
}
|
||||
|
||||
type hugoBuilderCommon struct {
|
||||
source string
|
||||
baseURL string
|
||||
source string
|
||||
baseURL string
|
||||
environment string
|
||||
|
||||
buildWatch bool
|
||||
|
||||
|
@ -200,15 +207,45 @@ type hugoBuilderCommon struct {
|
|||
quiet bool
|
||||
|
||||
cfgFile string
|
||||
cfgDir string
|
||||
logFile string
|
||||
}
|
||||
|
||||
func (cc *hugoBuilderCommon) getConfigDir(baseDir string) string {
|
||||
if cc.cfgDir != "" {
|
||||
return paths.AbsPathify(baseDir, cc.cfgDir)
|
||||
}
|
||||
|
||||
if v, found := os.LookupEnv("HUGO_CONFIGDIR"); found {
|
||||
return paths.AbsPathify(baseDir, v)
|
||||
}
|
||||
|
||||
return paths.AbsPathify(baseDir, "config")
|
||||
}
|
||||
|
||||
func (cc *hugoBuilderCommon) getEnvironment(isServer bool) string {
|
||||
if cc.environment != "" {
|
||||
return cc.environment
|
||||
}
|
||||
|
||||
if v, found := os.LookupEnv("HUGO_ENVIRONMENT"); found {
|
||||
return v
|
||||
}
|
||||
|
||||
if isServer {
|
||||
return hugo.EnvironmentDevelopment
|
||||
}
|
||||
|
||||
return hugo.EnvironmentProduction
|
||||
}
|
||||
|
||||
func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().Bool("cleanDestinationDir", false, "remove files from destination not found in static directories")
|
||||
cmd.Flags().BoolP("buildDrafts", "D", false, "include content marked as draft")
|
||||
cmd.Flags().BoolP("buildFuture", "F", false, "include content with publishdate in the future")
|
||||
cmd.Flags().BoolP("buildExpired", "E", false, "include expired content")
|
||||
cmd.Flags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
|
||||
cmd.Flags().StringVarP(&cc.environment, "environment", "e", "", "build environment")
|
||||
cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory")
|
||||
cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
|
||||
cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/")
|
||||
|
|
|
@ -56,8 +56,11 @@ func TestCommandsPersistentFlags(t *testing.T) {
|
|||
check func(command []cmder)
|
||||
}{{[]string{"server",
|
||||
"--config=myconfig.toml",
|
||||
"--configDir=myconfigdir",
|
||||
"--contentDir=mycontent",
|
||||
"--disableKinds=page,home",
|
||||
"--environment=testing",
|
||||
"--configDir=myconfigdir",
|
||||
"--layoutDir=mylayouts",
|
||||
"--theme=mytheme",
|
||||
"--gc",
|
||||
|
@ -78,6 +81,7 @@ func TestCommandsPersistentFlags(t *testing.T) {
|
|||
if b, ok := command.(commandsBuilderGetter); ok {
|
||||
v := b.getCommandsBuilder().hugoBuilderCommon
|
||||
assert.Equal("myconfig.toml", v.cfgFile)
|
||||
assert.Equal("myconfigdir", v.cfgDir)
|
||||
assert.Equal("mysource", v.source)
|
||||
assert.Equal("https://example.com/b/", v.baseURL)
|
||||
}
|
||||
|
@ -93,6 +97,7 @@ func TestCommandsPersistentFlags(t *testing.T) {
|
|||
assert.True(sc.noHTTPCache)
|
||||
assert.True(sc.renderToDisk)
|
||||
assert.Equal(1366, sc.serverPort)
|
||||
assert.Equal("testing", sc.environment)
|
||||
|
||||
cfg := viper.New()
|
||||
sc.flagsToConfig(cfg)
|
||||
|
@ -233,6 +238,7 @@ Single: {{ .Title }}
|
|||
writeFile(t, filepath.Join(d, "layouts", "_default", "list.html"), `
|
||||
|
||||
List: {{ .Title }}
|
||||
Environment: {{ hugo.Environment }}
|
||||
|
||||
`)
|
||||
|
||||
|
|
|
@ -718,8 +718,8 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
|
|||
// Identifies changes to config (config.toml) files.
|
||||
configSet := make(map[string]bool)
|
||||
|
||||
c.logger.FEEDBACK.Println("Watching for config changes in", strings.Join(c.configFiles, ", "))
|
||||
for _, configFile := range c.configFiles {
|
||||
c.logger.FEEDBACK.Println("Watching for config changes in", configFile)
|
||||
watcher.Add(configFile)
|
||||
configSet[configFile] = true
|
||||
}
|
||||
|
@ -750,7 +750,17 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
|
|||
configSet map[string]bool) {
|
||||
|
||||
for _, ev := range evs {
|
||||
if configSet[ev.Name] {
|
||||
isConfig := configSet[ev.Name]
|
||||
if !isConfig {
|
||||
// It may be one of the /config folders
|
||||
dirname := filepath.Dir(ev.Name)
|
||||
if dirname != "." && configSet[dirname] {
|
||||
isConfig = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if isConfig {
|
||||
if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
|
||||
continue
|
||||
}
|
||||
|
@ -766,7 +776,7 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
|
|||
}
|
||||
}
|
||||
}
|
||||
// Config file changed. Need full rebuild.
|
||||
// Config file(s) changed. Need full rebuild.
|
||||
c.fullRebuild()
|
||||
break
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ import (
|
|||
"github.com/gohugoio/hugo/tpl"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -301,6 +300,8 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
|
|||
|
||||
absPublishDir := f.c.hugo.PathSpec.AbsPathify(publishDir)
|
||||
|
||||
jww.FEEDBACK.Printf("Environment: %q", f.c.hugo.Deps.Site.Hugo().Environment)
|
||||
|
||||
if i == 0 {
|
||||
if f.s.renderToDisk {
|
||||
jww.FEEDBACK.Println("Serving pages from " + absPublishDir)
|
||||
|
|
|
@ -68,6 +68,7 @@ func TestServer(t *testing.T) {
|
|||
homeContent := helpers.ReaderToString(resp.Body)
|
||||
|
||||
assert.Contains(homeContent, "List: Hugo Commands")
|
||||
assert.Contains(homeContent, "Environment: development")
|
||||
|
||||
// Stop the server.
|
||||
stop <- true
|
||||
|
|
|
@ -17,10 +17,10 @@ package herrors
|
|||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
@ -172,12 +172,16 @@ func chromaLexerFromType(fileType string) string {
|
|||
return fileType
|
||||
}
|
||||
|
||||
func extNoDelimiter(filename string) string {
|
||||
return strings.TrimPrefix(".", filepath.Ext(filename))
|
||||
}
|
||||
|
||||
func chromaLexerFromFilename(filename string) string {
|
||||
if strings.Contains(filename, "layouts") {
|
||||
return "go-html-template"
|
||||
}
|
||||
|
||||
ext := helpers.ExtNoDelimiter(filename)
|
||||
ext := extNoDelimiter(filename)
|
||||
return chromaLexerFromType(ext)
|
||||
}
|
||||
|
||||
|
|
|
@ -18,28 +18,50 @@ import (
|
|||
"html/template"
|
||||
)
|
||||
|
||||
var (
|
||||
// CommitHash contains the current Git revision. Use make to build to make
|
||||
// sure this gets set.
|
||||
CommitHash string
|
||||
const (
|
||||
EnvironmentDevelopment = "development"
|
||||
EnvironmentProduction = "production"
|
||||
)
|
||||
|
||||
// BuildDate contains the date of the current build.
|
||||
BuildDate string
|
||||
var (
|
||||
// commitHash contains the current Git revision. Use make to build to make
|
||||
// sure this gets set.
|
||||
commitHash string
|
||||
|
||||
// buildDate contains the date of the current build.
|
||||
buildDate string
|
||||
)
|
||||
|
||||
// Info contains information about the current Hugo environment
|
||||
type Info struct {
|
||||
Version VersionString
|
||||
Generator template.HTML
|
||||
CommitHash string
|
||||
BuildDate string
|
||||
|
||||
// The build environment.
|
||||
// Defaults are "production" (hugo) and "development" (hugo server).
|
||||
// This can also be set by the user.
|
||||
// It can be any string, but it will be all lower case.
|
||||
Environment string
|
||||
}
|
||||
|
||||
func NewInfo() Info {
|
||||
// Version returns the current version as a comparable version string.
|
||||
func (i Info) Version() VersionString {
|
||||
return CurrentVersion.Version()
|
||||
}
|
||||
|
||||
// Generator a Hugo meta generator HTML tag.
|
||||
func (i Info) Generator() template.HTML {
|
||||
return template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s" />`, CurrentVersion.String()))
|
||||
}
|
||||
|
||||
// NewInfo creates a new Hugo Info object.
|
||||
func NewInfo(environment string) Info {
|
||||
if environment == "" {
|
||||
environment = EnvironmentProduction
|
||||
}
|
||||
return Info{
|
||||
Version: CurrentVersion.Version(),
|
||||
CommitHash: CommitHash,
|
||||
BuildDate: BuildDate,
|
||||
Generator: template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s" />`, CurrentVersion.String())),
|
||||
CommitHash: commitHash,
|
||||
BuildDate: buildDate,
|
||||
Environment: environment,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,13 @@ import (
|
|||
func TestHugoInfo(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
|
||||
hugoInfo := NewInfo()
|
||||
hugoInfo := NewInfo("")
|
||||
|
||||
assert.Equal(CurrentVersion.Version(), hugoInfo.Version)
|
||||
assert.IsType(VersionString(""), hugoInfo.Version)
|
||||
assert.Equal(CommitHash, hugoInfo.CommitHash)
|
||||
assert.Equal(BuildDate, hugoInfo.BuildDate)
|
||||
assert.Contains(hugoInfo.Generator, fmt.Sprintf("Hugo %s", hugoInfo.Version))
|
||||
assert.Equal(CurrentVersion.Version(), hugoInfo.Version())
|
||||
assert.IsType(VersionString(""), hugoInfo.Version())
|
||||
assert.Equal(commitHash, hugoInfo.CommitHash)
|
||||
assert.Equal(buildDate, hugoInfo.BuildDate)
|
||||
assert.Equal("production", hugoInfo.Environment)
|
||||
assert.Contains(hugoInfo.Generator(), fmt.Sprintf("Hugo %s", hugoInfo.Version()))
|
||||
|
||||
}
|
||||
|
|
|
@ -130,8 +130,8 @@ func BuildVersionString() string {
|
|||
program := "Hugo Static Site Generator"
|
||||
|
||||
version := "v" + CurrentVersion.String()
|
||||
if CommitHash != "" {
|
||||
version += "-" + strings.ToUpper(CommitHash)
|
||||
if commitHash != "" {
|
||||
version += "-" + strings.ToUpper(commitHash)
|
||||
}
|
||||
if isExtended {
|
||||
version += "/extended"
|
||||
|
@ -139,14 +139,12 @@ func BuildVersionString() string {
|
|||
|
||||
osArch := runtime.GOOS + "/" + runtime.GOARCH
|
||||
|
||||
var buildDate string
|
||||
if BuildDate != "" {
|
||||
buildDate = BuildDate
|
||||
} else {
|
||||
buildDate = "unknown"
|
||||
date := buildDate
|
||||
if date == "" {
|
||||
date = "unknown"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %s %s BuildDate: %s", program, version, osArch, buildDate)
|
||||
return fmt.Sprintf("%s %s %s BuildDate: %s", program, version, osArch, date)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ package maps
|
|||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
|
@ -42,3 +44,73 @@ func ToLower(m map[string]interface{}) {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
type keyRename struct {
|
||||
pattern glob.Glob
|
||||
newKey string
|
||||
}
|
||||
|
||||
// KeyRenamer supports renaming of keys in a map.
|
||||
type KeyRenamer struct {
|
||||
renames []keyRename
|
||||
}
|
||||
|
||||
// NewKeyRenamer creates a new KeyRenamer given a list of pattern and new key
|
||||
// value pairs.
|
||||
func NewKeyRenamer(patternKeys ...string) (KeyRenamer, error) {
|
||||
var renames []keyRename
|
||||
for i := 0; i < len(patternKeys); i += 2 {
|
||||
g, err := glob.Compile(strings.ToLower(patternKeys[i]), '/')
|
||||
if err != nil {
|
||||
return KeyRenamer{}, err
|
||||
}
|
||||
renames = append(renames, keyRename{pattern: g, newKey: patternKeys[i+1]})
|
||||
}
|
||||
|
||||
return KeyRenamer{renames: renames}, nil
|
||||
}
|
||||
|
||||
func (r KeyRenamer) getNewKey(keyPath string) string {
|
||||
for _, matcher := range r.renames {
|
||||
if matcher.pattern.Match(keyPath) {
|
||||
return matcher.newKey
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Rename renames the keys in the given map according
|
||||
// to the patterns in the current KeyRenamer.
|
||||
func (r KeyRenamer) Rename(m map[string]interface{}) {
|
||||
r.renamePath("", m)
|
||||
}
|
||||
|
||||
func (KeyRenamer) keyPath(k1, k2 string) string {
|
||||
k1, k2 = strings.ToLower(k1), strings.ToLower(k2)
|
||||
if k1 == "" {
|
||||
return k2
|
||||
} else {
|
||||
return k1 + "/" + k2
|
||||
}
|
||||
}
|
||||
|
||||
func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]interface{}) {
|
||||
for key, val := range m {
|
||||
keyPath := r.keyPath(parentKeyPath, key)
|
||||
switch val.(type) {
|
||||
case map[interface{}]interface{}:
|
||||
val = cast.ToStringMap(val)
|
||||
r.renamePath(keyPath, val.(map[string]interface{}))
|
||||
case map[string]interface{}:
|
||||
r.renamePath(keyPath, val.(map[string]interface{}))
|
||||
}
|
||||
|
||||
newKey := r.getNewKey(keyPath)
|
||||
|
||||
if newKey != "" {
|
||||
delete(m, key)
|
||||
m[newKey] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ package maps
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestToLower(t *testing.T) {
|
||||
|
@ -70,3 +72,52 @@ func TestToLower(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenameKeys(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
|
||||
m := map[string]interface{}{
|
||||
"a": 32,
|
||||
"ren1": "m1",
|
||||
"ren2": "m1_2",
|
||||
"sub": map[string]interface{}{
|
||||
"subsub": map[string]interface{}{
|
||||
"REN1": "m2",
|
||||
"ren2": "m2_2",
|
||||
},
|
||||
},
|
||||
"no": map[string]interface{}{
|
||||
"ren1": "m2",
|
||||
"ren2": "m2_2",
|
||||
},
|
||||
}
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"a": 32,
|
||||
"new1": "m1",
|
||||
"new2": "m1_2",
|
||||
"sub": map[string]interface{}{
|
||||
"subsub": map[string]interface{}{
|
||||
"new1": "m2",
|
||||
"ren2": "m2_2",
|
||||
},
|
||||
},
|
||||
"no": map[string]interface{}{
|
||||
"ren1": "m2",
|
||||
"ren2": "m2_2",
|
||||
},
|
||||
}
|
||||
|
||||
renamer, err := NewKeyRenamer(
|
||||
"{ren1,sub/*/ren1}", "new1",
|
||||
"{Ren2,sub/ren2}", "new2",
|
||||
)
|
||||
assert.NoError(err)
|
||||
|
||||
renamer.Rename(m)
|
||||
|
||||
if !reflect.DeepEqual(expected, m) {
|
||||
t.Errorf("Expected\n%#v, got\n%#v\n", expected, m)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
106
config/configLoader.go
Normal file
106
config/configLoader.go
Normal file
|
@ -0,0 +1,106 @@
|
|||
// 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 (
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// FromConfigString creates a config from the given YAML, JSON or TOML config. This is useful in tests.
|
||||
func FromConfigString(config, configType string) (Provider, error) {
|
||||
v := newViper()
|
||||
m, err := readConfig(metadecoders.FormatFromString(configType), []byte(config))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v.MergeConfigMap(m)
|
||||
|
||||
return v, 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := newViper()
|
||||
|
||||
err = v.MergeConfigMap(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v, 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]interface{}, error) {
|
||||
return loadConfigFromFile(fs, filename)
|
||||
}
|
||||
|
||||
func readConfig(format metadecoders.Format, data []byte) (map[string]interface{}, error) {
|
||||
m, err := metadecoders.UnmarshalToMap(data, format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
RenameKeys(m)
|
||||
|
||||
return m, nil
|
||||
|
||||
}
|
||||
|
||||
func loadConfigFromFile(fs afero.Fs, filename string) (map[string]interface{}, error) {
|
||||
m, err := metadecoders.UnmarshalFileToMap(fs, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
RenameKeys(m)
|
||||
return m, 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]interface{}) {
|
||||
keyAliases.Rename(m)
|
||||
}
|
||||
|
||||
func newViper() *viper.Viper {
|
||||
v := viper.New()
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvPrefix("hugo")
|
||||
|
||||
return v
|
||||
}
|
|
@ -14,11 +14,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Provider provides the configuration settings for Hugo.
|
||||
|
@ -34,16 +30,6 @@ type Provider interface {
|
|||
IsSet(key string) bool
|
||||
}
|
||||
|
||||
// FromConfigString creates a config from the given YAML, JSON or TOML config. This is useful in tests.
|
||||
func FromConfigString(config, configType string) (Provider, error) {
|
||||
v := viper.New()
|
||||
v.SetConfigType(configType)
|
||||
if err := v.ReadConfig(strings.NewReader(config)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// GetStringSlicePreserveString returns a string slice from the given config and key.
|
||||
// It differs from the GetStringSlice method in that if the config value is a string,
|
||||
// we do not attempt to split it into fields.
|
||||
|
|
8
go.mod
8
go.mod
|
@ -33,7 +33,7 @@ require (
|
|||
github.com/mattn/go-runewidth v0.0.3 // indirect
|
||||
github.com/miekg/mmark v1.3.6
|
||||
github.com/mitchellh/hashstructure v1.0.0
|
||||
github.com/mitchellh/mapstructure v1.0.0
|
||||
github.com/mitchellh/mapstructure v1.1.2
|
||||
github.com/muesli/smartcrop v0.0.0-20180228075044-f6ebaa786a12
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/nicksnyder/go-i18n v1.10.0
|
||||
|
@ -50,16 +50,18 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.0.1-0.20181028145347-94f6ae3ed3bc
|
||||
github.com/spf13/nitro v0.0.0-20131003134307-24d7ef30a12d
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/spf13/viper v1.2.0
|
||||
github.com/spf13/viper v1.3.1
|
||||
github.com/stretchr/testify v1.2.3-0.20181014000028-04af85275a5c
|
||||
github.com/tdewolff/minify/v2 v2.3.7
|
||||
github.com/ugorji/go/codec v0.0.0-20181206144755-e72634d4d386 // indirect
|
||||
github.com/yosssi/ace v0.0.5
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd // indirect
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
|
||||
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e // indirect
|
||||
golang.org/x/text v0.3.0
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
)
|
||||
|
||||
exclude github.com/chaseadamsio/goorgeous v2.0.0+incompatible
|
||||
|
|
32
go.sum
32
go.sum
|
@ -14,6 +14,7 @@ github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VEN
|
|||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/bep/debounce v1.1.0 h1:6ocXeW2iZ/7vAzgXz82J00tYxncMiEEBExPftTtOQzk=
|
||||
github.com/bep/debounce v1.1.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/bep/gitmap v1.0.0 h1:cTTZwq7vpGuhwefKCBDV9UrHnZAPVJTvoWobimrqkUc=
|
||||
|
@ -24,6 +25,9 @@ github.com/chaseadamsio/goorgeous v1.1.0 h1:J9UrYDhzucUMHXsCKG+kICvpR5dT1cqZdVFT
|
|||
github.com/chaseadamsio/goorgeous v1.1.0/go.mod h1:6QaC0vFoKWYDth94dHFNgRT2YkT5FHdQp/Yx15aAAi0=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.8 h1:DwoNytLphI8hzS2Af4D0dfaEaiSq2bN05mEm4R6vf8M=
|
||||
github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||
|
@ -79,8 +83,8 @@ github.com/miekg/mmark v1.3.6 h1:t47x5vThdwgLJzofNsbsAl7gmIiJ7kbDQN5BxwBmwvY=
|
|||
github.com/miekg/mmark v1.3.6/go.mod h1:w7r9mkTvpS55jlfyn22qJ618itLryxXBhA7Jp3FIlkw=
|
||||
github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y=
|
||||
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
||||
github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
|
||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/muesli/smartcrop v0.0.0-20180228075044-f6ebaa786a12 h1:l0X/8IDy2UoK+oXcQFMRSIOcyuYb5iEPytPGplnM41Y=
|
||||
github.com/muesli/smartcrop v0.0.0-20180228075044-f6ebaa786a12/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
|
@ -105,8 +109,6 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:
|
|||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
|
||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
|
@ -119,12 +121,12 @@ github.com/spf13/jwalterweatherman v1.0.1-0.20181028145347-94f6ae3ed3bc h1:Iwxhe
|
|||
github.com/spf13/jwalterweatherman v1.0.1-0.20181028145347-94f6ae3ed3bc/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/nitro v0.0.0-20131003134307-24d7ef30a12d h1:ihvj2nmx8eqWjlgNgdW6h0DyGJuq5GiwHadJkG0wXtQ=
|
||||
github.com/spf13/nitro v0.0.0-20131003134307-24d7ef30a12d/go.mod h1:jU8A+8xL+6n1OX4XaZtCj4B3mIa64tULUsD6YegdpFo=
|
||||
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
|
||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.2.0 h1:M4Rzxlu+RgU4pyBRKhKaVN1VeYOm8h2jgyXnAseDgCc=
|
||||
github.com/spf13/viper v1.2.0/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
|
||||
github.com/spf13/viper v1.3.0 h1:cO6QlTTeK9RQDhFAbGLV5e3fHXbRpin/Gi8qfL4rdLk=
|
||||
github.com/spf13/viper v1.3.0/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38=
|
||||
github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.3-0.20181014000028-04af85275a5c h1:03OmljzZYsezlgAfa+f/cY8E8XXPiFh5bgANMhUlDI4=
|
||||
|
@ -135,24 +137,30 @@ github.com/tdewolff/parse/v2 v2.3.5 h1:/uS8JfhwVJsNkEh769GM5ENv6L9LOh2Z9uW3tCdlh
|
|||
github.com/tdewolff/parse/v2 v2.3.5/go.mod h1:HansaqmN4I/U7L6/tUp0NcwT2tFO0F4EAWYGSDzkYNk=
|
||||
github.com/tdewolff/test v1.0.0 h1:jOwzqCXr5ePXEPGJaq2ivoR6HOCi+D5TPfpoyg8yvmU=
|
||||
github.com/tdewolff/test v1.0.0/go.mod h1:DiQUlutnqlEvdvhSn2LPGy4TFwRauAaYDsL+683RNX4=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v0.0.0-20181206144755-e72634d4d386/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/wellington/go-libsass v0.9.3-0.20181113175235-c63644206701 h1:9vG9vvVNVupO4Y7uwFkRgIMNe9rdaJMCINDe8vhAhLo=
|
||||
github.com/wellington/go-libsass v0.9.3-0.20181113175235-c63644206701/go.mod h1:mxgxgam0N0E+NAUMHLcu20Ccfc3mVpDkyrLDayqfiTs=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
|
||||
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg=
|
||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc h1:SdCq5U4J+PpbSDIl9bM0V1e1Ug1jsnBkAFvTs1htn7U=
|
||||
golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo=
|
||||
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -2,7 +2,7 @@ project_name: hugo_extended
|
|||
builds:
|
||||
- binary: hugo
|
||||
ldflags:
|
||||
- -s -w -X github.com/gohugoio/hugo/common/hugo.BuildDate={{.Date}} -X github.com/gohugoio/hugo/common/hugo.CommitHash={{ .ShortCommit }}
|
||||
- -s -w -X github.com/gohugoio/hugo/common/hugo.buildDate={{.Date}} -X github.com/gohugoio/hugo/common/hugo.commitHash={{ .ShortCommit }}
|
||||
- "-extldflags '-static'"
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
|
|
|
@ -2,7 +2,7 @@ project_name: hugo
|
|||
build:
|
||||
main: main.go
|
||||
binary: hugo
|
||||
ldflags: -s -w -X github.com/gohugoio/hugo/common/hugo.BuildDate={{.Date}} -X github.com/gohugoio/hugo/common/hugo.CommitHash={{ .ShortCommit }}
|
||||
ldflags: -s -w -X github.com/gohugoio/hugo/common/hugo.buildDate={{.Date}} -X github.com/gohugoio/hugo/common/hugo.commitHash={{ .ShortCommit }}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
|
|
|
@ -274,6 +274,13 @@ func FileAndExt(in string) (string, string) {
|
|||
return fileAndExt(in, fpb)
|
||||
}
|
||||
|
||||
// FileAndExtNoDelimiter takes a path and returns the file and extension separated,
|
||||
// the extension excluding the delmiter, e.g "md".
|
||||
func FileAndExtNoDelimiter(in string) (string, string) {
|
||||
file, ext := fileAndExt(in, fpb)
|
||||
return file, strings.TrimPrefix(ext, ".")
|
||||
}
|
||||
|
||||
// Filename takes a path, strips out the extension,
|
||||
// and returns the name of the file.
|
||||
func Filename(in string) (name string) {
|
||||
|
@ -400,6 +407,8 @@ func ExtractRootPaths(paths []string) []string {
|
|||
|
||||
}
|
||||
|
||||
var numInPathRe = regexp.MustCompile("\\.(\\d+)\\.")
|
||||
|
||||
// FindCWD returns the current working directory from where the Hugo
|
||||
// executable is run.
|
||||
func FindCWD() (string, error) {
|
||||
|
|
|
@ -39,7 +39,7 @@ func (t testSite) Language() *langs.Language {
|
|||
// NewTestHugoSite creates a new minimal test site.
|
||||
func NewTestHugoSite() hugo.Site {
|
||||
return testSite{
|
||||
h: hugo.NewInfo(),
|
||||
h: hugo.NewInfo(hugo.EnvironmentProduction),
|
||||
l: langs.NewLanguage("en", newTestConfig()),
|
||||
}
|
||||
}
|
||||
|
|
59
htesting/testdata_builder.go
Normal file
59
htesting/testdata_builder.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
// 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 htesting
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
type testFile struct {
|
||||
name string
|
||||
content string
|
||||
}
|
||||
|
||||
type testdataBuilder struct {
|
||||
t testing.TB
|
||||
fs afero.Fs
|
||||
workingDir string
|
||||
|
||||
files []testFile
|
||||
}
|
||||
|
||||
func NewTestdataBuilder(fs afero.Fs, workingDir string, t testing.TB) *testdataBuilder {
|
||||
workingDir = filepath.Clean(workingDir)
|
||||
return &testdataBuilder{fs: fs, workingDir: workingDir, t: t}
|
||||
}
|
||||
|
||||
func (b *testdataBuilder) Add(filename, content string) *testdataBuilder {
|
||||
b.files = append(b.files, testFile{name: filename, content: content})
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *testdataBuilder) Build() *testdataBuilder {
|
||||
for _, f := range b.files {
|
||||
if err := afero.WriteFile(b.fs, filepath.Join(b.workingDir, f.name), []byte(f.content), 0666); err != nil {
|
||||
b.t.Fatalf("failed to add %q: %s", f.name, err)
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b testdataBuilder) WithWorkingDir(dir string) *testdataBuilder {
|
||||
b.workingDir = filepath.Clean(dir)
|
||||
b.files = make([]testFile, 0)
|
||||
return &b
|
||||
}
|
|
@ -14,14 +14,19 @@
|
|||
package hugolib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugolib/paths"
|
||||
"github.com/pkg/errors"
|
||||
_errors "github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
|
@ -65,96 +70,84 @@ func loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err error) {
|
|||
type ConfigSourceDescriptor struct {
|
||||
Fs afero.Fs
|
||||
|
||||
// Full path to the config file to use, i.e. /my/project/config.toml
|
||||
// Path to the config file to use, e.g. /my/project/config.toml
|
||||
Filename string
|
||||
|
||||
// The path to the directory to look for configuration. Is used if Filename is not
|
||||
// set.
|
||||
// set or if it is set to a relative filename.
|
||||
Path string
|
||||
|
||||
// The project's working dir. Is used to look for additional theme config.
|
||||
WorkingDir string
|
||||
|
||||
// The (optional) directory for additional configuration files.
|
||||
AbsConfigDir string
|
||||
|
||||
// production, development
|
||||
Environment string
|
||||
}
|
||||
|
||||
func (d ConfigSourceDescriptor) configFilenames() []string {
|
||||
if d.Filename == "" {
|
||||
return []string{"config"}
|
||||
}
|
||||
return strings.Split(d.Filename, ",")
|
||||
}
|
||||
|
||||
func (d ConfigSourceDescriptor) configFileDir() string {
|
||||
if d.Path != "" {
|
||||
return d.Path
|
||||
}
|
||||
return d.WorkingDir
|
||||
}
|
||||
|
||||
// LoadConfigDefault is a convenience method to load the default "config.toml" config.
|
||||
func LoadConfigDefault(fs afero.Fs) (*viper.Viper, error) {
|
||||
v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs, Filename: "config.toml"})
|
||||
return v, err
|
||||
}
|
||||
|
||||
var ErrNoConfigFile = errors.New("Unable to locate Config file. Perhaps you need to create a new site.\n Run `hugo help new` for details.\n")
|
||||
var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\n Run `hugo help new` for details.\n")
|
||||
|
||||
// LoadConfig loads Hugo configuration into a new Viper and then adds
|
||||
// a set of defaults.
|
||||
func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provider) error) (*viper.Viper, []string, error) {
|
||||
if d.Environment == "" {
|
||||
d.Environment = hugo.EnvironmentProduction
|
||||
}
|
||||
|
||||
var configFiles []string
|
||||
|
||||
fs := d.Fs
|
||||
v := viper.New()
|
||||
v.SetFs(fs)
|
||||
l := configLoader{ConfigSourceDescriptor: d}
|
||||
|
||||
if d.Path == "" {
|
||||
d.Path = "."
|
||||
}
|
||||
|
||||
configFilenames := d.configFilenames()
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvPrefix("hugo")
|
||||
v.SetConfigFile(configFilenames[0])
|
||||
v.AddConfigPath(d.Path)
|
||||
|
||||
applyFileContext := func(filename string, err error) error {
|
||||
err, _ = herrors.WithFileContextForFile(
|
||||
err,
|
||||
filename,
|
||||
filename,
|
||||
fs,
|
||||
herrors.SimpleLineMatcher)
|
||||
var cerr error
|
||||
|
||||
return err
|
||||
for _, name := range d.configFilenames() {
|
||||
var filename string
|
||||
if filename, cerr = l.loadConfig(name, v); cerr != nil && cerr != ErrNoConfigFile {
|
||||
return nil, nil, cerr
|
||||
}
|
||||
configFiles = append(configFiles, filename)
|
||||
}
|
||||
|
||||
var configFileErr error
|
||||
|
||||
err := v.ReadInConfig()
|
||||
if err != nil {
|
||||
if _, ok := err.(viper.ConfigParseError); ok {
|
||||
return nil, configFiles, applyFileContext(v.ConfigFileUsed(), err)
|
||||
if d.AbsConfigDir != "" {
|
||||
dirnames, err := l.loadConfigFromConfigDir(v)
|
||||
if err == nil {
|
||||
configFiles = append(configFiles, dirnames...)
|
||||
}
|
||||
configFileErr = ErrNoConfigFile
|
||||
}
|
||||
|
||||
if configFileErr == nil {
|
||||
|
||||
if cf := v.ConfigFileUsed(); cf != "" {
|
||||
configFiles = append(configFiles, cf)
|
||||
}
|
||||
|
||||
for _, configFile := range configFilenames[1:] {
|
||||
var r io.Reader
|
||||
var err error
|
||||
if r, err = fs.Open(configFile); err != nil {
|
||||
return nil, configFiles, fmt.Errorf("Unable to open Config file.\n (%s)\n", err)
|
||||
}
|
||||
if err = v.MergeConfig(r); err != nil {
|
||||
return nil, configFiles, applyFileContext(configFile, err)
|
||||
}
|
||||
configFiles = append(configFiles, configFile)
|
||||
}
|
||||
|
||||
cerr = err
|
||||
}
|
||||
|
||||
if err := loadDefaultSettingsFor(v); err != nil {
|
||||
return v, configFiles, err
|
||||
}
|
||||
|
||||
if configFileErr == nil {
|
||||
|
||||
themeConfigFiles, err := loadThemeConfig(d, v)
|
||||
if cerr == nil {
|
||||
themeConfigFiles, err := l.loadThemeConfig(v)
|
||||
if err != nil {
|
||||
return v, configFiles, err
|
||||
}
|
||||
|
@ -176,10 +169,181 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid
|
|||
return v, configFiles, err
|
||||
}
|
||||
|
||||
return v, configFiles, configFileErr
|
||||
return v, configFiles, cerr
|
||||
|
||||
}
|
||||
|
||||
type configLoader struct {
|
||||
ConfigSourceDescriptor
|
||||
}
|
||||
|
||||
func (l configLoader) wrapFileInfoError(err error, fi os.FileInfo) error {
|
||||
rfi, ok := fi.(hugofs.RealFilenameInfo)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
return l.wrapFileError(err, rfi.RealFilename())
|
||||
}
|
||||
|
||||
func (l configLoader) loadConfig(configName string, v *viper.Viper) (string, error) {
|
||||
baseDir := l.configFileDir()
|
||||
var baseFilename string
|
||||
if filepath.IsAbs(configName) {
|
||||
baseFilename = configName
|
||||
} else {
|
||||
baseFilename = filepath.Join(baseDir, configName)
|
||||
}
|
||||
|
||||
var filename string
|
||||
fileExt := helpers.ExtNoDelimiter(configName)
|
||||
if fileExt != "" {
|
||||
exists, _ := helpers.Exists(baseFilename, l.Fs)
|
||||
if exists {
|
||||
filename = baseFilename
|
||||
}
|
||||
} else {
|
||||
for _, ext := range []string{"toml", "yaml", "yml", "json"} {
|
||||
filenameToCheck := baseFilename + "." + ext
|
||||
exists, _ := helpers.Exists(filenameToCheck, l.Fs)
|
||||
if exists {
|
||||
filename = filenameToCheck
|
||||
fileExt = ext
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if filename == "" {
|
||||
return "", ErrNoConfigFile
|
||||
}
|
||||
|
||||
m, err := config.FromFileToMap(l.Fs, filename)
|
||||
if err != nil {
|
||||
return "", l.wrapFileError(err, filename)
|
||||
}
|
||||
|
||||
if err = v.MergeConfigMap(m); err != nil {
|
||||
return "", l.wrapFileError(err, filename)
|
||||
}
|
||||
|
||||
return filename, nil
|
||||
|
||||
}
|
||||
|
||||
func (l configLoader) wrapFileError(err error, filename string) error {
|
||||
err, _ = herrors.WithFileContextForFile(
|
||||
err,
|
||||
filename,
|
||||
filename,
|
||||
l.Fs,
|
||||
herrors.SimpleLineMatcher)
|
||||
return err
|
||||
}
|
||||
|
||||
func (l configLoader) newRealBaseFs(path string) afero.Fs {
|
||||
return hugofs.NewBasePathRealFilenameFs(afero.NewBasePathFs(l.Fs, path).(*afero.BasePathFs))
|
||||
|
||||
}
|
||||
|
||||
func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]string, error) {
|
||||
sourceFs := l.Fs
|
||||
configDir := l.AbsConfigDir
|
||||
|
||||
if _, err := sourceFs.Stat(configDir); err != nil {
|
||||
// Config dir does not exist.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
defaultConfigDir := filepath.Join(configDir, "_default")
|
||||
environmentConfigDir := filepath.Join(configDir, l.Environment)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
dirnames = append(dirnames, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
name := helpers.Filename(filepath.Base(path))
|
||||
|
||||
item, err := metadecoders.UnmarshalFileToMap(sourceFs, path)
|
||||
if err != nil {
|
||||
return l.wrapFileError(err, path)
|
||||
}
|
||||
|
||||
var keyPath []string
|
||||
|
||||
if name != "config" {
|
||||
// Can be params.jp, menus.en etc.
|
||||
name, lang := helpers.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]interface{})
|
||||
m := root
|
||||
for i, key := range keyPath {
|
||||
if i >= len(keyPath)-1 {
|
||||
m[key] = item
|
||||
} else {
|
||||
nm := make(map[string]interface{})
|
||||
m[key] = nm
|
||||
m = nm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate menu => menus etc.
|
||||
config.RenameKeys(root)
|
||||
|
||||
if err := v.MergeConfigMap(root); err != nil {
|
||||
return l.wrapFileError(err, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return dirnames, nil
|
||||
}
|
||||
|
||||
func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error {
|
||||
|
||||
defaultLang := cfg.GetString("defaultContentLanguage")
|
||||
|
@ -289,12 +453,11 @@ func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) ([]string, error) {
|
||||
themesDir := paths.AbsPathify(d.WorkingDir, v1.GetString("themesDir"))
|
||||
func (l configLoader) loadThemeConfig(v1 *viper.Viper) ([]string, error) {
|
||||
themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir"))
|
||||
themes := config.GetStringSlicePreserveString(v1, "theme")
|
||||
|
||||
// CollectThemes(fs afero.Fs, themesDir string, themes []strin
|
||||
themeConfigs, err := paths.CollectThemes(d.Fs, themesDir, themes)
|
||||
themeConfigs, err := paths.CollectThemes(l.Fs, themesDir, themes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -309,7 +472,7 @@ func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) ([]string, error
|
|||
for _, tc := range themeConfigs {
|
||||
if tc.ConfigFilename != "" {
|
||||
configFilenames = append(configFilenames, tc.ConfigFilename)
|
||||
if err := applyThemeConfig(v1, tc); err != nil {
|
||||
if err := l.applyThemeConfig(v1, tc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -319,18 +482,18 @@ func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) ([]string, error
|
|||
|
||||
}
|
||||
|
||||
func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error {
|
||||
func (l configLoader) applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error {
|
||||
|
||||
const (
|
||||
paramsKey = "params"
|
||||
languagesKey = "languages"
|
||||
menuKey = "menu"
|
||||
menuKey = "menus"
|
||||
)
|
||||
|
||||
v2 := theme.Cfg
|
||||
|
||||
for _, key := range []string{paramsKey, "outputformats", "mediatypes"} {
|
||||
mergeStringMapKeepLeft("", key, v1, v2)
|
||||
l.mergeStringMapKeepLeft("", key, v1, v2)
|
||||
}
|
||||
|
||||
themeLower := strings.ToLower(theme.Name)
|
||||
|
@ -348,7 +511,7 @@ func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error {
|
|||
v1Langs := v1.GetStringMap(languagesKey)
|
||||
for k := range v1Langs {
|
||||
langParamsKey := languagesKey + "." + k + "." + paramsKey
|
||||
mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2)
|
||||
l.mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2)
|
||||
}
|
||||
v2Langs := v2.GetStringMap(languagesKey)
|
||||
for k := range v2Langs {
|
||||
|
@ -378,7 +541,7 @@ func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error {
|
|||
}
|
||||
|
||||
// Add menu definitions from theme not found in project
|
||||
if v2.IsSet("menu") {
|
||||
if v2.IsSet(menuKey) {
|
||||
v2menus := v2.GetStringMap(menuKey)
|
||||
for k, v := range v2menus {
|
||||
menuEntry := menuKey + "." + k
|
||||
|
@ -392,7 +555,7 @@ func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error {
|
|||
|
||||
}
|
||||
|
||||
func mergeStringMapKeepLeft(rootKey, key string, v1, v2 config.Provider) {
|
||||
func (configLoader) mergeStringMapKeepLeft(rootKey, key string, v1, v2 config.Provider) {
|
||||
if !v2.IsSet(key) {
|
||||
return
|
||||
}
|
||||
|
@ -440,6 +603,7 @@ func loadDefaultSettingsFor(v *viper.Viper) error {
|
|||
v.SetDefault("buildDrafts", false)
|
||||
v.SetDefault("buildFuture", false)
|
||||
v.SetDefault("buildExpired", false)
|
||||
v.SetDefault("environment", hugo.EnvironmentProduction)
|
||||
v.SetDefault("uglyURLs", false)
|
||||
v.SetDefault("verbose", false)
|
||||
v.SetDefault("ignoreCache", false)
|
||||
|
|
|
@ -247,8 +247,8 @@ map[string]interface {}{
|
|||
b.AssertObject(`map[string]interface {}{
|
||||
"en": map[string]interface {}{
|
||||
"languagename": "English",
|
||||
"menu": map[string]interface {}{
|
||||
"theme": []interface {}{
|
||||
"menus": map[string]interface {}{
|
||||
"theme": []map[string]interface {}{
|
||||
map[string]interface {}{
|
||||
"name": "menu-lang-en-theme",
|
||||
},
|
||||
|
@ -265,8 +265,8 @@ map[string]interface {}{
|
|||
},
|
||||
"nb": map[string]interface {}{
|
||||
"languagename": "Norsk",
|
||||
"menu": map[string]interface {}{
|
||||
"theme": []interface {}{
|
||||
"menus": map[string]interface {}{
|
||||
"theme": []map[string]interface {}{
|
||||
map[string]interface {}{
|
||||
"name": "menu-lang-nb-theme",
|
||||
},
|
||||
|
@ -287,23 +287,23 @@ map[string]interface {}{
|
|||
|
||||
b.AssertObject(`
|
||||
map[string]interface {}{
|
||||
"main": []interface {}{
|
||||
"main": []map[string]interface {}{
|
||||
map[string]interface {}{
|
||||
"name": "menu-main-main",
|
||||
},
|
||||
},
|
||||
"thememenu": []interface {}{
|
||||
"thememenu": []map[string]interface {}{
|
||||
map[string]interface {}{
|
||||
"name": "menu-theme",
|
||||
},
|
||||
},
|
||||
"top": []interface {}{
|
||||
"top": []map[string]interface {}{
|
||||
map[string]interface {}{
|
||||
"name": "menu-top-main",
|
||||
},
|
||||
},
|
||||
}
|
||||
`, got["menu"])
|
||||
`, got["menus"])
|
||||
|
||||
assert.Equal("https://example.com/", got["baseurl"])
|
||||
|
||||
|
|
152
hugolib/configdir_test.go
Normal file
152
hugolib/configdir_test.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
// 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 hugolib
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
|
||||
"github.com/gohugoio/hugo/htesting"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoadConfigDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := require.New(t)
|
||||
|
||||
configContent := `
|
||||
baseURL = "https://example.org"
|
||||
paginagePath = "pag_root"
|
||||
|
||||
[languages.en]
|
||||
weight = 0
|
||||
languageName = "English"
|
||||
|
||||
[languages.no]
|
||||
weight = 10
|
||||
languageName = "FOO"
|
||||
|
||||
[params]
|
||||
p1 = "p1_base"
|
||||
|
||||
`
|
||||
|
||||
mm := afero.NewMemMapFs()
|
||||
|
||||
writeToFs(t, mm, "hugo.toml", configContent)
|
||||
|
||||
fb := htesting.NewTestdataBuilder(mm, "config/_default", t)
|
||||
|
||||
fb.Add("config.toml", `paginatePath = "pag_default"`)
|
||||
|
||||
fb.Add("params.yaml", `
|
||||
p2: "p2params_default"
|
||||
p3: "p3params_default"
|
||||
p4: "p4params_default"
|
||||
`)
|
||||
fb.Add("menus.toml", `
|
||||
[[docs]]
|
||||
name = "About Hugo"
|
||||
weight = 1
|
||||
[[docs]]
|
||||
name = "Home"
|
||||
weight = 2
|
||||
`)
|
||||
|
||||
fb.Add("menus.no.toml", `
|
||||
[[docs]]
|
||||
name = "Om Hugo"
|
||||
weight = 1
|
||||
`)
|
||||
|
||||
fb.Add("params.no.toml",
|
||||
`
|
||||
p3 = "p3params_no_default"
|
||||
p4 = "p4params_no_default"`,
|
||||
)
|
||||
fb.Add("languages.no.toml", `languageName = "Norsk_no_default"`)
|
||||
|
||||
fb.Build()
|
||||
|
||||
fb = fb.WithWorkingDir("config/production")
|
||||
|
||||
fb.Add("config.toml", `paginatePath = "pag_production"`)
|
||||
|
||||
fb.Add("params.no.toml", `
|
||||
p2 = "p2params_no_production"
|
||||
p3 = "p3params_no_production"
|
||||
`)
|
||||
|
||||
fb.Build()
|
||||
|
||||
fb = fb.WithWorkingDir("config/development")
|
||||
|
||||
// This is set in all the config.toml variants above, but this will win.
|
||||
fb.Add("config.toml", `paginatePath = "pag_development"`)
|
||||
|
||||
fb.Add("params.no.toml", `p3 = "p3params_no_development"`)
|
||||
fb.Add("params.toml", `p3 = "p3params_development"`)
|
||||
|
||||
fb.Build()
|
||||
|
||||
cfg, _, err := LoadConfig(ConfigSourceDescriptor{Fs: mm, Environment: "development", Filename: "hugo.toml", AbsConfigDir: "config"})
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Equal("pag_development", cfg.GetString("paginatePath")) // /config/development/config.toml
|
||||
|
||||
assert.Equal(10, cfg.GetInt("languages.no.weight")) // /config.toml
|
||||
assert.Equal("Norsk_no_default", cfg.GetString("languages.no.languageName")) // /config/_default/languages.no.toml
|
||||
|
||||
assert.Equal("p1_base", cfg.GetString("params.p1"))
|
||||
assert.Equal("p2params_default", cfg.GetString("params.p2")) // Is in both _default and production
|
||||
assert.Equal("p3params_development", cfg.GetString("params.p3"))
|
||||
assert.Equal("p3params_no_development", cfg.GetString("languages.no.params.p3"))
|
||||
|
||||
assert.Equal(2, len(cfg.Get("menus.docs").(([]map[string]interface{}))))
|
||||
noMenus := cfg.Get("languages.no.menus.docs")
|
||||
assert.NotNil(noMenus)
|
||||
assert.Equal(1, len(noMenus.(([]map[string]interface{}))))
|
||||
|
||||
}
|
||||
|
||||
func TestLoadConfigDirError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := require.New(t)
|
||||
|
||||
configContent := `
|
||||
baseURL = "https://example.org"
|
||||
|
||||
`
|
||||
|
||||
mm := afero.NewMemMapFs()
|
||||
|
||||
writeToFs(t, mm, "hugo.toml", configContent)
|
||||
|
||||
fb := htesting.NewTestdataBuilder(mm, "config/development", t)
|
||||
|
||||
fb.Add("config.toml", `invalid & syntax`).Build()
|
||||
|
||||
_, _, err := LoadConfig(ConfigSourceDescriptor{Fs: mm, Environment: "development", Filename: "hugo.toml", AbsConfigDir: "config"})
|
||||
assert.Error(err)
|
||||
|
||||
fe := herrors.UnwrapErrorWithFileContext(err)
|
||||
assert.NotNil(fe)
|
||||
assert.Equal(filepath.FromSlash("config/development/config.toml"), fe.Position().Filename)
|
||||
|
||||
}
|
|
@ -21,6 +21,8 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
|
||||
"github.com/gohugoio/hugo/publisher"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
|
@ -361,14 +363,14 @@ func (h *HugoSites) resetLogs() {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *HugoSites) createSitesFromConfig() error {
|
||||
func (h *HugoSites) createSitesFromConfig(cfg config.Provider) error {
|
||||
oldLangs, _ := h.Cfg.Get("languagesSorted").(langs.Languages)
|
||||
|
||||
if err := loadLanguageSettings(h.Cfg, oldLangs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: h.Cfg}
|
||||
depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: cfg}
|
||||
|
||||
sites, err := createSitesFromConfig(depsCfg)
|
||||
|
||||
|
@ -412,9 +414,9 @@ func (h *HugoSites) toSiteInfos() []*SiteInfo {
|
|||
type BuildCfg struct {
|
||||
// Reset site state before build. Use to force full rebuilds.
|
||||
ResetState bool
|
||||
// Re-creates the sites from configuration before a build.
|
||||
// If set, we re-create the sites from the given configuration before a build.
|
||||
// This is needed if new languages are added.
|
||||
CreateSitesFromConfig bool
|
||||
NewConfig config.Provider
|
||||
// Skip rendering. Useful for testing.
|
||||
SkipRender bool
|
||||
// Use this to indicate what changed (for rebuilds).
|
||||
|
|
|
@ -144,8 +144,8 @@ func (h *HugoSites) init(config *BuildCfg) error {
|
|||
h.reset()
|
||||
}
|
||||
|
||||
if config.CreateSitesFromConfig {
|
||||
if err := h.createSitesFromConfig(); err != nil {
|
||||
if config.NewConfig != nil {
|
||||
if err := h.createSitesFromConfig(config.NewConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -154,8 +154,8 @@ func (h *HugoSites) init(config *BuildCfg) error {
|
|||
}
|
||||
|
||||
func (h *HugoSites) initRebuild(config *BuildCfg) error {
|
||||
if config.CreateSitesFromConfig {
|
||||
return errors.New("Rebuild does not support 'CreateSitesFromConfig'.")
|
||||
if config.NewConfig != nil {
|
||||
return errors.New("Rebuild does not support 'NewConfig'.")
|
||||
}
|
||||
|
||||
if config.ResetState {
|
||||
|
|
|
@ -11,14 +11,11 @@ import (
|
|||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
|
||||
"github.com/fortytw2/leaktest"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -661,9 +658,8 @@ title = "Svenska"
|
|||
|
||||
sites := b.H
|
||||
|
||||
// Watching does not work with in-memory fs, so we trigger a reload manually
|
||||
assert.NoError(sites.Cfg.(*langs.Language).Cfg.(*viper.Viper).ReadInConfig())
|
||||
err := b.H.Build(BuildCfg{CreateSitesFromConfig: true})
|
||||
assert.NoError(b.LoadConfig())
|
||||
err := b.H.Build(BuildCfg{NewConfig: b.Cfg})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to rebuild sites: %s", err)
|
||||
|
@ -723,10 +719,9 @@ func TestChangeDefaultLanguage(t *testing.T) {
|
|||
"DefaultContentLanguageInSubdir": false,
|
||||
})
|
||||
|
||||
// Watching does not work with in-memory fs, so we trigger a reload manually
|
||||
// This does not look pretty, so we should think of something else.
|
||||
assert.NoError(b.H.Cfg.(*langs.Language).Cfg.(*viper.Viper).ReadInConfig())
|
||||
err := b.H.Build(BuildCfg{CreateSitesFromConfig: true})
|
||||
assert.NoError(b.LoadConfig())
|
||||
err := b.H.Build(BuildCfg{NewConfig: b.Cfg})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to rebuild sites: %s", err)
|
||||
}
|
||||
|
|
|
@ -1647,7 +1647,12 @@ func (p *Page) Menus() PageMenus {
|
|||
p.pageMenusInit.Do(func() {
|
||||
p.pageMenus = PageMenus{}
|
||||
|
||||
if ms, ok := p.params["menu"]; ok {
|
||||
ms, ok := p.params["menus"]
|
||||
if !ok {
|
||||
ms, ok = p.params["menu"]
|
||||
}
|
||||
|
||||
if ok {
|
||||
link := p.RelPermalink()
|
||||
|
||||
me := MenuEntry{Page: p, Name: p.LinkTitle(), Weight: p.Weight, URL: link}
|
||||
|
|
|
@ -1436,7 +1436,7 @@ func TestIndexPageSimpleMethods(t *testing.T) {
|
|||
{func(n *Page) bool { return n.IsNode() }},
|
||||
{func(n *Page) bool { return !n.IsPage() }},
|
||||
{func(n *Page) bool { return n.Scratch() != nil }},
|
||||
{func(n *Page) bool { return n.Hugo().Version != "" }},
|
||||
{func(n *Page) bool { return n.Hugo().Version() != "" }},
|
||||
} {
|
||||
|
||||
n := s.newHomePage()
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type ThemeConfig struct {
|
||||
|
@ -73,18 +72,11 @@ func (c *themesCollector) add(name, configFilename string) (ThemeConfig, error)
|
|||
var tc ThemeConfig
|
||||
|
||||
if configFilename != "" {
|
||||
v := viper.New()
|
||||
v.SetFs(c.fs)
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvPrefix("hugo")
|
||||
v.SetConfigFile(configFilename)
|
||||
|
||||
err := v.ReadInConfig()
|
||||
var err error
|
||||
cfg, err = config.FromFile(c.fs, configFilename)
|
||||
if err != nil {
|
||||
return tc, err
|
||||
return tc, nil
|
||||
}
|
||||
cfg = v
|
||||
|
||||
}
|
||||
|
||||
tc = ThemeConfig{Name: name, ConfigFilename: configFilename, Cfg: cfg}
|
||||
|
|
|
@ -1226,7 +1226,7 @@ func (s *Site) initializeSiteInfo() error {
|
|||
Data: &s.Data,
|
||||
owner: s.owner,
|
||||
s: s,
|
||||
hugoInfo: hugo.NewInfo(),
|
||||
hugoInfo: hugo.NewInfo(s.Cfg.GetString("environment")),
|
||||
// TODO(bep) make this Menu and similar into delegate methods on SiteInfo
|
||||
Taxonomies: s.Taxonomies,
|
||||
}
|
||||
|
@ -1370,7 +1370,7 @@ func (s *Site) getMenusFromConfig() Menus {
|
|||
|
||||
ret := Menus{}
|
||||
|
||||
if menus := s.Language.GetStringMap("menu"); menus != nil {
|
||||
if menus := s.Language.GetStringMap("menus"); menus != nil {
|
||||
for name, menu := range menus {
|
||||
m, err := cast.ToSliceE(menu)
|
||||
if err != nil {
|
||||
|
|
|
@ -322,6 +322,15 @@ func (s *sitesBuilder) CreateSites() *sitesBuilder {
|
|||
return s
|
||||
}
|
||||
|
||||
func (s *sitesBuilder) LoadConfig() error {
|
||||
cfg, _, err := LoadConfig(ConfigSourceDescriptor{Fs: s.Fs.Source, Filename: "config." + s.configFormat})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Cfg = cfg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sitesBuilder) CreateSitesE() error {
|
||||
s.addDefaults()
|
||||
s.writeFilePairs("content", s.contentFilePairs)
|
||||
|
@ -334,18 +343,9 @@ func (s *sitesBuilder) CreateSitesE() error {
|
|||
s.writeFilePairs("i18n", s.i18nFilePairsAdded)
|
||||
|
||||
if s.Cfg == nil {
|
||||
cfg, _, err := LoadConfig(ConfigSourceDescriptor{Fs: s.Fs.Source, Filename: "config." + s.configFormat})
|
||||
if err != nil {
|
||||
if err := s.LoadConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(bep)
|
||||
/* expectedConfigs := 1
|
||||
if s.theme != "" {
|
||||
expectedConfigs = 2
|
||||
}
|
||||
require.Equal(s.T, expectedConfigs, len(configFiles), fmt.Sprintf("Configs: %v", configFiles))
|
||||
*/
|
||||
s.Cfg = cfg
|
||||
}
|
||||
|
||||
sites, err := NewHugoSites(deps.DepsCfg{Fs: s.Fs, Cfg: s.Cfg, Logger: s.logger, Running: s.running})
|
||||
|
|
|
@ -21,10 +21,10 @@ import (
|
|||
|
||||
const (
|
||||
packageName = "github.com/gohugoio/hugo"
|
||||
noGitLdflags = "-X $PACKAGE/common/hugo.BuildDate=$BUILD_DATE"
|
||||
noGitLdflags = "-X $PACKAGE/common/hugo.buildDate=$BUILD_DATE"
|
||||
)
|
||||
|
||||
var ldflags = "-X $PACKAGE/common/hugo.CommitHash=$COMMIT_HASH -X $PACKAGE/common/hugo.BuildDate=$BUILD_DATE"
|
||||
var ldflags = "-X $PACKAGE/common/hugo.commitHash=$COMMIT_HASH -X $PACKAGE/common/hugo.buildDate=$BUILD_DATE"
|
||||
|
||||
// allow user to override go executable by running as GOEXE=xxx make ... on unix-like systems
|
||||
var goexe = "go"
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/BurntSushi/toml"
|
||||
"github.com/chaseadamsio/goorgeous"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
@ -37,7 +38,21 @@ func UnmarshalToMap(data []byte, f Format) (map[string]interface{}, error) {
|
|||
err := unmarshal(data, f, &m)
|
||||
|
||||
return m, err
|
||||
}
|
||||
|
||||
// UnmarshalFileToMap is the same as UnmarshalToMap, but reads the data from
|
||||
// the given filename.
|
||||
func UnmarshalFileToMap(fs afero.Fs, filename string) (map[string]interface{}, error) {
|
||||
format := FormatFromString(filename)
|
||||
if format == "" {
|
||||
return nil, errors.Errorf("%q is not a valid configuration format", filename)
|
||||
}
|
||||
|
||||
data, err := afero.ReadFile(fs, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return UnmarshalToMap(data, format)
|
||||
}
|
||||
|
||||
// Unmarshal will unmarshall data in format f into an interface{}.
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
package metadecoders
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
|
@ -34,6 +35,11 @@ const (
|
|||
// into a Format. It returns an empty string for unknown formats.
|
||||
func FormatFromString(formatStr string) Format {
|
||||
formatStr = strings.ToLower(formatStr)
|
||||
if strings.Contains(formatStr, ".") {
|
||||
// Assume a filename
|
||||
formatStr = strings.TrimPrefix(filepath.Ext(formatStr), ".")
|
||||
|
||||
}
|
||||
switch formatStr {
|
||||
case "yaml", "yml":
|
||||
return YAML
|
||||
|
|
|
@ -32,6 +32,7 @@ func TestFormatFromString(t *testing.T) {
|
|||
{"yaml", YAML},
|
||||
{"yml", YAML},
|
||||
{"toml", TOML},
|
||||
{"config.toml", TOML},
|
||||
{"tOMl", TOML},
|
||||
{"org", ORG},
|
||||
{"foo", ""},
|
||||
|
|
Loading…
Reference in a new issue