Allow themes to define output formats, media types and params

This allows a `config.toml` (or `yaml`, ´yml`, or `json`)  in the theme to set:

1) `params` (but cannot override params in project. Will also get its own "namespace", i.e. `{{ .Site.Params.mytheme.my_param }}` will be the same as `{{ .Site.Params.my_param }}` providing that the main project does not define a param with that key.
2) `menu` -- but cannot redefine/add menus in the project. Must create its own menus with its own identifiers.
3) `languages` -- only `params` and `menu`. Same rules as above.
4) **new** `outputFormats`
5) **new** `mediaTypes`

This should help with the "theme portability" issue and people having to copy and paste lots of setting into their projects.

Fixes #4490
This commit is contained in:
Bjørn Erik Pedersen 2018-03-18 11:07:24 +01:00
parent 3d1a6e109c
commit e9c7b6205f
No known key found for this signature in database
GPG key ID: 330E6E2BD4859D8F
12 changed files with 796 additions and 218 deletions

View file

@ -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})
c, err := newCommandeer(running, doWithCommandeer, subCmdVs...)
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)
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)