all: Refactor to nonglobal Viper, i18n etc.

This is a final rewrite that removes all the global state in Hugo, which also enables
the use if `t.Parallel` in tests.

Updates #2701
Fixes #3016
This commit is contained in:
Bjørn Erik Pedersen
2017-02-05 10:20:06 +07:00
parent e34af6ee30
commit 93ca7c9e95
99 changed files with 2843 additions and 2458 deletions

View File

@@ -54,7 +54,7 @@ func benchmark(cmd *cobra.Command, args []string) error {
return err
}
c := commandeer{cfg}
c := newCommandeer(cfg)
var memProf *os.File
if memProfileFile != "" {

46
commands/commandeer.go Normal file
View File

@@ -0,0 +1,46 @@
// Copyright 2017 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 commands
import (
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
)
type commandeer struct {
*deps.DepsCfg
pathSpec *helpers.PathSpec
configured bool
}
func (c *commandeer) Set(key string, value interface{}) {
if c.configured {
panic("commandeer cannot be changed")
}
c.Cfg.Set(key, value)
}
// PathSpec lazily creates a new PathSpec, as all the paths must
// be configured before it is created.
func (c *commandeer) PathSpec() *helpers.PathSpec {
c.configured = true
if c.pathSpec == nil {
c.pathSpec = helpers.NewPathSpec(c.Fs, c.Cfg)
}
return c.pathSpec
}
func newCommandeer(cfg *deps.DepsCfg) *commandeer {
return &commandeer{DepsCfg: cfg}
}

View File

@@ -21,11 +21,8 @@ import (
"github.com/spf13/cast"
"github.com/spf13/cobra"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugolib"
"github.com/spf13/hugo/parser"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
)
var outputDir string
@@ -86,7 +83,7 @@ func convertContents(mark rune) error {
return err
}
h, err := hugolib.NewHugoSitesFromConfiguration(cfg)
h, err := hugolib.NewHugoSites(*cfg)
if err != nil {
return err
}
@@ -104,10 +101,10 @@ func convertContents(mark rune) error {
return errors.New("No source files found")
}
contentDir := helpers.AbsPathify(viper.GetString("contentDir"))
jww.FEEDBACK.Println("processing", len(site.Source.Files()), "content files")
contentDir := site.PathSpec.AbsPathify(site.Cfg.GetString("contentDir"))
site.Log.FEEDBACK.Println("processing", len(site.Source.Files()), "content files")
for _, file := range site.Source.Files() {
jww.INFO.Println("Attempting to convert", file.LogicalName())
site.Log.INFO.Println("Attempting to convert", file.LogicalName())
page, err := site.NewPage(file.LogicalName())
if err != nil {
return err
@@ -115,12 +112,12 @@ func convertContents(mark rune) error {
psr, err := parser.ReadFrom(file.Contents)
if err != nil {
jww.ERROR.Println("Error processing file:", file.Path())
site.Log.ERROR.Println("Error processing file:", file.Path())
return err
}
metadata, err := psr.Metadata()
if err != nil {
jww.ERROR.Println("Error processing file:", file.Path())
site.Log.ERROR.Println("Error processing file:", file.Path())
return err
}
@@ -139,7 +136,7 @@ func convertContents(mark rune) error {
page.SetDir(filepath.Join(contentDir, file.Dir()))
page.SetSourceContent(psr.Content())
if err = page.SetSourceMetaData(metadata, mark); err != nil {
jww.ERROR.Printf("Failed to set source metadata for file %q: %s. For more info see For more info see https://github.com/spf13/hugo/issues/2458", page.FullFilePath(), err)
site.Log.ERROR.Printf("Failed to set source metadata for file %q: %s. For more info see For more info see https://github.com/spf13/hugo/issues/2458", page.FullFilePath(), err)
continue
}
@@ -153,7 +150,7 @@ func convertContents(mark rune) error {
return fmt.Errorf("Failed to save file %q: %s", page.FullFilePath(), err)
}
} else {
jww.FEEDBACK.Println("Unsafe operation not allowed, use --unsafe or set a different output path")
site.Log.FEEDBACK.Println("Unsafe operation not allowed, use --unsafe or set a different output path")
}
}
}

View File

@@ -27,8 +27,7 @@ import (
"sync"
"time"
"github.com/spf13/hugo/tpl"
"github.com/spf13/hugo/config"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/parser"
@@ -51,10 +50,6 @@ import (
"github.com/spf13/viper"
)
type commandeer struct {
deps.DepsCfg
}
// Hugo represents the Hugo sites to build. This variable is exported as it
// is used by at least one external library (the Hugo caddy plugin). We should
// provide a cleaner external API, but until then, this is it.
@@ -64,7 +59,6 @@ var Hugo *hugolib.HugoSites
// for benchmark testing etc. via the CLI commands.
func Reset() error {
Hugo = nil
viper.Reset()
return nil
}
@@ -124,10 +118,10 @@ Complete documentation is available at http://gohugo.io/.`,
return err
}
c := commandeer{cfg}
c := newCommandeer(cfg)
if buildWatch {
viper.Set("disableLiveReload", true)
cfg.Cfg.Set("disableLiveReload", true)
c.watchConfig()
}
@@ -148,16 +142,17 @@ var (
)
var (
baseURL string
cacheDir string
contentDir string
layoutDir string
cfgFile string
destination string
logFile string
theme string
themesDir string
source string
baseURL string
cacheDir string
contentDir string
layoutDir string
cfgFile string
destination string
logFile string
theme string
themesDir string
source string
logI18nWarnings bool
)
// Execute adds all child commands to the root command HugoCmd and sets flags appropriately.
@@ -242,7 +237,7 @@ func initHugoBuildCommonFlags(cmd *cobra.Command) {
cmd.Flags().BoolP("forceSyncStatic", "", false, "Copy all files when static is changed.")
cmd.Flags().BoolP("noTimes", "", false, "Don't sync modification time of files")
cmd.Flags().BoolP("noChmod", "", false, "Don't sync permission mode of files")
cmd.Flags().BoolVarP(&tpl.Logi18nWarnings, "i18n-warnings", "", false, "Print missing translations")
cmd.Flags().BoolVarP(&logI18nWarnings, "i18n-warnings", "", false, "Print missing translations")
// Set bash-completion.
// Each flag must first be defined before using the SetAnnotation() call.
@@ -275,39 +270,56 @@ func init() {
}
// InitializeConfig initializes a config file with sensible default configuration flags.
func InitializeConfig(subCmdVs ...*cobra.Command) (deps.DepsCfg, error) {
func InitializeConfig(subCmdVs ...*cobra.Command) (*deps.DepsCfg, error) {
var cfg deps.DepsCfg
var cfg *deps.DepsCfg = &deps.DepsCfg{}
if err := hugolib.LoadGlobalConfig(source, cfgFile); err != nil {
// Init file systems. This may be changed at a later point.
osFs := hugofs.Os
config, err := hugolib.LoadConfig(osFs, source, cfgFile)
if err != nil {
return cfg, err
}
cfg.Cfg = config
c := newCommandeer(cfg)
for _, cmdV := range append([]*cobra.Command{hugoCmdV}, subCmdVs...) {
initializeFlags(cmdV)
c.initializeFlags(cmdV)
}
logger, err := createLogger(cfg.Cfg)
if err != nil {
return cfg, err
}
cfg.Logger = logger
config.Set("logI18nWarnings", logI18nWarnings)
if baseURL != "" {
if !strings.HasSuffix(baseURL, "/") {
baseURL = baseURL + "/"
}
viper.Set("baseURL", baseURL)
config.Set("baseURL", baseURL)
}
if !viper.GetBool("relativeURLs") && viper.GetString("baseURL") == "" {
jww.ERROR.Println("No 'baseURL' set in configuration or as a flag. Features like page menus will not work without one.")
if !config.GetBool("relativeURLs") && config.GetString("baseURL") == "" {
cfg.Logger.ERROR.Println("No 'baseURL' set in configuration or as a flag. Features like page menus will not work without one.")
}
if theme != "" {
viper.Set("theme", theme)
config.Set("theme", theme)
}
if themesDir != "" {
viper.Set("themesDir", themesDir)
config.Set("themesDir", themesDir)
}
if destination != "" {
viper.Set("publishDir", destination)
config.Set("publishDir", destination)
}
var dir string
@@ -316,24 +328,32 @@ func InitializeConfig(subCmdVs ...*cobra.Command) (deps.DepsCfg, error) {
} else {
dir, _ = os.Getwd()
}
viper.Set("workingDir", dir)
config.Set("workingDir", dir)
cfg.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 renderToMemory {
c.Fs.Destination = new(afero.MemMapFs)
// Rendering to memoryFS, publish to Root regardless of publishDir.
c.Set("publishDir", "/")
}
if contentDir != "" {
viper.Set("contentDir", contentDir)
config.Set("contentDir", contentDir)
}
if layoutDir != "" {
viper.Set("layoutDir", layoutDir)
config.Set("layoutDir", layoutDir)
}
if cacheDir != "" {
viper.Set("cacheDir", cacheDir)
config.Set("cacheDir", cacheDir)
}
// Init file systems. This may be changed at a later point.
cfg.Fs = hugofs.NewDefault()
cacheDir = viper.GetString("cacheDir")
cacheDir = config.GetString("cacheDir")
if cacheDir != "" {
if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] {
cacheDir = cacheDir + helpers.FilePathSeparator
@@ -343,39 +363,32 @@ func InitializeConfig(subCmdVs ...*cobra.Command) (deps.DepsCfg, error) {
if !isDir {
mkdir(cacheDir)
}
viper.Set("cacheDir", cacheDir)
config.Set("cacheDir", cacheDir)
} else {
viper.Set("cacheDir", helpers.GetTempDir("hugo_cache", cfg.Fs.Source))
config.Set("cacheDir", helpers.GetTempDir("hugo_cache", cfg.Fs.Source))
}
jww.INFO.Println("Using config file:", viper.ConfigFileUsed())
cfg.Logger.INFO.Println("Using config file:", viper.ConfigFileUsed())
themeDir := helpers.GetThemeDir()
themeDir := c.PathSpec().GetThemeDir()
if themeDir != "" {
if _, err := cfg.Fs.Source.Stat(themeDir); os.IsNotExist(err) {
return cfg, newSystemError("Unable to find theme Directory:", themeDir)
}
}
themeVersionMismatch, minVersion := isThemeVsHugoVersionMismatch(cfg.Fs.Source)
themeVersionMismatch, minVersion := c.isThemeVsHugoVersionMismatch()
if themeVersionMismatch {
jww.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
cfg.Logger.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
helpers.HugoReleaseVersion(), minVersion)
}
logger, err := createLogger()
if err != nil {
return cfg, err
}
cfg.Logger = logger
return cfg, nil
}
func createLogger() (*jww.Notepad, error) {
func createLogger(cfg config.Provider) (*jww.Notepad, error) {
var (
logHandle = ioutil.Discard
outHandle = os.Stdout
@@ -383,11 +396,11 @@ func createLogger() (*jww.Notepad, error) {
logThreshold = jww.LevelWarn
)
if verboseLog || logging || (viper.IsSet("logFile") && viper.GetString("logFile") != "") {
if verboseLog || logging || (cfg.GetString("logFile") != "") {
var err error
if viper.IsSet("logFile") && viper.GetString("logFile") != "" {
path := viper.GetString("logFile")
if cfg.GetString("logFile") != "" {
path := cfg.GetString("logFile")
logHandle, err = os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
return nil, newSystemError("Failed to open log file:", path, err)
@@ -398,7 +411,7 @@ func createLogger() (*jww.Notepad, error) {
return nil, newSystemError(err)
}
}
} else if !quiet && viper.GetBool("verbose") {
} else if !quiet && cfg.GetBool("verbose") {
stdoutThreshold = jww.LevelInfo
}
@@ -409,7 +422,7 @@ func createLogger() (*jww.Notepad, error) {
return jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime), nil
}
func initializeFlags(cmd *cobra.Command) {
func (c *commandeer) initializeFlags(cmd *cobra.Command) {
persFlagKeys := []string{"verbose", "logFile"}
flagKeys := []string{
"cleanDestinationDir",
@@ -432,21 +445,21 @@ func initializeFlags(cmd *cobra.Command) {
}
for _, key := range persFlagKeys {
setValueFromFlag(cmd.PersistentFlags(), key)
c.setValueFromFlag(cmd.PersistentFlags(), key)
}
for _, key := range flagKeys {
setValueFromFlag(cmd.Flags(), key)
c.setValueFromFlag(cmd.Flags(), key)
}
}
func setValueFromFlag(flags *flag.FlagSet, key string) {
if flagChanged(flags, key) {
func (c *commandeer) setValueFromFlag(flags *flag.FlagSet, key string) {
if c.flagChanged(flags, key) {
f := flags.Lookup(key)
viper.Set(key, f.Value.String())
c.Set(key, f.Value.String())
}
}
func flagChanged(flags *flag.FlagSet, key string) bool {
func (c *commandeer) flagChanged(flags *flag.FlagSet, key string) bool {
flag := flags.Lookup(key)
if flag == nil {
return false
@@ -454,31 +467,23 @@ func flagChanged(flags *flag.FlagSet, key string) bool {
return flag.Changed
}
func (c commandeer) watchConfig() {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
jww.FEEDBACK.Println("Config file changed:", e.Name)
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.recreateAndBuildSites(true))
if !viper.GetBool("disableLiveReload") {
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) build(watches ...bool) error {
// 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 renderToMemory {
c.Fs.Destination = new(afero.MemMapFs)
// Rendering to memoryFS, publish to Root regardless of publishDir.
viper.Set("publishDir", "/")
}
func (c *commandeer) build(watches ...bool) error {
if err := c.copyStatic(); err != nil {
return fmt.Errorf("Error copying static files to %s: %s", helpers.AbsPathify(viper.GetString("publishDir")), err)
return fmt.Errorf("Error copying static files to %s: %s", c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")), err)
}
watch := false
if len(watches) > 0 && watches[0] {
@@ -489,37 +494,35 @@ func (c commandeer) build(watches ...bool) error {
}
if buildWatch {
jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("contentDir")))
jww.FEEDBACK.Println("Press Ctrl+C to stop")
c.Logger.FEEDBACK.Println("Watching for changes in", c.PathSpec().AbsPathify(c.Cfg.GetString("contentDir")))
c.Logger.FEEDBACK.Println("Press Ctrl+C to stop")
utils.CheckErr(c.newWatcher(0))
}
return nil
}
func (c commandeer) getStaticSourceFs() afero.Fs {
func (c *commandeer) getStaticSourceFs() afero.Fs {
source := c.Fs.Source
pathSpec := helpers.NewPathSpec(c.Fs, viper.GetViper())
themeDir, err := pathSpec.GetThemeStaticDirPath()
staticDir := helpers.GetStaticDirPath() + helpers.FilePathSeparator
themeDir, err := c.PathSpec().GetThemeStaticDirPath()
staticDir := c.PathSpec().GetStaticDirPath() + helpers.FilePathSeparator
useTheme := true
useStatic := true
if err != nil {
if err != helpers.ErrThemeUndefined {
jww.WARN.Println(err)
c.Logger.WARN.Println(err)
}
useTheme = false
} else {
if _, err := source.Stat(themeDir); os.IsNotExist(err) {
jww.WARN.Println("Unable to find Theme Static Directory:", themeDir)
c.Logger.WARN.Println("Unable to find Theme Static Directory:", themeDir)
useTheme = false
}
}
if _, err := source.Stat(staticDir); os.IsNotExist(err) {
jww.WARN.Println("Unable to find Static Directory:", staticDir)
c.Logger.WARN.Println("Unable to find Static Directory:", staticDir)
useStatic = false
}
@@ -528,25 +531,25 @@ func (c commandeer) getStaticSourceFs() afero.Fs {
}
if !useStatic {
jww.INFO.Println(themeDir, "is the only static directory available to sync from")
c.Logger.INFO.Println(themeDir, "is the only static directory available to sync from")
return afero.NewReadOnlyFs(afero.NewBasePathFs(source, themeDir))
}
if !useTheme {
jww.INFO.Println(staticDir, "is the only static directory available to sync from")
c.Logger.INFO.Println(staticDir, "is the only static directory available to sync from")
return afero.NewReadOnlyFs(afero.NewBasePathFs(source, staticDir))
}
jww.INFO.Println("using a UnionFS for static directory comprised of:")
jww.INFO.Println("Base:", themeDir)
jww.INFO.Println("Overlay:", staticDir)
c.Logger.INFO.Println("using a UnionFS for static directory comprised of:")
c.Logger.INFO.Println("Base:", themeDir)
c.Logger.INFO.Println("Overlay:", staticDir)
base := afero.NewReadOnlyFs(afero.NewBasePathFs(source, themeDir))
overlay := afero.NewReadOnlyFs(afero.NewBasePathFs(source, staticDir))
return afero.NewCopyOnWriteFs(base, overlay)
}
func (c commandeer) copyStatic() error {
publishDir := helpers.AbsPathify(viper.GetString("publishDir")) + helpers.FilePathSeparator
func (c *commandeer) copyStatic() error {
publishDir := c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")) + helpers.FilePathSeparator
// If root, remove the second '/'
if publishDir == "//" {
@@ -557,22 +560,22 @@ func (c commandeer) copyStatic() error {
staticSourceFs := c.getStaticSourceFs()
if staticSourceFs == nil {
jww.WARN.Println("No static directories found to sync")
c.Logger.WARN.Println("No static directories found to sync")
return nil
}
syncer := fsync.NewSyncer()
syncer.NoTimes = viper.GetBool("noTimes")
syncer.NoChmod = viper.GetBool("noChmod")
syncer.NoTimes = c.Cfg.GetBool("noTimes")
syncer.NoChmod = c.Cfg.GetBool("noChmod")
syncer.SrcFs = staticSourceFs
syncer.DestFs = c.Fs.Destination
// Now that we are using a unionFs for the static directories
// We can effectively clean the publishDir on initial sync
syncer.Delete = viper.GetBool("cleanDestinationDir")
syncer.Delete = c.Cfg.GetBool("cleanDestinationDir")
if syncer.Delete {
jww.INFO.Println("removing all files from destination that don't exist in static dirs")
c.Logger.INFO.Println("removing all files from destination that don't exist in static dirs")
}
jww.INFO.Println("syncing static files to", publishDir)
c.Logger.INFO.Println("syncing static files to", publishDir)
// because we are using a baseFs (to get the union right).
// set sync src to root
@@ -580,37 +583,37 @@ func (c commandeer) copyStatic() error {
}
// getDirList provides NewWatcher() with a list of directories to watch for changes.
func (c commandeer) getDirList() []string {
func (c *commandeer) getDirList() []string {
var a []string
dataDir := helpers.AbsPathify(viper.GetString("dataDir"))
i18nDir := helpers.AbsPathify(viper.GetString("i18nDir"))
layoutDir := helpers.AbsPathify(viper.GetString("layoutDir"))
staticDir := helpers.AbsPathify(viper.GetString("staticDir"))
dataDir := c.PathSpec().AbsPathify(c.Cfg.GetString("dataDir"))
i18nDir := c.PathSpec().AbsPathify(c.Cfg.GetString("i18nDir"))
layoutDir := c.PathSpec().AbsPathify(c.Cfg.GetString("layoutDir"))
staticDir := c.PathSpec().AbsPathify(c.Cfg.GetString("staticDir"))
var themesDir string
if helpers.ThemeSet() {
themesDir = helpers.AbsPathify(viper.GetString("themesDir") + "/" + viper.GetString("theme"))
if c.PathSpec().ThemeSet() {
themesDir = c.PathSpec().AbsPathify(c.Cfg.GetString("themesDir") + "/" + c.Cfg.GetString("theme"))
}
walker := func(path string, fi os.FileInfo, err error) error {
if err != nil {
if path == dataDir && os.IsNotExist(err) {
jww.WARN.Println("Skip dataDir:", err)
c.Logger.WARN.Println("Skip dataDir:", err)
return nil
}
if path == i18nDir && os.IsNotExist(err) {
jww.WARN.Println("Skip i18nDir:", err)
c.Logger.WARN.Println("Skip i18nDir:", err)
return nil
}
if path == layoutDir && os.IsNotExist(err) {
jww.WARN.Println("Skip layoutDir:", err)
c.Logger.WARN.Println("Skip layoutDir:", err)
return nil
}
if path == staticDir && os.IsNotExist(err) {
jww.WARN.Println("Skip staticDir:", err)
c.Logger.WARN.Println("Skip staticDir:", err)
return nil
}
@@ -619,23 +622,23 @@ func (c commandeer) getDirList() []string {
return nil
}
jww.ERROR.Println("Walker: ", err)
c.Logger.ERROR.Println("Walker: ", err)
return nil
}
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
link, err := filepath.EvalSymlinks(path)
if err != nil {
jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err)
c.Logger.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err)
return nil
}
linkfi, err := c.Fs.Source.Stat(link)
if err != nil {
jww.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
c.Logger.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
return nil
}
if !linkfi.Mode().IsRegular() {
jww.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", path)
c.Logger.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", path)
}
return nil
}
@@ -651,12 +654,12 @@ func (c commandeer) getDirList() []string {
}
helpers.SymbolicWalk(c.Fs.Source, dataDir, walker)
helpers.SymbolicWalk(c.Fs.Source, helpers.AbsPathify(viper.GetString("contentDir")), walker)
helpers.SymbolicWalk(c.Fs.Source, c.PathSpec().AbsPathify(c.Cfg.GetString("contentDir")), walker)
helpers.SymbolicWalk(c.Fs.Source, i18nDir, walker)
helpers.SymbolicWalk(c.Fs.Source, helpers.AbsPathify(viper.GetString("layoutDir")), walker)
helpers.SymbolicWalk(c.Fs.Source, c.PathSpec().AbsPathify(c.Cfg.GetString("layoutDir")), walker)
helpers.SymbolicWalk(c.Fs.Source, staticDir, walker)
if helpers.ThemeSet() {
if c.PathSpec().ThemeSet() {
helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "layouts"), walker)
helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "static"), walker)
helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "i18n"), walker)
@@ -667,32 +670,31 @@ func (c commandeer) getDirList() []string {
return a
}
func (c commandeer) recreateAndBuildSites(watching bool) (err error) {
func (c *commandeer) recreateAndBuildSites(watching bool) (err error) {
if err := c.initSites(); err != nil {
return err
}
if !quiet {
jww.FEEDBACK.Println("Started building sites ...")
c.Logger.FEEDBACK.Println("Started building sites ...")
}
return Hugo.Build(hugolib.BuildCfg{CreateSitesFromConfig: true, Watching: watching, PrintStats: !quiet})
}
func (c commandeer) resetAndBuildSites(watching bool) (err error) {
func (c *commandeer) resetAndBuildSites(watching bool) (err error) {
if err = c.initSites(); err != nil {
return
}
if !quiet {
jww.FEEDBACK.Println("Started building sites ...")
c.Logger.FEEDBACK.Println("Started building sites ...")
}
return Hugo.Build(hugolib.BuildCfg{ResetState: true, Watching: watching, PrintStats: !quiet})
}
func (c commandeer) initSites() error {
func (c *commandeer) initSites() error {
if Hugo != nil {
return nil
}
h, err := hugolib.NewHugoSitesFromConfiguration(c.DepsCfg)
h, err := hugolib.NewHugoSites(*c.DepsCfg)
if err != nil {
return err
@@ -702,17 +704,17 @@ func (c commandeer) initSites() error {
return nil
}
func (c commandeer) buildSites(watching bool) (err error) {
func (c *commandeer) buildSites(watching bool) (err error) {
if err := c.initSites(); err != nil {
return err
}
if !quiet {
jww.FEEDBACK.Println("Started building sites ...")
c.Logger.FEEDBACK.Println("Started building sites ...")
}
return Hugo.Build(hugolib.BuildCfg{Watching: watching, PrintStats: !quiet})
}
func (c commandeer) rebuildSites(events []fsnotify.Event) error {
func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
if err := c.initSites(); err != nil {
return err
}
@@ -720,13 +722,11 @@ func (c commandeer) rebuildSites(events []fsnotify.Event) error {
}
// newWatcher creates a new watcher to watch filesystem events.
func (c commandeer) newWatcher(port int) error {
func (c *commandeer) newWatcher(port int) error {
if runtime.GOOS == "darwin" {
tweakLimit()
}
pathSpec := helpers.NewPathSpec(c.Fs, viper.GetViper())
watcher, err := watcher.New(1 * time.Second)
var wg sync.WaitGroup
@@ -748,7 +748,7 @@ func (c commandeer) newWatcher(port int) error {
for {
select {
case evs := <-watcher.Events:
jww.INFO.Println("Received System Events:", evs)
c.Logger.INFO.Println("Received System Events:", evs)
staticEvents := []fsnotify.Event{}
dynamicEvents := []fsnotify.Event{}
@@ -794,7 +794,7 @@ func (c commandeer) newWatcher(port int) error {
walkAdder := func(path string, f os.FileInfo, err error) error {
if f.IsDir() {
jww.FEEDBACK.Println("adding created directory to watchlist", path)
c.Logger.FEEDBACK.Println("adding created directory to watchlist", path)
watcher.Add(path)
}
return nil
@@ -808,7 +808,7 @@ func (c commandeer) newWatcher(port int) error {
}
}
isstatic := strings.HasPrefix(ev.Name, helpers.GetStaticDirPath()) || (len(pathSpec.GetThemesDirPath()) > 0 && strings.HasPrefix(ev.Name, pathSpec.GetThemesDirPath()))
isstatic := strings.HasPrefix(ev.Name, c.PathSpec().GetStaticDirPath()) || (len(c.PathSpec().GetThemesDirPath()) > 0 && strings.HasPrefix(ev.Name, c.PathSpec().GetThemesDirPath()))
if isstatic {
staticEvents = append(staticEvents, ev)
@@ -818,19 +818,19 @@ func (c commandeer) newWatcher(port int) error {
}
if len(staticEvents) > 0 {
publishDir := helpers.AbsPathify(viper.GetString("publishDir")) + helpers.FilePathSeparator
publishDir := c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")) + helpers.FilePathSeparator
// If root, remove the second '/'
if publishDir == "//" {
publishDir = helpers.FilePathSeparator
}
jww.FEEDBACK.Println("\nStatic file changes detected")
c.Logger.FEEDBACK.Println("\nStatic file changes detected")
const layout = "2006-01-02 15:04 -0700"
jww.FEEDBACK.Println(time.Now().Format(layout))
c.Logger.FEEDBACK.Println(time.Now().Format(layout))
if viper.GetBool("forceSyncStatic") {
jww.FEEDBACK.Printf("Syncing all static files\n")
if c.Cfg.GetBool("forceSyncStatic") {
c.Logger.FEEDBACK.Printf("Syncing all static files\n")
err := c.copyStatic()
if err != nil {
utils.StopOnErr(err, fmt.Sprintf("Error copying static files to %s", publishDir))
@@ -839,13 +839,13 @@ func (c commandeer) newWatcher(port int) error {
staticSourceFs := c.getStaticSourceFs()
if staticSourceFs == nil {
jww.WARN.Println("No static directories found to sync")
c.Logger.WARN.Println("No static directories found to sync")
return
}
syncer := fsync.NewSyncer()
syncer.NoTimes = viper.GetBool("noTimes")
syncer.NoChmod = viper.GetBool("noChmod")
syncer.NoTimes = c.Cfg.GetBool("noTimes")
syncer.NoChmod = c.Cfg.GetBool("noChmod")
syncer.SrcFs = staticSourceFs
syncer.DestFs = c.Fs.Destination
@@ -872,9 +872,9 @@ func (c commandeer) newWatcher(port int) error {
fromPath := ev.Name
// If we are here we already know the event took place in a static dir
relPath, err := pathSpec.MakeStaticPathRelative(fromPath)
relPath, err := c.PathSpec().MakeStaticPathRelative(fromPath)
if err != nil {
jww.ERROR.Println(err)
c.Logger.ERROR.Println(err)
continue
}
@@ -897,10 +897,10 @@ func (c commandeer) newWatcher(port int) error {
// If file still exists, sync it
logger.Println("Syncing", relPath, "to", publishDir)
if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
jww.ERROR.Println(err)
c.Logger.ERROR.Println(err)
}
} else {
jww.ERROR.Println(err)
c.Logger.ERROR.Println(err)
}
continue
@@ -909,18 +909,18 @@ func (c commandeer) newWatcher(port int) error {
// For all other event operations Hugo will sync static.
logger.Println("Syncing", relPath, "to", publishDir)
if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
jww.ERROR.Println(err)
c.Logger.ERROR.Println(err)
}
}
}
if !buildWatch && !viper.GetBool("disableLiveReload") {
if !buildWatch && !c.Cfg.GetBool("disableLiveReload") {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
// force refresh when more than one file
if len(staticEvents) > 0 {
for _, ev := range staticEvents {
path, _ := pathSpec.MakeStaticPathRelative(ev.Name)
path, _ := c.PathSpec().MakeStaticPathRelative(ev.Name)
livereload.RefreshPath(path)
}
@@ -931,27 +931,27 @@ func (c commandeer) newWatcher(port int) error {
}
if len(dynamicEvents) > 0 {
jww.FEEDBACK.Println("\nChange detected, rebuilding site")
c.Logger.FEEDBACK.Println("\nChange detected, rebuilding site")
const layout = "2006-01-02 15:04 -0700"
jww.FEEDBACK.Println(time.Now().Format(layout))
c.Logger.FEEDBACK.Println(time.Now().Format(layout))
c.rebuildSites(dynamicEvents)
if !buildWatch && !viper.GetBool("disableLiveReload") {
if !buildWatch && !c.Cfg.GetBool("disableLiveReload") {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
livereload.ForceRefresh()
}
}
case err := <-watcher.Errors:
if err != nil {
jww.ERROR.Println(err)
c.Logger.ERROR.Println(err)
}
}
}
}()
if port > 0 {
if !viper.GetBool("disableLiveReload") {
if !c.Cfg.GetBool("disableLiveReload") {
livereload.Initialize()
http.HandleFunc("/livereload.js", livereload.ServeJS)
http.HandleFunc("/livereload", livereload.Handler)
@@ -966,30 +966,30 @@ func (c commandeer) newWatcher(port int) error {
// isThemeVsHugoVersionMismatch returns whether the current Hugo version is
// less than the theme's min_version.
func isThemeVsHugoVersionMismatch(fs afero.Fs) (mismatch bool, requiredMinVersion string) {
if !helpers.ThemeSet() {
func (c *commandeer) isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinVersion string) {
if !c.PathSpec().ThemeSet() {
return
}
themeDir := helpers.GetThemeDir()
themeDir := c.PathSpec().GetThemeDir()
path := filepath.Join(themeDir, "theme.toml")
exists, err := helpers.Exists(path, fs)
exists, err := helpers.Exists(path, c.Fs.Source)
if err != nil || !exists {
return
}
b, err := afero.ReadFile(fs, path)
b, err := afero.ReadFile(c.Fs.Source, path)
c, err := parser.HandleTOMLMetaData(b)
tomlMeta, err := parser.HandleTOMLMetaData(b)
if err != nil {
return
}
config := c.(map[string]interface{})
config := tomlMeta.(map[string]interface{})
if minVersion, ok := config["min_version"]; ok {
switch minVersion.(type) {

View File

@@ -19,7 +19,6 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/hugo/hugolib"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
)
func init() {
@@ -50,9 +49,11 @@ var listDraftsCmd = &cobra.Command{
return err
}
viper.Set("buildDrafts", true)
c := newCommandeer(cfg)
sites, err := hugolib.NewHugoSitesFromConfiguration(cfg)
c.Set("buildDrafts", true)
sites, err := hugolib.NewHugoSites(*cfg)
if err != nil {
return newSystemError("Error creating sites", err)
@@ -86,9 +87,11 @@ posted in the future.`,
return err
}
viper.Set("buildFuture", true)
c := newCommandeer(cfg)
sites, err := hugolib.NewHugoSitesFromConfiguration(cfg)
c.Set("buildFuture", true)
sites, err := hugolib.NewHugoSites(*cfg)
if err != nil {
return newSystemError("Error creating sites", err)
@@ -122,9 +125,11 @@ expired.`,
return err
}
viper.Set("buildExpired", true)
c := newCommandeer(cfg)
sites, err := hugolib.NewHugoSitesFromConfiguration(cfg)
c.Set("buildExpired", true)
sites, err := hugolib.NewHugoSites(*cfg)
if err != nil {
return newSystemError("Error creating sites", err)

View File

@@ -29,15 +29,17 @@ var configCmd = &cobra.Command{
}
func init() {
configCmd.RunE = config
configCmd.RunE = printConfig
}
func config(cmd *cobra.Command, args []string) error {
if _, err := InitializeConfig(configCmd); err != nil {
func printConfig(cmd *cobra.Command, args []string) error {
cfg, err := InitializeConfig(configCmd)
if err != nil {
return err
}
allSettings := viper.AllSettings()
allSettings := cfg.Cfg.(*viper.Viper).AllSettings()
var separator string
if allSettings["metadataformat"] == "toml" {

View File

@@ -93,12 +93,14 @@ func NewContent(cmd *cobra.Command, args []string) error {
return err
}
if flagChanged(cmd.Flags(), "format") {
viper.Set("metaDataFormat", configFormat)
c := newCommandeer(cfg)
if c.flagChanged(cmd.Flags(), "format") {
c.Set("metaDataFormat", configFormat)
}
if flagChanged(cmd.Flags(), "editor") {
viper.Set("newContentEditor", contentEditor)
if c.flagChanged(cmd.Flags(), "editor") {
c.Set("newContentEditor", contentEditor)
}
if len(args) < 1 {
@@ -115,7 +117,7 @@ func NewContent(cmd *cobra.Command, args []string) error {
kind = contentType
}
s, err := hugolib.NewSite(cfg)
s, err := hugolib.NewSite(*cfg)
if err != nil {
return newSystemError(err)
@@ -203,7 +205,7 @@ func NewSite(cmd *cobra.Command, args []string) error {
forceNew, _ := cmd.Flags().GetBool("force")
return doNewSite(hugofs.NewDefault(), createpath, forceNew)
return doNewSite(hugofs.NewDefault(viper.New()), createpath, forceNew)
}
// NewTheme creates a new Hugo theme.
@@ -215,11 +217,12 @@ func NewTheme(cmd *cobra.Command, args []string) error {
}
if len(args) < 1 {
return newUserError("theme name needs to be provided")
}
createpath := helpers.AbsPathify(filepath.Join(viper.GetString("themesDir"), args[0]))
c := newCommandeer(cfg)
createpath := c.PathSpec().AbsPathify(filepath.Join(c.Cfg.GetString("themesDir"), args[0]))
jww.INFO.Println("creating theme at", createpath)
if x, _ := helpers.Exists(createpath, cfg.Fs.Source); x {

View File

@@ -18,6 +18,7 @@ import (
"testing"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -48,7 +49,7 @@ func checkNewSiteInited(fs *hugofs.Fs, basepath string, t *testing.T) {
func TestDoNewSite(t *testing.T) {
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
_, fs := newTestCfg()
require.NoError(t, doNewSite(fs, basepath, false))
@@ -57,7 +58,7 @@ func TestDoNewSite(t *testing.T) {
func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) {
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
_, fs := newTestCfg()
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
@@ -66,7 +67,7 @@ func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) {
func TestDoNewSite_error_base_exists(t *testing.T) {
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
_, fs := newTestCfg()
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
_, err := fs.Source.Create(filepath.Join(basepath, "foo"))
@@ -78,7 +79,7 @@ func TestDoNewSite_error_base_exists(t *testing.T) {
func TestDoNewSite_force_empty_dir(t *testing.T) {
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
_, fs := newTestCfg()
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
@@ -89,7 +90,7 @@ func TestDoNewSite_force_empty_dir(t *testing.T) {
func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) {
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
_, fs := newTestCfg()
contentPath := filepath.Join(basepath, "content")
@@ -99,7 +100,7 @@ func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) {
func TestDoNewSite_error_force_config_inside_exists(t *testing.T) {
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
_, fs := newTestCfg()
configPath := filepath.Join(basepath, "config.toml")
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
@@ -108,3 +109,14 @@ func TestDoNewSite_error_force_config_inside_exists(t *testing.T) {
require.Error(t, doNewSite(fs, basepath, true))
}
func newTestCfg() (*viper.Viper, *hugofs.Fs) {
v := viper.New()
fs := hugofs.NewMem(v)
v.SetFs(fs.Source)
return v, fs
}

View File

@@ -28,9 +28,9 @@ import (
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/hugo/config"
"github.com/spf13/hugo/helpers"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
)
var (
@@ -42,8 +42,6 @@ var (
serverWatch bool
)
//var serverCmdV *cobra.Command
var serverCmd = &cobra.Command{
Use: "server",
Aliases: []string{"serve"},
@@ -108,17 +106,17 @@ func server(cmd *cobra.Command, args []string) error {
return err
}
c := commandeer{cfg}
c := newCommandeer(cfg)
if flagChanged(cmd.Flags(), "disableLiveReload") {
viper.Set("disableLiveReload", disableLiveReload)
if c.flagChanged(cmd.Flags(), "disableLiveReload") {
c.Set("disableLiveReload", disableLiveReload)
}
if serverWatch {
viper.Set("watch", true)
c.Set("watch", true)
}
if viper.GetBool("watch") {
if c.Cfg.GetBool("watch") {
serverWatch = true
c.watchConfig()
}
@@ -127,7 +125,7 @@ func server(cmd *cobra.Command, args []string) error {
if err == nil {
l.Close()
} else {
if flagChanged(serverCmd.Flags(), "port") {
if c.flagChanged(serverCmd.Flags(), "port") {
// port set explicitly by user -- he/she probably meant it!
return newSystemErrorF("Server startup failed: %s", err)
}
@@ -139,13 +137,13 @@ func server(cmd *cobra.Command, args []string) error {
serverPort = sp.Port
}
viper.Set("port", serverPort)
c.Set("port", serverPort)
baseURL, err = fixURL(baseURL)
baseURL, err = fixURL(c.Cfg, baseURL)
if err != nil {
return err
}
viper.Set("baseURL", baseURL)
c.Set("baseURL", baseURL)
if err := memStats(); err != nil {
jww.ERROR.Println("memstats error:", err)
@@ -160,7 +158,7 @@ func server(cmd *cobra.Command, args []string) error {
if !renderToDisk {
cfg.Fs.Destination = new(afero.MemMapFs)
// Rendering to memoryFS, publish to Root regardless of publishDir.
viper.Set("publishDir", "/")
c.Set("publishDir", "/")
}
if err := c.build(serverWatch); err != nil {
@@ -170,7 +168,7 @@ func server(cmd *cobra.Command, args []string) error {
// Watch runs its own server as part of the routine
if serverWatch {
watchDirs := c.getDirList()
baseWatchDir := viper.GetString("workingDir")
baseWatchDir := c.Cfg.GetString("workingDir")
for i, dir := range watchDirs {
watchDirs[i], _ = helpers.GetRelativePath(dir, baseWatchDir)
}
@@ -190,19 +188,19 @@ func server(cmd *cobra.Command, args []string) error {
return nil
}
func (c commandeer) serve(port int) {
func (c *commandeer) serve(port int) {
if renderToDisk {
jww.FEEDBACK.Println("Serving pages from " + helpers.AbsPathify(viper.GetString("publishDir")))
jww.FEEDBACK.Println("Serving pages from " + c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")))
} else {
jww.FEEDBACK.Println("Serving pages from memory")
}
httpFs := afero.NewHttpFs(c.Fs.Destination)
fs := filesOnlyFs{httpFs.Dir(helpers.AbsPathify(viper.GetString("publishDir")))}
fs := filesOnlyFs{httpFs.Dir(c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")))}
fileserver := http.FileServer(fs)
// We're only interested in the path
u, err := url.Parse(viper.GetString("baseURL"))
u, err := url.Parse(c.Cfg.GetString("baseURL"))
if err != nil {
jww.ERROR.Fatalf("Invalid baseURL: %s", err)
}
@@ -225,10 +223,10 @@ func (c commandeer) serve(port int) {
// fixURL massages the baseURL into a form needed for serving
// all pages correctly.
func fixURL(s string) (string, error) {
func fixURL(cfg config.Provider, s string) (string, error) {
useLocalhost := false
if s == "" {
s = viper.GetString("baseURL")
s = cfg.GetString("baseURL")
useLocalhost = true
}

View File

@@ -20,8 +20,6 @@ import (
)
func TestFixURL(t *testing.T) {
defer viper.Reset()
type data struct {
TestName string
CLIBaseURL string
@@ -44,12 +42,12 @@ func TestFixURL(t *testing.T) {
}
for i, test := range tests {
viper.Reset()
v := viper.New()
baseURL = test.CLIBaseURL
viper.Set("baseURL", test.CfgBaseURL)
v.Set("baseURL", test.CfgBaseURL)
serverAppend = test.AppendPort
serverPort = test.Port
result, err := fixURL(baseURL)
result, err := fixURL(v, baseURL)
if err != nil {
t.Errorf("Test #%d %s: unexpected error %s", i, test.TestName, err)
}