summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gopkg.lock13
-rw-r--r--Gopkg.toml4
-rw-r--r--commands/commandeer.go177
-rw-r--r--commands/hugo.go173
-rw-r--r--commands/server.go68
-rw-r--r--helpers/path.go9
-rw-r--r--hugolib/case_insensitive_test.go2
-rw-r--r--hugolib/config.go204
-rw-r--r--hugolib/config_test.go314
-rw-r--r--hugolib/page_bundler_test.go3
-rw-r--r--hugolib/site.go2
-rw-r--r--hugolib/testhelpers_test.go41
12 files changed, 794 insertions, 216 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index 1b766e9ff..bf3c7dc6c 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -163,6 +163,7 @@
".",
"hcl/ast",
"hcl/parser",
+ "hcl/printer",
"hcl/scanner",
"hcl/strconv",
"hcl/token",
@@ -275,6 +276,12 @@
revision = "55d61fa8aa702f59229e6cff85793c22e580eaf5"
[[projects]]
+ name = "github.com/sanity-io/litter"
+ packages = ["."]
+ revision = "ae543b7ba8fd6af63e4976198f146e1348ae53c1"
+ version = "v1.1.0"
+
+[[projects]]
branch = "master"
name = "github.com/shurcooL/sanitized_anchor_name"
packages = ["."]
@@ -331,8 +338,8 @@
[[projects]]
name = "github.com/spf13/viper"
packages = ["."]
- revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
- version = "v1.0.0"
+ revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736"
+ version = "v1.0.2"
[[projects]]
name = "github.com/stretchr/testify"
@@ -417,6 +424,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "4657586103d844434bda6db23d03f30e2ae0db16dc48746b9559ce742902535a"
+ inputs-digest = "13ab39f8bfafadc12c05726e565ee3f3d94bf7d6c0e8adf04056de0691bf2dd6"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index 4e0cd5c6b..fc1af824b 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -141,3 +141,7 @@
name = "github.com/muesli/smartcrop"
branch = "master"
+
+[[constraint]]
+ name = "github.com/sanity-io/litter"
+ version = "1.1.0"
diff --git a/commands/commandeer.go b/commands/commandeer.go
index a69ce2084..e96c97814 100644
--- a/commands/commandeer.go
+++ b/commands/commandeer.go
@@ -14,6 +14,18 @@
package commands
import (
+ "os"
+ "path/filepath"
+ "sync"
+
+ "github.com/spf13/cobra"
+
+ "github.com/gohugoio/hugo/utils"
+
+ "github.com/spf13/afero"
+
+ "github.com/gohugoio/hugo/hugolib"
+
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers"
@@ -23,11 +35,22 @@ import (
type commandeer struct {
*deps.DepsCfg
+
+ subCmdVs []*cobra.Command
+
pathSpec *helpers.PathSpec
visitedURLs *types.EvictingStringQueue
staticDirsConfig []*src.Dirs
+ // We watch these for changes.
+ configFiles []string
+
+ doWithCommandeer func(c *commandeer) error
+
+ // We can do this only once.
+ fsCreate sync.Once
+
serverPorts []int
languages helpers.Languages
@@ -65,16 +88,158 @@ func (c *commandeer) initFs(fs *hugofs.Fs) error {
return nil
}
-func newCommandeer(cfg *deps.DepsCfg, running bool) (*commandeer, error) {
+func newCommandeer(running bool, doWithCommandeer func(c *commandeer) error, subCmdVs ...*cobra.Command) (*commandeer, error) {
+
+ c := &commandeer{
+ doWithCommandeer: doWithCommandeer,
+ subCmdVs: append([]*cobra.Command{hugoCmdV}, subCmdVs...),
+ visitedURLs: types.NewEvictingStringQueue(10)}
+
+ return c, c.loadConfig(running)
+}
+
+func (c *commandeer) loadConfig(running bool) error {
+
+ if c.DepsCfg == nil {
+ c.DepsCfg = &deps.DepsCfg{}
+ }
+
+ cfg := c.DepsCfg
+ c.configured = false
cfg.Running = running
- var languages helpers.Languages
+ var dir string
+ if source != "" {
+ dir, _ = filepath.Abs(source)
+ } else {
+ dir, _ = os.Getwd()
+ }
+
+ var sourceFs afero.Fs = hugofs.Os
+ if c.DepsCfg.Fs != nil {
+ sourceFs = c.DepsCfg.Fs.Source
+ }
+
+ config, configFiles, err := hugolib.LoadConfig(hugolib.ConfigSourceDescriptor{Fs: sourceFs, Path: source, WorkingDir: dir, Filename: cfgFile})
+ if err != nil {
+ return err
+ }
+
+ c.Cfg = config
+ c.configFiles = configFiles
+
+ for _, cmdV := range c.subCmdVs {
+ c.initializeFlags(cmdV)
+ }
+
+ if l, ok := c.Cfg.Get("languagesSorted").(helpers.Languages); ok {
+ c.languages = l
+ }
- if l, ok := cfg.Cfg.Get("languagesSorted").(helpers.Languages); ok {
- languages = l
+ if baseURL != "" {
+ config.Set("baseURL", baseURL)
}
- c := &commandeer{DepsCfg: cfg, languages: languages, visitedURLs: types.NewEvictingStringQueue(10)}
+ if c.doWithCommandeer != nil {
+ err = c.doWithCommandeer(c)
+ }
+
+ if err != nil {
+ return err
+ }
+
+ if len(disableKinds) > 0 {
+ c.Set("disableKinds", disableKinds)
+ }
+
+ logger, err := createLogger(cfg.Cfg)
+ if err != nil {
+ return err
+ }
+
+ cfg.Logger = logger
+
+ config.Set("logI18nWarnings", logI18nWarnings)
+
+ if theme != "" {
+ config.Set("theme", theme)
+ }
+
+ if themesDir != "" {
+ config.Set("themesDir", themesDir)
+ }
+
+ if destination != "" {
+ config.Set("publishDir", destination)
+ }
+
+ config.Set("workingDir", dir)
+
+ if contentDir != "" {
+ config.Set("contentDir", contentDir)
+ }
+
+ if layoutDir != "" {
+ config.Set("layoutDir", layoutDir)
+ }
+
+ if cacheDir != "" {
+ config.Set("cacheDir", cacheDir)
+ }
+
+ createMemFs := config.GetBool("renderToMemory")
+
+ if createMemFs {
+ // Rendering to memoryFS, publish to Root regardless of publishDir.
+ config.Set("publishDir", "/")
+ }
+
+ c.fsCreate.Do(func() {
+ fs := hugofs.NewFrom(sourceFs, config)
+
+ // Hugo writes the output to memory instead of the disk.
+ if createMemFs {
+ fs.Destination = new(afero.MemMapFs)
+ }
+
+ err = c.initFs(fs)
+ })
+
+ if err != nil {
+ return err
+ }
+
+ cacheDir = config.GetString("cacheDir")
+ if cacheDir != "" {
+ if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] {
+ cacheDir = cacheDir + helpers.FilePathSeparator
+ }
+ isDir, err := helpers.DirExists(cacheDir, sourceFs)
+ utils.CheckErr(cfg.Logger, err)
+ if !isDir {
+ mkdir(cacheDir)
+ }
+ config.Set("cacheDir", cacheDir)
+ } else {
+ config.Set("cacheDir", helpers.GetTempDir("hugo_cache", sourceFs))
+ }
+
+ cfg.Logger.INFO.Println("Using config file:", config.ConfigFileUsed())
+
+ themeDir := c.PathSpec().GetThemeDir()
+ if themeDir != "" {
+ if _, err := sourceFs.Stat(themeDir); os.IsNotExist(err) {
+ return newSystemError("Unable to find theme Directory:", themeDir)
+ }
+ }
+
+ themeVersionMismatch, minVersion := c.isThemeVsHugoVersionMismatch(sourceFs)
+
+ if themeVersionMismatch {
+ cfg.Logger.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
+ helpers.CurrentHugoVersion.ReleaseVersion(), minVersion)
+ }
+
+ return nil
- return c, nil
}
diff --git a/commands/hugo.go b/commands/hugo.go
index b041fad38..a5b2c8895 100644
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -25,8 +25,6 @@ import (
"golang.org/x/sync/errgroup"
- "github.com/gohugoio/hugo/hugofs"
-
"log"
"os"
"path/filepath"
@@ -44,7 +42,6 @@ import (
"regexp"
"github.com/fsnotify/fsnotify"
- "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/livereload"
@@ -55,7 +52,6 @@ import (
"github.com/spf13/fsync"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/nitro"
- "github.com/spf13/viper"
)
// Hugo represents the Hugo sites to build. This variable is exported as it
@@ -142,10 +138,6 @@ Complete documentation is available at http://gohugo.io/.`,
return err
}
- if buildWatch {
- c.watchConfig()
- }
-
return c.build()
},
}
@@ -301,129 +293,11 @@ func init() {
// InitializeConfig initializes a config file with sensible default configuration flags.
func InitializeConfig(running bool, doWithCommandeer func(c *commandeer) error, subCmdVs ...*cobra.Command) (*commandeer, error) {
- var cfg *deps.DepsCfg = &deps.DepsCfg{}
-
- // Init file systems. This may be changed at a later point.
- osFs := hugofs.Os
-
- config, err := hugolib.LoadConfig(hugolib.ConfigSourceDescriptor{Fs: osFs, Src: source, Name: cfgFile})
- if err != nil {
- return nil, err
- }
-
- // Init file systems. This may be changed at a later point.
- cfg.Cfg = config
-
- c, err := newCommandeer(cfg, running)
- if err != nil {
- return nil, err
- }
-
- for _, cmdV := range append([]*cobra.Command{hugoCmdV}, subCmdVs...) {
- c.initializeFlags(cmdV)
- }
-
- if baseURL != "" {
- config.Set("baseURL", baseURL)
- }
-
- if doWithCommandeer != nil {
- if err := doWithCommandeer(c); err != nil {
- return nil, err
- }
- }
-
- if len(disableKinds) > 0 {
- c.Set("disableKinds", disableKinds)
- }
-
- logger, err := createLogger(cfg.Cfg)
+ c, err := newCommandeer(running, doWithCommandeer, subCmdVs...)
if err != nil {
return nil, err
}
- cfg.Logger = logger
-
- config.Set("logI18nWarnings", logI18nWarnings)
-
- if theme != "" {
- config.Set("theme", theme)
- }
-
- if themesDir != "" {
- config.Set("themesDir", themesDir)
- }
-
- if destination != "" {
- config.Set("publishDir", destination)
- }
-
- var dir string
- if source != "" {
- dir, _ = filepath.Abs(source)
- } else {
- dir, _ = os.Getwd()
- }
- config.Set("workingDir", dir)
-
- if contentDir != "" {
- config.Set("contentDir", contentDir)
- }
-
- if layoutDir != "" {
- config.Set("layoutDir", layoutDir)
- }
-
- if cacheDir != "" {
- config.Set("cacheDir", cacheDir)
- }
-
- fs := hugofs.NewFrom(osFs, config)
-
- // Hugo writes the output to memory instead of the disk.
- // This is only used for benchmark testing. Cause the content is only visible
- // in memory.
- if config.GetBool("renderToMemory") {
- fs.Destination = new(afero.MemMapFs)
- // Rendering to memoryFS, publish to Root regardless of publishDir.
- config.Set("publishDir", "/")
- }
-
- cacheDir = config.GetString("cacheDir")
- if cacheDir != "" {
- if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] {
- cacheDir = cacheDir + helpers.FilePathSeparator
- }
- isDir, err := helpers.DirExists(cacheDir, fs.Source)
- utils.CheckErr(cfg.Logger, err)
- if !isDir {
- mkdir(cacheDir)
- }
- config.Set("cacheDir", cacheDir)
- } else {
- config.Set("cacheDir", helpers.GetTempDir("hugo_cache", fs.Source))
- }
-
- if err := c.initFs(fs); err != nil {
- return nil, err
- }
-
- cfg.Logger.INFO.Println("Using config file:", config.ConfigFileUsed())
-
- themeDir := c.PathSpec().GetThemeDir()
- if themeDir != "" {
- if _, err := cfg.Fs.Source.Stat(themeDir); os.IsNotExist(err) {
- return nil, newSystemError("Unable to find theme Directory:", themeDir)
- }
- }
-
- themeVersionMismatch, minVersion := c.isThemeVsHugoVersionMismatch()
-
- if themeVersionMismatch {
- cfg.Logger.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
- helpers.CurrentHugoVersion.ReleaseVersion(), minVersion)
- }
-
return c, nil
}
@@ -524,20 +398,6 @@ If you need to set this configuration value from the command line, set it via an
}
}
-func (c *commandeer) watchConfig() {
- v := c.Cfg.(*viper.Viper)
- v.WatchConfig()
- v.OnConfigChange(func(e fsnotify.Event) {
- c.Logger.FEEDBACK.Println("Config file changed:", e.Name)
- // Force a full rebuild
- utils.CheckErr(c.Logger, c.recreateAndBuildSites(true))
- if !c.Cfg.GetBool("disableLiveReload") {
- // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
- livereload.ForceRefresh()
- }
- })
-}
-
func (c *commandeer) fullBuild() error {
var (
g errgroup.Group
@@ -942,6 +802,7 @@ func (c *commandeer) resetAndBuildSites() (err error) {
func (c *commandeer) initSites() error {
if Hugo != nil {
+ Hugo.Cfg = c.Cfg
Hugo.Log.ResetLogCounters()
return nil
}
@@ -1009,6 +870,15 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
}
}
+ // Identifies changes to config (config.toml) files.
+ configSet := make(map[string]bool)
+
+ for _, configFile := range c.configFiles {
+ c.Logger.FEEDBACK.Println("Watching for config changes in", configFile)
+ watcher.Add(configFile)
+ configSet[configFile] = true
+ }
+
go func() {
for {
select {
@@ -1021,6 +891,21 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
// Special handling for symbolic links inside /content.
filtered := []fsnotify.Event{}
for _, ev := range evs {
+ if configSet[ev.Name] {
+ if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
+ continue
+ }
+ // Config file changed. Need full rebuild.
+ if err := c.loadConfig(true); err != nil {
+ jww.ERROR.Println("Failed to reload config:", err)
+ } else if err := c.recreateAndBuildSites(true); err != nil {
+ jww.ERROR.Println(err)
+ } else if !buildWatch && !c.Cfg.GetBool("disableLiveReload") {
+ livereload.ForceRefresh()
+ }
+ break
+ }
+
// Check the most specific first, i.e. files.
contentMapped := Hugo.ContentChanges.GetSymbolicLinkMappings(ev.Name)
if len(contentMapped) > 0 {
@@ -1212,7 +1097,7 @@ func pickOneWriteOrCreatePath(events []fsnotify.Event) string {
// isThemeVsHugoVersionMismatch returns whether the current Hugo version is
// less than the theme's min_version.
-func (c *commandeer) isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinVersion string) {
+func (c *commandeer) isThemeVsHugoVersionMismatch(fs afero.Fs) (mismatch bool, requiredMinVersion string) {
if !c.PathSpec().ThemeSet() {
return
}
@@ -1221,13 +1106,13 @@ func (c *commandeer) isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinV
path := filepath.Join(themeDir, "theme.toml")
- exists, err := helpers.Exists(path, c.Fs.Source)
+ exists, err := helpers.Exists(path, fs)
if err != nil || !exists {
return
}
- b, err := afero.ReadFile(c.Fs.Source, path)
+ b, err := afero.ReadFile(fs, path)
tomlMeta, err := parser.HandleTOMLMetaData(b)
diff --git a/commands/server.go b/commands/server.go
index 130ac18be..278ba7f37 100644
--- a/commands/server.go
+++ b/commands/server.go
@@ -24,6 +24,7 @@ import (
"runtime"
"strconv"
"strings"
+ "sync"
"syscall"
"time"
@@ -111,12 +112,16 @@ func init() {
}
+var serverPorts []int
+
func server(cmd *cobra.Command, args []string) error {
// If a Destination is provided via flag write to disk
if destination != "" {
renderToDisk = true
}
+ var serverCfgInit sync.Once
+
cfgInit := func(c *commandeer) error {
c.Set("renderToMemory", !renderToDisk)
if cmd.Flags().Changed("navigateToChanged") {
@@ -132,37 +137,42 @@ func server(cmd *cobra.Command, args []string) error {
c.Set("watch", true)
}
- serverPorts := make([]int, 1)
+ var err error
- if c.languages.IsMultihost() {
- if !serverAppend {
- return newSystemError("--appendPort=false not supported when in multihost mode")
+ // We can only do this once.
+ serverCfgInit.Do(func() {
+ serverPorts = make([]int, 1)
+
+ if c.languages.IsMultihost() {
+ if !serverAppend {
+ err = newSystemError("--appendPort=false not supported when in multihost mode")
+ }
+ serverPorts = make([]int, len(c.languages))
}
- serverPorts = make([]int, len(c.languages))
- }
- currentServerPort := serverPort
+ currentServerPort := serverPort
- for i := 0; i < len(serverPorts); i++ {
- l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(currentServerPort)))
- if err == nil {
- l.Close()
- serverPorts[i] = currentServerPort
- } else {
- if i == 0 && serverCmd.Flags().Changed("port") {
- // port set explicitly by user -- he/she probably meant it!
- return newSystemErrorF("Server startup failed: %s", err)
- }
- jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port")
- sp, err := helpers.FindAvailablePort()
- if err != nil {
- return newSystemError("Unable to find alternative port to use:", err)
+ for i := 0; i < len(serverPorts); i++ {
+ l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(currentServerPort)))
+ if err == nil {
+ l.Close()
+ serverPorts[i] = currentServerPort
+ } else {
+ if i == 0 && serverCmd.Flags().Changed("port") {
+ // port set explicitly by user -- he/she probably meant it!
+ err = newSystemErrorF("Server startup failed: %s", err)
+ }
+ jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port")
+ sp, err := helpers.FindAvailablePort()
+ if err != nil {
+ err = newSystemError("Unable to find alternative port to use:", err)
+ }
+ serverPorts[i] = sp.Port
}
- serverPorts[i] = sp.Port
- }
- currentServerPort = serverPorts[i] + 1
- }
+ currentServerPort = serverPorts[i] + 1
+ }
+ })
c.serverPorts = serverPorts
@@ -184,7 +194,7 @@ func server(cmd *cobra.Command, args []string) error {
baseURL, err := fixURL(language, baseURL, serverPort)
if err != nil {
- return err
+ return nil
}
if isMultiHost {
language.Set("baseURL", baseURL)
@@ -194,7 +204,7 @@ func server(cmd *cobra.Command, args []string) error {
}
}
- return nil
+ return err
}
@@ -215,10 +225,6 @@ func server(cmd *cobra.Command, args []string) error {
s.RegisterMediaTypes()
}
- if serverWatch {
- c.watchConfig()
- }
-
// Watch runs its own server as part of the routine
if serverWatch {
diff --git a/helpers/path.go b/helpers/path.go
index 44d53d018..0a8544357 100644
--- a/helpers/path.go
+++ b/helpers/path.go
@@ -154,11 +154,16 @@ func ReplaceExtension(path string, newExt string) string {
// AbsPathify creates an absolute path if given a relative path. If already
// absolute, the path is just cleaned.
func (p *PathSpec) AbsPathify(inPath string) string {
+ return AbsPathify(p.workingDir, inPath)
+}
+
+// AbsPathify creates an absolute path if given a working dir and arelative path.
+// If already absolute, the path is just cleaned.
+func AbsPathify(workingDir, inPath string) string {
if filepath.IsAbs(inPath) {
return filepath.Clean(inPath)
}
-
- return filepath.Join(p.workingDir, inPath)
+ return filepath.Join(workingDir, inPath)
}
// GetLayoutDirPath returns the absolute path to the layout file dir
diff --git a/hugolib/case_insensitive_test.go b/hugolib/case_insensitive_test.go
index 680a701aa..52ef198a5 100644
--- a/hugolib/case_insensitive_test.go
+++ b/hugolib/case_insensitive_test.go
@@ -149,7 +149,7 @@ func TestCaseInsensitiveConfigurationVariations(t *testing.T) {
caseMixingTestsWriteCommonSources(t, mm)
- cfg, err := LoadConfig(ConfigSourceDescriptor{Fs: mm})
+ cfg, _, err := LoadConfig(ConfigSourceDescriptor{Fs: mm, Filename: "config.toml"})
require.NoError(t, err)
fs := hugofs.NewFrom(mm, cfg)
diff --git a/hugolib/config.go b/hugolib/config.go
index e47e65435..6eca1a969 100644
--- a/hugolib/config.go
+++ b/hugolib/config.go
@@ -16,6 +16,7 @@ package hugolib
import (
"errors"
"fmt"
+ "path/filepath"
"io"
"strings"
@@ -28,64 +29,91 @@ import (
// ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.).
type ConfigSourceDescriptor struct {
- Fs afero.Fs
- Src string
- Name string
+ Fs afero.Fs
+
+ // Full path to the config file to use, i.e. /my/project/config.toml
+ Filename string
+
+ // The path to the directory to look for configuration. Is used if Filename is not
+ // set.
+ Path string
+
+ // The project's working dir. Is used to look for additional theme config.
+ WorkingDir string
}
func (d ConfigSourceDescriptor) configFilenames() []string {
- return strings.Split(d.Name, ",")
+ return strings.Split(d.Filename, ",")
}
// LoadConfigDefault is a convenience method to load the default "config.toml" config.
func LoadConfigDefault(fs afero.Fs) (*viper.Viper, error) {
- return LoadConfig(ConfigSourceDescriptor{Fs: fs, Name: "config.toml"})
+ v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs, Filename: "config.toml"})
+ return v, err
}
// LoadConfig loads Hugo configuration into a new Viper and then adds
// a set of defaults.
-func LoadConfig(d ConfigSourceDescriptor) (*viper.Viper, error) {
+func LoadConfig(d ConfigSourceDescriptor) (*viper.Viper, []string, error) {
+ var configFiles []string
+
fs := d.Fs
v := viper.New()
v.SetFs(fs)
- if d.Name == "" {
- d.Name = "config.toml"
- }
-
- if d.Src == "" {
- d.Src = "."
+ if d.Path == "" {
+ d.Path = "."
}
configFilenames := d.configFilenames()
v.AutomaticEnv()
v.SetEnvPrefix("hugo")
v.SetConfigFile(configFilenames[0])
- v.AddConfigPath(d.Src)
+ v.AddConfigPath(d.Path)
err := v.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigParseError); ok {
- return nil, err
+ return nil, configFiles, err
}
- return nil, fmt.Errorf("Unable to locate Config file. Perhaps you need to create a new site.\n Run `hugo help new` for details. (%s)\n", err)
+ return nil, configFiles, fmt.Errorf("Unable to locate Config file. Perhaps you need to create a new site.\n Run `hugo help new` for details. (%s)\n", err)
+ }
+
+ 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, fmt.Errorf("Unable to open Config file.\n (%s)\n", err)
+ return nil, configFiles, fmt.Errorf("Unable to open Config file.\n (%s)\n", err)
}
if err = v.MergeConfig(r); err != nil {
- return nil, fmt.Errorf("Unable to parse/merge Config file (%s).\n (%s)\n", configFile, err)
+ return nil, configFiles, fmt.Errorf("Unable to parse/merge Config file (%s).\n (%s)\n", configFile, err)
}
+ configFiles = append(configFiles, configFile)
}
if err := loadDefaultSettingsFor(v); err != nil {
- return v, err
+ return v, configFiles, err
}
- return v, nil
+ themeConfigFile, err := loadThemeConfig(d, v)
+ if err != nil {
+ return v, configFiles, err
+ }
+
+ if themeConfigFile != "" {
+ configFiles = append(configFiles, themeConfigFile)
+ }
+
+ if err := loadLanguageSettings(v, nil); err != nil {
+ return v, configFiles, err
+ }
+
+ return v, configFiles, nil
+
}
func loadLanguageSettings(cfg config.Provider, oldLangs helpers.Languages) error {
@@ -201,6 +229,142 @@ func loadLanguageSettings(cfg config.Provider, oldLangs helpers.Languages) error
return nil
}
+func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) (string, error) {
+
+ theme := v1.GetString("theme")
+ if theme == "" {
+ return "", nil
+ }
+
+ themesDir := helpers.AbsPathify(d.WorkingDir, v1.GetString("themesDir"))
+ configDir := filepath.Join(themesDir, theme)
+
+ var (
+ configPath string
+ exists bool
+ err error
+ )
+
+ // Viper supports more, but this is the sub-set supported by Hugo.
+ for _, configFormats := range []string{"toml", "yaml", "yml", "json"} {
+ configPath = filepath.Join(configDir, "config."+configFormats)
+ exists, err = helpers.Exists(configPath, d.Fs)
+ if err != nil {
+ return "", err
+ }
+ if exists {
+ break
+ }
+ }
+
+ if !exists {
+ // No theme config set.
+ return "", nil
+ }
+
+ v2 := viper.New()
+ v2.SetFs(d.Fs)
+ v2.AutomaticEnv()
+ v2.SetEnvPrefix("hugo")
+ v2.SetConfigFile(configPath)
+
+ err = v2.ReadInConfig()
+ if err != nil {
+ return "", err
+ }
+
+ const (
+ paramsKey = "params"
+ languagesKey = "languages"
+ menuKey = "menu"
+ )
+
+ for _, key := range []string{paramsKey, "outputformats", "mediatypes"} {
+ mergeStringMapKeepLeft("", key, v1, v2)
+ }
+
+ themeLower := strings.ToLower(theme)
+ themeParamsNamespace := paramsKey + "." + themeLower
+
+ // Set namespaced params
+ if v2.IsSet(paramsKey) && !v1.IsSet(themeParamsNamespace) {
+ // Set it in the default store to make sure it gets in the same or
+ // behind the others.
+ v1.SetDefault(themeParamsNamespace, v2.Get(paramsKey))
+ }
+
+ // Only add params and new menu entries, we do not add language definitions.
+ if v1.IsSet(languagesKey) && v2.IsSet(languagesKey) {
+ v1Langs := v1.GetStringMap(languagesKey)
+ for k, _ := range v1Langs {
+ langParamsKey := languagesKey + "." + k + "." + paramsKey
+ mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2)
+ }
+ v2Langs := v2.GetStringMap(languagesKey)
+ for k, _ := range v2Langs {
+ if k == "" {
+ continue
+ }
+ langParamsKey := languagesKey + "." + k + "." + paramsKey
+ langParamsThemeNamespace := langParamsKey + "." + themeLower
+ // Set namespaced params
+ if v2.IsSet(langParamsKey) && !v1.IsSet(langParamsThemeNamespace) {
+ v1.SetDefault(langParamsThemeNamespace, v2.Get(langParamsKey))
+ }
+
+ langMenuKey := languagesKey + "." + k + "." + menuKey
+ if v2.IsSet(langMenuKey) {
+ // Only add if not in the main config.
+ v2menus := v2.GetStringMap(langMenuKey)
+ for k, v := range v2menus {
+ menuEntry := menuKey + "." + k
+ menuLangEntry := langMenuKey + "." + k
+ if !v1.IsSet(menuEntry) && !v1.IsSet(menuLangEntry) {
+ v1.Set(menuLangEntry, v)
+ }
+ }
+ }
+ }
+ }
+
+ // Add menu definitions from theme not found in project
+ if v2.IsSet("menu") {
+ v2menus := v2.GetStringMap(menuKey)
+ for k, v := range v2menus {
+ menuEntry := menuKey + "." + k
+ if !v1.IsSet(menuEntry) {
+ v1.SetDefault(menuEntry, v)
+ }
+ }
+ }
+
+ return v2.ConfigFileUsed(), nil
+