Permalink
Browse files

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
  • Loading branch information...
bep committed Nov 15, 2018
1 parent 2564189 commit 7829474088f835251f04caa1121d47e35fe89f7e
@@ -249,14 +249,16 @@ 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 {
c.ftch.flagsToConfig(cfg)
}

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"
)

const (
EnvironmentDevelopment = "development"
EnvironmentProduction = "production"
)

var (
// CommitHash contains the current Git revision. Use make to build to make
// commitHash contains the current Git revision. Use make to build to make
// sure this gets set.
CommitHash string
commitHash string

// BuildDate contains the date of the current build.
BuildDate 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,23 +130,21 @@ 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"
}

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)

}

Oops, something went wrong.

0 comments on commit 7829474

Please sign in to comment.