mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-28 23:12:14 -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
|
sourceFs = c.DepsCfg.Fs.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
environment := c.h.getEnvironment(running)
|
||||||
|
|
||||||
doWithConfig := func(cfg config.Provider) error {
|
doWithConfig := func(cfg config.Provider) error {
|
||||||
|
|
||||||
if c.ftch != nil {
|
if c.ftch != nil {
|
||||||
|
@ -256,7 +258,7 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Set("workingDir", dir)
|
cfg.Set("workingDir", dir)
|
||||||
|
cfg.Set("environment", environment)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,8 +271,18 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configPath := c.h.source
|
||||||
|
if configPath == "" {
|
||||||
|
configPath = dir
|
||||||
|
}
|
||||||
config, configFiles, err := hugolib.LoadConfig(
|
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,
|
doWithCommandeer,
|
||||||
doWithConfig)
|
doWithConfig)
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,11 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/hugolib/paths"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/hugo"
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
"github.com/gohugoio/hugo/common/loggers"
|
||||||
"github.com/gohugoio/hugo/config"
|
"github.com/gohugoio/hugo/config"
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"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.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")
|
cc.cmd.PersistentFlags().BoolVar(&cc.quiet, "quiet", false, "build in quiet mode")
|
||||||
|
|
||||||
// Set bash-completion
|
// Set bash-completion
|
||||||
|
@ -185,8 +191,9 @@ Complete documentation is available at http://gohugo.io/.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
type hugoBuilderCommon struct {
|
type hugoBuilderCommon struct {
|
||||||
source string
|
source string
|
||||||
baseURL string
|
baseURL string
|
||||||
|
environment string
|
||||||
|
|
||||||
buildWatch bool
|
buildWatch bool
|
||||||
|
|
||||||
|
@ -200,15 +207,45 @@ type hugoBuilderCommon struct {
|
||||||
quiet bool
|
quiet bool
|
||||||
|
|
||||||
cfgFile string
|
cfgFile string
|
||||||
|
cfgDir string
|
||||||
logFile 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) {
|
func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
|
||||||
cmd.Flags().Bool("cleanDestinationDir", false, "remove files from destination not found in static directories")
|
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("buildDrafts", "D", false, "include content marked as draft")
|
||||||
cmd.Flags().BoolP("buildFuture", "F", false, "include content with publishdate in the future")
|
cmd.Flags().BoolP("buildFuture", "F", false, "include content with publishdate in the future")
|
||||||
cmd.Flags().BoolP("buildExpired", "E", false, "include expired content")
|
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.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("contentDir", "c", "", "filesystem path to content directory")
|
||||||
cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
|
cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
|
||||||
cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/")
|
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)
|
check func(command []cmder)
|
||||||
}{{[]string{"server",
|
}{{[]string{"server",
|
||||||
"--config=myconfig.toml",
|
"--config=myconfig.toml",
|
||||||
|
"--configDir=myconfigdir",
|
||||||
"--contentDir=mycontent",
|
"--contentDir=mycontent",
|
||||||
"--disableKinds=page,home",
|
"--disableKinds=page,home",
|
||||||
|
"--environment=testing",
|
||||||
|
"--configDir=myconfigdir",
|
||||||
"--layoutDir=mylayouts",
|
"--layoutDir=mylayouts",
|
||||||
"--theme=mytheme",
|
"--theme=mytheme",
|
||||||
"--gc",
|
"--gc",
|
||||||
|
@ -78,6 +81,7 @@ func TestCommandsPersistentFlags(t *testing.T) {
|
||||||
if b, ok := command.(commandsBuilderGetter); ok {
|
if b, ok := command.(commandsBuilderGetter); ok {
|
||||||
v := b.getCommandsBuilder().hugoBuilderCommon
|
v := b.getCommandsBuilder().hugoBuilderCommon
|
||||||
assert.Equal("myconfig.toml", v.cfgFile)
|
assert.Equal("myconfig.toml", v.cfgFile)
|
||||||
|
assert.Equal("myconfigdir", v.cfgDir)
|
||||||
assert.Equal("mysource", v.source)
|
assert.Equal("mysource", v.source)
|
||||||
assert.Equal("https://example.com/b/", v.baseURL)
|
assert.Equal("https://example.com/b/", v.baseURL)
|
||||||
}
|
}
|
||||||
|
@ -93,6 +97,7 @@ func TestCommandsPersistentFlags(t *testing.T) {
|
||||||
assert.True(sc.noHTTPCache)
|
assert.True(sc.noHTTPCache)
|
||||||
assert.True(sc.renderToDisk)
|
assert.True(sc.renderToDisk)
|
||||||
assert.Equal(1366, sc.serverPort)
|
assert.Equal(1366, sc.serverPort)
|
||||||
|
assert.Equal("testing", sc.environment)
|
||||||
|
|
||||||
cfg := viper.New()
|
cfg := viper.New()
|
||||||
sc.flagsToConfig(cfg)
|
sc.flagsToConfig(cfg)
|
||||||
|
@ -233,6 +238,7 @@ Single: {{ .Title }}
|
||||||
writeFile(t, filepath.Join(d, "layouts", "_default", "list.html"), `
|
writeFile(t, filepath.Join(d, "layouts", "_default", "list.html"), `
|
||||||
|
|
||||||
List: {{ .Title }}
|
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.
|
// Identifies changes to config (config.toml) files.
|
||||||
configSet := make(map[string]bool)
|
configSet := make(map[string]bool)
|
||||||
|
|
||||||
|
c.logger.FEEDBACK.Println("Watching for config changes in", strings.Join(c.configFiles, ", "))
|
||||||
for _, configFile := range c.configFiles {
|
for _, configFile := range c.configFiles {
|
||||||
c.logger.FEEDBACK.Println("Watching for config changes in", configFile)
|
|
||||||
watcher.Add(configFile)
|
watcher.Add(configFile)
|
||||||
configSet[configFile] = true
|
configSet[configFile] = true
|
||||||
}
|
}
|
||||||
|
@ -750,7 +750,17 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
|
||||||
configSet map[string]bool) {
|
configSet map[string]bool) {
|
||||||
|
|
||||||
for _, ev := range evs {
|
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 {
|
if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
|
||||||
continue
|
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()
|
c.fullRebuild()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ import (
|
||||||
"github.com/gohugoio/hugo/tpl"
|
"github.com/gohugoio/hugo/tpl"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
"github.com/gohugoio/hugo/config"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cobra"
|
"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)
|
absPublishDir := f.c.hugo.PathSpec.AbsPathify(publishDir)
|
||||||
|
|
||||||
|
jww.FEEDBACK.Printf("Environment: %q", f.c.hugo.Deps.Site.Hugo().Environment)
|
||||||
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
if f.s.renderToDisk {
|
if f.s.renderToDisk {
|
||||||
jww.FEEDBACK.Println("Serving pages from " + absPublishDir)
|
jww.FEEDBACK.Println("Serving pages from " + absPublishDir)
|
||||||
|
|
|
@ -68,6 +68,7 @@ func TestServer(t *testing.T) {
|
||||||
homeContent := helpers.ReaderToString(resp.Body)
|
homeContent := helpers.ReaderToString(resp.Body)
|
||||||
|
|
||||||
assert.Contains(homeContent, "List: Hugo Commands")
|
assert.Contains(homeContent, "List: Hugo Commands")
|
||||||
|
assert.Contains(homeContent, "Environment: development")
|
||||||
|
|
||||||
// Stop the server.
|
// Stop the server.
|
||||||
stop <- true
|
stop <- true
|
||||||
|
|
|
@ -17,10 +17,10 @@ package herrors
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/text"
|
"github.com/gohugoio/hugo/common/text"
|
||||||
"github.com/gohugoio/hugo/helpers"
|
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
@ -172,12 +172,16 @@ func chromaLexerFromType(fileType string) string {
|
||||||
return fileType
|
return fileType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extNoDelimiter(filename string) string {
|
||||||
|
return strings.TrimPrefix(".", filepath.Ext(filename))
|
||||||
|
}
|
||||||
|
|
||||||
func chromaLexerFromFilename(filename string) string {
|
func chromaLexerFromFilename(filename string) string {
|
||||||
if strings.Contains(filename, "layouts") {
|
if strings.Contains(filename, "layouts") {
|
||||||
return "go-html-template"
|
return "go-html-template"
|
||||||
}
|
}
|
||||||
|
|
||||||
ext := helpers.ExtNoDelimiter(filename)
|
ext := extNoDelimiter(filename)
|
||||||
return chromaLexerFromType(ext)
|
return chromaLexerFromType(ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,28 +18,50 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
// CommitHash contains the current Git revision. Use make to build to make
|
EnvironmentDevelopment = "development"
|
||||||
// sure this gets set.
|
EnvironmentProduction = "production"
|
||||||
CommitHash string
|
)
|
||||||
|
|
||||||
// BuildDate contains the date of the current build.
|
var (
|
||||||
BuildDate string
|
// 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
|
// Info contains information about the current Hugo environment
|
||||||
type Info struct {
|
type Info struct {
|
||||||
Version VersionString
|
|
||||||
Generator template.HTML
|
|
||||||
CommitHash string
|
CommitHash string
|
||||||
BuildDate 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{
|
return Info{
|
||||||
Version: CurrentVersion.Version(),
|
CommitHash: commitHash,
|
||||||
CommitHash: CommitHash,
|
BuildDate: buildDate,
|
||||||
BuildDate: BuildDate,
|
Environment: environment,
|
||||||
Generator: template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s" />`, CurrentVersion.String())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,13 @@ import (
|
||||||
func TestHugoInfo(t *testing.T) {
|
func TestHugoInfo(t *testing.T) {
|
||||||
assert := require.New(t)
|
assert := require.New(t)
|
||||||
|
|
||||||
hugoInfo := NewInfo()
|
hugoInfo := NewInfo("")
|
||||||
|
|
||||||
assert.Equal(CurrentVersion.Version(), hugoInfo.Version)
|
assert.Equal(CurrentVersion.Version(), hugoInfo.Version())
|
||||||
assert.IsType(VersionString(""), hugoInfo.Version)
|
assert.IsType(VersionString(""), hugoInfo.Version())
|
||||||
assert.Equal(CommitHash, hugoInfo.CommitHash)
|
assert.Equal(commitHash, hugoInfo.CommitHash)
|
||||||
assert.Equal(BuildDate, hugoInfo.BuildDate)
|
assert.Equal(buildDate, hugoInfo.BuildDate)
|
||||||
assert.Contains(hugoInfo.Generator, fmt.Sprintf("Hugo %s", hugoInfo.Version))
|
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"
|
program := "Hugo Static Site Generator"
|
||||||
|
|
||||||
version := "v" + CurrentVersion.String()
|
version := "v" + CurrentVersion.String()
|
||||||
if CommitHash != "" {
|
if commitHash != "" {
|
||||||
version += "-" + strings.ToUpper(CommitHash)
|
version += "-" + strings.ToUpper(commitHash)
|
||||||
}
|
}
|
||||||
if isExtended {
|
if isExtended {
|
||||||
version += "/extended"
|
version += "/extended"
|
||||||
|
@ -139,14 +139,12 @@ func BuildVersionString() string {
|
||||||
|
|
||||||
osArch := runtime.GOOS + "/" + runtime.GOARCH
|
osArch := runtime.GOOS + "/" + runtime.GOARCH
|
||||||
|
|
||||||
var buildDate string
|
date := buildDate
|
||||||
if BuildDate != "" {
|
if date == "" {
|
||||||
buildDate = BuildDate
|
date = "unknown"
|
||||||
} else {
|
|
||||||
buildDate = "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 (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gobwas/glob"
|
||||||
|
|
||||||
"github.com/spf13/cast"
|
"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 (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestToLower(t *testing.T) {
|
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
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider provides the configuration settings for Hugo.
|
// Provider provides the configuration settings for Hugo.
|
||||||
|
@ -34,16 +30,6 @@ type Provider interface {
|
||||||
IsSet(key string) bool
|
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.
|
// 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,
|
// It differs from the GetStringSlice method in that if the config value is a string,
|
||||||
// we do not attempt to split it into fields.
|
// 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/mattn/go-runewidth v0.0.3 // indirect
|
||||||
github.com/miekg/mmark v1.3.6
|
github.com/miekg/mmark v1.3.6
|
||||||
github.com/mitchellh/hashstructure v1.0.0
|
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/muesli/smartcrop v0.0.0-20180228075044-f6ebaa786a12
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||||
github.com/nicksnyder/go-i18n v1.10.0
|
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/jwalterweatherman v1.0.1-0.20181028145347-94f6ae3ed3bc
|
||||||
github.com/spf13/nitro v0.0.0-20131003134307-24d7ef30a12d
|
github.com/spf13/nitro v0.0.0-20131003134307-24d7ef30a12d
|
||||||
github.com/spf13/pflag v1.0.3
|
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/stretchr/testify v1.2.3-0.20181014000028-04af85275a5c
|
||||||
github.com/tdewolff/minify/v2 v2.3.7
|
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
|
github.com/yosssi/ace v0.0.5
|
||||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81
|
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd // indirect
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd // indirect
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
|
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
|
golang.org/x/text v0.3.0
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
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
|
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/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 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
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 h1:6ocXeW2iZ/7vAzgXz82J00tYxncMiEEBExPftTtOQzk=
|
||||||
github.com/bep/debounce v1.1.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
github.com/bep/debounce v1.1.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
github.com/bep/gitmap v1.0.0 h1:cTTZwq7vpGuhwefKCBDV9UrHnZAPVJTvoWobimrqkUc=
|
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/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 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=
|
||||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
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 h1:DwoNytLphI8hzS2Af4D0dfaEaiSq2bN05mEm4R6vf8M=
|
||||||
github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
|
github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
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/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 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y=
|
||||||
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
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.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
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 h1:l0X/8IDy2UoK+oXcQFMRSIOcyuYb5iEPytPGplnM41Y=
|
||||||
github.com/muesli/smartcrop v0.0.0-20180228075044-f6ebaa786a12/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI=
|
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=
|
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/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 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
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 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
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/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 h1:ihvj2nmx8eqWjlgNgdW6h0DyGJuq5GiwHadJkG0wXtQ=
|
||||||
github.com/spf13/nitro v0.0.0-20131003134307-24d7ef30a12d/go.mod h1:jU8A+8xL+6n1OX4XaZtCj4B3mIa64tULUsD6YegdpFo=
|
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 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
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.3.0 h1:cO6QlTTeK9RQDhFAbGLV5e3fHXbRpin/Gi8qfL4rdLk=
|
||||||
github.com/spf13/viper v1.2.0/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
|
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 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
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=
|
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/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 h1:jOwzqCXr5ePXEPGJaq2ivoR6HOCi+D5TPfpoyg8yvmU=
|
||||||
github.com/tdewolff/test v1.0.0/go.mod h1:DiQUlutnqlEvdvhSn2LPGy4TFwRauAaYDsL+683RNX4=
|
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 h1:9vG9vvVNVupO4Y7uwFkRgIMNe9rdaJMCINDe8vhAhLo=
|
||||||
github.com/wellington/go-libsass v0.9.3-0.20181113175235-c63644206701/go.mod h1:mxgxgam0N0E+NAUMHLcu20Ccfc3mVpDkyrLDayqfiTs=
|
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 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
|
||||||
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
|
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 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
|
||||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
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 h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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 h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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 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-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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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 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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
@ -2,7 +2,7 @@ project_name: hugo_extended
|
||||||
builds:
|
builds:
|
||||||
- binary: hugo
|
- binary: hugo
|
||||||
ldflags:
|
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'"
|
- "-extldflags '-static'"
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1
|
- CGO_ENABLED=1
|
||||||
|
|
|
@ -2,7 +2,7 @@ project_name: hugo
|
||||||
build:
|
build:
|
||||||
main: main.go
|
main: main.go
|
||||||
binary: hugo
|
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:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
goos:
|
goos:
|
||||||
|
|
|
@ -274,6 +274,13 @@ func FileAndExt(in string) (string, string) {
|
||||||
return fileAndExt(in, fpb)
|
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,
|
// Filename takes a path, strips out the extension,
|
||||||
// and returns the name of the file.
|
// and returns the name of the file.
|
||||||
func Filename(in string) (name string) {
|
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
|
// FindCWD returns the current working directory from where the Hugo
|
||||||
// executable is run.
|
// executable is run.
|
||||||
func FindCWD() (string, error) {
|
func FindCWD() (string, error) {
|
||||||
|
|
|
@ -39,7 +39,7 @@ func (t testSite) Language() *langs.Language {
|
||||||
// NewTestHugoSite creates a new minimal test site.
|
// NewTestHugoSite creates a new minimal test site.
|
||||||
func NewTestHugoSite() hugo.Site {
|
func NewTestHugoSite() hugo.Site {
|
||||||
return testSite{
|
return testSite{
|
||||||
h: hugo.NewInfo(),
|
h: hugo.NewInfo(hugo.EnvironmentProduction),
|
||||||
l: langs.NewLanguage("en", newTestConfig()),
|
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
|
package hugolib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"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/gohugoio/hugo/hugolib/paths"
|
||||||
|
"github.com/pkg/errors"
|
||||||
_errors "github.com/pkg/errors"
|
_errors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/langs"
|
"github.com/gohugoio/hugo/langs"
|
||||||
|
@ -65,96 +70,84 @@ func loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err error) {
|
||||||
type ConfigSourceDescriptor struct {
|
type ConfigSourceDescriptor struct {
|
||||||
Fs afero.Fs
|
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
|
Filename string
|
||||||
|
|
||||||
// The path to the directory to look for configuration. Is used if Filename is not
|
// 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
|
Path string
|
||||||
|
|
||||||
// The project's working dir. Is used to look for additional theme config.
|
// The project's working dir. Is used to look for additional theme config.
|
||||||
WorkingDir string
|
WorkingDir string
|
||||||
|
|
||||||
|
// The (optional) directory for additional configuration files.
|
||||||
|
AbsConfigDir string
|
||||||
|
|
||||||
|
// production, development
|
||||||
|
Environment string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d ConfigSourceDescriptor) configFilenames() []string {
|
func (d ConfigSourceDescriptor) configFilenames() []string {
|
||||||
|
if d.Filename == "" {
|
||||||
|
return []string{"config"}
|
||||||
|
}
|
||||||
return strings.Split(d.Filename, ",")
|
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.
|
// LoadConfigDefault is a convenience method to load the default "config.toml" config.
|
||||||
func LoadConfigDefault(fs afero.Fs) (*viper.Viper, error) {
|
func LoadConfigDefault(fs afero.Fs) (*viper.Viper, error) {
|
||||||
v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs, Filename: "config.toml"})
|
v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs, Filename: "config.toml"})
|
||||||
return v, err
|
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
|
// LoadConfig loads Hugo configuration into a new Viper and then adds
|
||||||
// a set of defaults.
|
// a set of defaults.
|
||||||
func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provider) error) (*viper.Viper, []string, error) {
|
func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provider) error) (*viper.Viper, []string, error) {
|
||||||
|
if d.Environment == "" {
|
||||||
|
d.Environment = hugo.EnvironmentProduction
|
||||||
|
}
|
||||||
|
|
||||||
var configFiles []string
|
var configFiles []string
|
||||||
|
|
||||||
fs := d.Fs
|
|
||||||
v := viper.New()
|
v := viper.New()
|
||||||
v.SetFs(fs)
|
l := configLoader{ConfigSourceDescriptor: d}
|
||||||
|
|
||||||
if d.Path == "" {
|
|
||||||
d.Path = "."
|
|
||||||
}
|
|
||||||
|
|
||||||
configFilenames := d.configFilenames()
|
|
||||||
v.AutomaticEnv()
|
v.AutomaticEnv()
|
||||||
v.SetEnvPrefix("hugo")
|
v.SetEnvPrefix("hugo")
|
||||||
v.SetConfigFile(configFilenames[0])
|
|
||||||
v.AddConfigPath(d.Path)
|
|
||||||
|
|
||||||
applyFileContext := func(filename string, err error) error {
|
var cerr error
|
||||||
err, _ = herrors.WithFileContextForFile(
|
|
||||||
err,
|
|
||||||
filename,
|
|
||||||
filename,
|
|
||||||
fs,
|
|
||||||
herrors.SimpleLineMatcher)
|
|
||||||
|
|
||||||
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
|
if d.AbsConfigDir != "" {
|
||||||
|
dirnames, err := l.loadConfigFromConfigDir(v)
|
||||||
err := v.ReadInConfig()
|
if err == nil {
|
||||||
if err != nil {
|
configFiles = append(configFiles, dirnames...)
|
||||||
if _, ok := err.(viper.ConfigParseError); ok {
|
|
||||||
return nil, configFiles, applyFileContext(v.ConfigFileUsed(), err)
|
|
||||||
}
|
}
|
||||||
configFileErr = ErrNoConfigFile
|
cerr = err
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := loadDefaultSettingsFor(v); err != nil {
|
if err := loadDefaultSettingsFor(v); err != nil {
|
||||||
return v, configFiles, err
|
return v, configFiles, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if configFileErr == nil {
|
if cerr == nil {
|
||||||
|
themeConfigFiles, err := l.loadThemeConfig(v)
|
||||||
themeConfigFiles, err := loadThemeConfig(d, v)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return v, configFiles, err
|
return v, configFiles, err
|
||||||
}
|
}
|
||||||
|
@ -176,10 +169,181 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid
|
||||||
return v, configFiles, err
|
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 {
|
func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error {
|
||||||
|
|
||||||
defaultLang := cfg.GetString("defaultContentLanguage")
|
defaultLang := cfg.GetString("defaultContentLanguage")
|
||||||
|
@ -289,12 +453,11 @@ func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) ([]string, error) {
|
func (l configLoader) loadThemeConfig(v1 *viper.Viper) ([]string, error) {
|
||||||
themesDir := paths.AbsPathify(d.WorkingDir, v1.GetString("themesDir"))
|
themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir"))
|
||||||
themes := config.GetStringSlicePreserveString(v1, "theme")
|
themes := config.GetStringSlicePreserveString(v1, "theme")
|
||||||
|
|
||||||
// CollectThemes(fs afero.Fs, themesDir string, themes []strin
|
themeConfigs, err := paths.CollectThemes(l.Fs, themesDir, themes)
|
||||||
themeConfigs, err := paths.CollectThemes(d.Fs, themesDir, themes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -309,7 +472,7 @@ func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) ([]string, error
|
||||||
for _, tc := range themeConfigs {
|
for _, tc := range themeConfigs {
|
||||||
if tc.ConfigFilename != "" {
|
if tc.ConfigFilename != "" {
|
||||||
configFilenames = append(configFilenames, 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
|
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 (
|
const (
|
||||||
paramsKey = "params"
|
paramsKey = "params"
|
||||||
languagesKey = "languages"
|
languagesKey = "languages"
|
||||||
menuKey = "menu"
|
menuKey = "menus"
|
||||||
)
|
)
|
||||||
|
|
||||||
v2 := theme.Cfg
|
v2 := theme.Cfg
|
||||||
|
|
||||||
for _, key := range []string{paramsKey, "outputformats", "mediatypes"} {
|
for _, key := range []string{paramsKey, "outputformats", "mediatypes"} {
|
||||||
mergeStringMapKeepLeft("", key, v1, v2)
|
l.mergeStringMapKeepLeft("", key, v1, v2)
|
||||||
}
|
}
|
||||||
|
|
||||||
themeLower := strings.ToLower(theme.Name)
|
themeLower := strings.ToLower(theme.Name)
|
||||||
|
@ -348,7 +511,7 @@ func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error {
|
||||||
v1Langs := v1.GetStringMap(languagesKey)
|
v1Langs := v1.GetStringMap(languagesKey)
|
||||||
for k := range v1Langs {
|
for k := range v1Langs {
|
||||||
langParamsKey := languagesKey + "." + k + "." + paramsKey
|
langParamsKey := languagesKey + "." + k + "." + paramsKey
|
||||||
mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2)
|
l.mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2)
|
||||||
}
|
}
|
||||||
v2Langs := v2.GetStringMap(languagesKey)
|
v2Langs := v2.GetStringMap(languagesKey)
|
||||||
for k := range v2Langs {
|
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
|
// Add menu definitions from theme not found in project
|
||||||
if v2.IsSet("menu") {
|
if v2.IsSet(menuKey) {
|
||||||
v2menus := v2.GetStringMap(menuKey)
|
v2menus := v2.GetStringMap(menuKey)
|
||||||
for k, v := range v2menus {
|
for k, v := range v2menus {
|
||||||
menuEntry := menuKey + "." + k
|
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) {
|
if !v2.IsSet(key) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -440,6 +603,7 @@ func loadDefaultSettingsFor(v *viper.Viper) error {
|
||||||
v.SetDefault("buildDrafts", false)
|
v.SetDefault("buildDrafts", false)
|
||||||
v.SetDefault("buildFuture", false)
|
v.SetDefault("buildFuture", false)
|
||||||
v.SetDefault("buildExpired", false)
|
v.SetDefault("buildExpired", false)
|
||||||
|
v.SetDefault("environment", hugo.EnvironmentProduction)
|
||||||
v.SetDefault("uglyURLs", false)
|
v.SetDefault("uglyURLs", false)
|
||||||
v.SetDefault("verbose", false)
|
v.SetDefault("verbose", false)
|
||||||
v.SetDefault("ignoreCache", false)
|
v.SetDefault("ignoreCache", false)
|
||||||
|
|
|
@ -247,8 +247,8 @@ map[string]interface {}{
|
||||||
b.AssertObject(`map[string]interface {}{
|
b.AssertObject(`map[string]interface {}{
|
||||||
"en": map[string]interface {}{
|
"en": map[string]interface {}{
|
||||||
"languagename": "English",
|
"languagename": "English",
|
||||||
"menu": map[string]interface {}{
|
"menus": map[string]interface {}{
|
||||||
"theme": []interface {}{
|
"theme": []map[string]interface {}{
|
||||||
map[string]interface {}{
|
map[string]interface {}{
|
||||||
"name": "menu-lang-en-theme",
|
"name": "menu-lang-en-theme",
|
||||||
},
|
},
|
||||||
|
@ -265,8 +265,8 @@ map[string]interface {}{
|
||||||
},
|
},
|
||||||
"nb": map[string]interface {}{
|
"nb": map[string]interface {}{
|
||||||
"languagename": "Norsk",
|
"languagename": "Norsk",
|
||||||
"menu": map[string]interface {}{
|
"menus": map[string]interface {}{
|
||||||
"theme": []interface {}{
|
"theme": []map[string]interface {}{
|
||||||
map[string]interface {}{
|
map[string]interface {}{
|
||||||
"name": "menu-lang-nb-theme",
|
"name": "menu-lang-nb-theme",
|
||||||
},
|
},
|
||||||
|
@ -287,23 +287,23 @@ map[string]interface {}{
|
||||||
|
|
||||||
b.AssertObject(`
|
b.AssertObject(`
|
||||||
map[string]interface {}{
|
map[string]interface {}{
|
||||||
"main": []interface {}{
|
"main": []map[string]interface {}{
|
||||||
map[string]interface {}{
|
map[string]interface {}{
|
||||||
"name": "menu-main-main",
|
"name": "menu-main-main",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"thememenu": []interface {}{
|
"thememenu": []map[string]interface {}{
|
||||||
map[string]interface {}{
|
map[string]interface {}{
|
||||||
"name": "menu-theme",
|
"name": "menu-theme",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"top": []interface {}{
|
"top": []map[string]interface {}{
|
||||||
map[string]interface {}{
|
map[string]interface {}{
|
||||||
"name": "menu-top-main",
|
"name": "menu-top-main",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
`, got["menu"])
|
`, got["menus"])
|
||||||
|
|
||||||
assert.Equal("https://example.com/", got["baseurl"])
|
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"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/config"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/publisher"
|
"github.com/gohugoio/hugo/publisher"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/herrors"
|
"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)
|
oldLangs, _ := h.Cfg.Get("languagesSorted").(langs.Languages)
|
||||||
|
|
||||||
if err := loadLanguageSettings(h.Cfg, oldLangs); err != nil {
|
if err := loadLanguageSettings(h.Cfg, oldLangs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: h.Cfg}
|
depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: cfg}
|
||||||
|
|
||||||
sites, err := createSitesFromConfig(depsCfg)
|
sites, err := createSitesFromConfig(depsCfg)
|
||||||
|
|
||||||
|
@ -412,9 +414,9 @@ func (h *HugoSites) toSiteInfos() []*SiteInfo {
|
||||||
type BuildCfg struct {
|
type BuildCfg struct {
|
||||||
// Reset site state before build. Use to force full rebuilds.
|
// Reset site state before build. Use to force full rebuilds.
|
||||||
ResetState bool
|
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.
|
// This is needed if new languages are added.
|
||||||
CreateSitesFromConfig bool
|
NewConfig config.Provider
|
||||||
// Skip rendering. Useful for testing.
|
// Skip rendering. Useful for testing.
|
||||||
SkipRender bool
|
SkipRender bool
|
||||||
// Use this to indicate what changed (for rebuilds).
|
// Use this to indicate what changed (for rebuilds).
|
||||||
|
|
|
@ -144,8 +144,8 @@ func (h *HugoSites) init(config *BuildCfg) error {
|
||||||
h.reset()
|
h.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.CreateSitesFromConfig {
|
if config.NewConfig != nil {
|
||||||
if err := h.createSitesFromConfig(); err != nil {
|
if err := h.createSitesFromConfig(config.NewConfig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,8 +154,8 @@ func (h *HugoSites) init(config *BuildCfg) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HugoSites) initRebuild(config *BuildCfg) error {
|
func (h *HugoSites) initRebuild(config *BuildCfg) error {
|
||||||
if config.CreateSitesFromConfig {
|
if config.NewConfig != nil {
|
||||||
return errors.New("Rebuild does not support 'CreateSitesFromConfig'.")
|
return errors.New("Rebuild does not support 'NewConfig'.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.ResetState {
|
if config.ResetState {
|
||||||
|
|
|
@ -11,14 +11,11 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/langs"
|
|
||||||
|
|
||||||
"github.com/fortytw2/leaktest"
|
"github.com/fortytw2/leaktest"
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
"github.com/gohugoio/hugo/hugofs"
|
"github.com/gohugoio/hugo/hugofs"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -661,9 +658,8 @@ title = "Svenska"
|
||||||
|
|
||||||
sites := b.H
|
sites := b.H
|
||||||
|
|
||||||
// Watching does not work with in-memory fs, so we trigger a reload manually
|
assert.NoError(b.LoadConfig())
|
||||||
assert.NoError(sites.Cfg.(*langs.Language).Cfg.(*viper.Viper).ReadInConfig())
|
err := b.H.Build(BuildCfg{NewConfig: b.Cfg})
|
||||||
err := b.H.Build(BuildCfg{CreateSitesFromConfig: true})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to rebuild sites: %s", err)
|
t.Fatalf("Failed to rebuild sites: %s", err)
|
||||||
|
@ -723,10 +719,9 @@ func TestChangeDefaultLanguage(t *testing.T) {
|
||||||
"DefaultContentLanguageInSubdir": false,
|
"DefaultContentLanguageInSubdir": false,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Watching does not work with in-memory fs, so we trigger a reload manually
|
assert.NoError(b.LoadConfig())
|
||||||
// This does not look pretty, so we should think of something else.
|
err := b.H.Build(BuildCfg{NewConfig: b.Cfg})
|
||||||
assert.NoError(b.H.Cfg.(*langs.Language).Cfg.(*viper.Viper).ReadInConfig())
|
|
||||||
err := b.H.Build(BuildCfg{CreateSitesFromConfig: true})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to rebuild sites: %s", err)
|
t.Fatalf("Failed to rebuild sites: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1647,7 +1647,12 @@ func (p *Page) Menus() PageMenus {
|
||||||
p.pageMenusInit.Do(func() {
|
p.pageMenusInit.Do(func() {
|
||||||
p.pageMenus = PageMenus{}
|
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()
|
link := p.RelPermalink()
|
||||||
|
|
||||||
me := MenuEntry{Page: p, Name: p.LinkTitle(), Weight: p.Weight, URL: link}
|
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.IsNode() }},
|
||||||
{func(n *Page) bool { return !n.IsPage() }},
|
{func(n *Page) bool { return !n.IsPage() }},
|
||||||
{func(n *Page) bool { return n.Scratch() != nil }},
|
{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()
|
n := s.newHomePage()
|
||||||
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"github.com/gohugoio/hugo/config"
|
"github.com/gohugoio/hugo/config"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ThemeConfig struct {
|
type ThemeConfig struct {
|
||||||
|
@ -73,18 +72,11 @@ func (c *themesCollector) add(name, configFilename string) (ThemeConfig, error)
|
||||||
var tc ThemeConfig
|
var tc ThemeConfig
|
||||||
|
|
||||||
if configFilename != "" {
|
if configFilename != "" {
|
||||||
v := viper.New()
|
var err error
|
||||||
v.SetFs(c.fs)
|
cfg, err = config.FromFile(c.fs, configFilename)
|
||||||
v.AutomaticEnv()
|
|
||||||
v.SetEnvPrefix("hugo")
|
|
||||||
v.SetConfigFile(configFilename)
|
|
||||||
|
|
||||||
err := v.ReadInConfig()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tc, err
|
return tc, nil
|
||||||
}
|
}
|
||||||
cfg = v
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tc = ThemeConfig{Name: name, ConfigFilename: configFilename, Cfg: cfg}
|
tc = ThemeConfig{Name: name, ConfigFilename: configFilename, Cfg: cfg}
|
||||||
|
|
|
@ -1226,7 +1226,7 @@ func (s *Site) initializeSiteInfo() error {
|
||||||
Data: &s.Data,
|
Data: &s.Data,
|
||||||
owner: s.owner,
|
owner: s.owner,
|
||||||
s: s,
|
s: s,
|
||||||
hugoInfo: hugo.NewInfo(),
|
hugoInfo: hugo.NewInfo(s.Cfg.GetString("environment")),
|
||||||
// TODO(bep) make this Menu and similar into delegate methods on SiteInfo
|
// TODO(bep) make this Menu and similar into delegate methods on SiteInfo
|
||||||
Taxonomies: s.Taxonomies,
|
Taxonomies: s.Taxonomies,
|
||||||
}
|
}
|
||||||
|
@ -1370,7 +1370,7 @@ func (s *Site) getMenusFromConfig() Menus {
|
||||||
|
|
||||||
ret := Menus{}
|
ret := Menus{}
|
||||||
|
|
||||||
if menus := s.Language.GetStringMap("menu"); menus != nil {
|
if menus := s.Language.GetStringMap("menus"); menus != nil {
|
||||||
for name, menu := range menus {
|
for name, menu := range menus {
|
||||||
m, err := cast.ToSliceE(menu)
|
m, err := cast.ToSliceE(menu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -322,6 +322,15 @@ func (s *sitesBuilder) CreateSites() *sitesBuilder {
|
||||||
return s
|
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 {
|
func (s *sitesBuilder) CreateSitesE() error {
|
||||||
s.addDefaults()
|
s.addDefaults()
|
||||||
s.writeFilePairs("content", s.contentFilePairs)
|
s.writeFilePairs("content", s.contentFilePairs)
|
||||||
|
@ -334,18 +343,9 @@ func (s *sitesBuilder) CreateSitesE() error {
|
||||||
s.writeFilePairs("i18n", s.i18nFilePairsAdded)
|
s.writeFilePairs("i18n", s.i18nFilePairsAdded)
|
||||||
|
|
||||||
if s.Cfg == nil {
|
if s.Cfg == nil {
|
||||||
cfg, _, err := LoadConfig(ConfigSourceDescriptor{Fs: s.Fs.Source, Filename: "config." + s.configFormat})
|
if err := s.LoadConfig(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
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})
|
sites, err := NewHugoSites(deps.DepsCfg{Fs: s.Fs, Cfg: s.Cfg, Logger: s.logger, Running: s.running})
|
||||||
|
|
|
@ -21,10 +21,10 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
packageName = "github.com/gohugoio/hugo"
|
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
|
// allow user to override go executable by running as GOEXE=xxx make ... on unix-like systems
|
||||||
var goexe = "go"
|
var goexe = "go"
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/chaseadamsio/goorgeous"
|
"github.com/chaseadamsio/goorgeous"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
@ -37,7 +38,21 @@ func UnmarshalToMap(data []byte, f Format) (map[string]interface{}, error) {
|
||||||
err := unmarshal(data, f, &m)
|
err := unmarshal(data, f, &m)
|
||||||
|
|
||||||
return m, err
|
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{}.
|
// Unmarshal will unmarshall data in format f into an interface{}.
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
package metadecoders
|
package metadecoders
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/parser/pageparser"
|
"github.com/gohugoio/hugo/parser/pageparser"
|
||||||
|
@ -34,6 +35,11 @@ const (
|
||||||
// into a Format. It returns an empty string for unknown formats.
|
// into a Format. It returns an empty string for unknown formats.
|
||||||
func FormatFromString(formatStr string) Format {
|
func FormatFromString(formatStr string) Format {
|
||||||
formatStr = strings.ToLower(formatStr)
|
formatStr = strings.ToLower(formatStr)
|
||||||
|
if strings.Contains(formatStr, ".") {
|
||||||
|
// Assume a filename
|
||||||
|
formatStr = strings.TrimPrefix(filepath.Ext(formatStr), ".")
|
||||||
|
|
||||||
|
}
|
||||||
switch formatStr {
|
switch formatStr {
|
||||||
case "yaml", "yml":
|
case "yaml", "yml":
|
||||||
return YAML
|
return YAML
|
||||||
|
|
|
@ -32,6 +32,7 @@ func TestFormatFromString(t *testing.T) {
|
||||||
{"yaml", YAML},
|
{"yaml", YAML},
|
||||||
{"yml", YAML},
|
{"yml", YAML},
|
||||||
{"toml", TOML},
|
{"toml", TOML},
|
||||||
|
{"config.toml", TOML},
|
||||||
{"tOMl", TOML},
|
{"tOMl", TOML},
|
||||||
{"org", ORG},
|
{"org", ORG},
|
||||||
{"foo", ""},
|
{"foo", ""},
|
||||||
|
|
Loading…
Reference in a new issue