ref: 93ca7c9e958e34469a337e4efcc7c75774ec50fd
parent: e34af6ee30f70f5780a281e2fd8f4ed9b487ee61
author: Bjørn Erik Pedersen <[email protected]>
date: Sun Feb 5 05:20:06 EST 2017
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
--- a/commands/benchmark.go
+++ b/commands/benchmark.go
@@ -54,7 +54,7 @@
return err
}
- c := commandeer{cfg}
+ c := newCommandeer(cfg)
var memProf *os.File
if memProfileFile != "" {
--- /dev/null
+++ b/commands/commandeer.go
@@ -1,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}
+}
--- a/commands/convert.go
+++ b/commands/convert.go
@@ -21,11 +21,8 @@
"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 @@
return err
}
- h, err := hugolib.NewHugoSitesFromConfiguration(cfg)
+ h, err := hugolib.NewHugoSites(*cfg)
if err != nil {
return err
}
@@ -104,10 +101,10 @@
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 @@
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 @@
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 @@
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")
}
}
}
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -27,8 +27,7 @@
"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 @@
"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 @@
// for benchmark testing etc. via the CLI commands.
func Reset() error {
Hugo = nil
- viper.Reset()
return nil
}
@@ -124,10 +118,10 @@
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 (
- 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 @@
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 @@
}
// 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 @@
} 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,14 +363,14 @@
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)
@@ -357,25 +377,18 @@
}
}
- 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 @@
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 @@
return nil, newSystemError(err)
}
}
- } else if !quiet && viper.GetBool("verbose") {
+ } else if !quiet && cfg.GetBool("verbose") {
stdoutThreshold = jww.LevelInfo
}
@@ -409,7 +422,7 @@
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 @@
}
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,13 +467,14 @@
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()
}
@@ -467,18 +481,9 @@
})
}
-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,8 +494,8 @@
}
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))
}
@@ -497,29 +502,27 @@
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 @@
}
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 @@
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 @@
}
// 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,7 +622,7 @@
return nil
}
- jww.ERROR.Println("Walker: ", err)
+ c.Logger.ERROR.Println("Walker: ", err)
return nil
}
@@ -626,16 +629,16 @@
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 @@
}
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,33 +670,32 @@
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.NewHugoSites(*c.DepsCfg)
- h, err := hugolib.NewHugoSitesFromConfiguration(c.DepsCfg)
-
if err != nil {
return err
}
@@ -702,17 +704,17 @@
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 @@
}
// 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 @@
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 @@
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 @@
}
}
- 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,7 +818,7 @@
}
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 == "//" {
@@ -825,12 +825,12 @@
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 @@
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 @@
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 @@
// 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 @@
// 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,13 +931,13 @@
}
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()
}
@@ -944,7 +944,7 @@
}
case err := <-watcher.Errors:
if err != nil {
- jww.ERROR.Println(err)
+ c.Logger.ERROR.Println(err)
}
}
}
@@ -951,7 +951,7 @@
}()
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 @@
// 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) {
--- a/commands/list.go
+++ b/commands/list.go
@@ -19,7 +19,6 @@
"github.com/spf13/cobra"
"github.com/spf13/hugo/hugolib"
jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
)
func init() {
@@ -50,10 +49,12 @@
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,10 +87,12 @@
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 @@
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)
--- a/commands/list_config.go
+++ b/commands/list_config.go
@@ -29,15 +29,17 @@
}
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" {
--- a/commands/new.go
+++ b/commands/new.go
@@ -93,12 +93,14 @@
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 @@
kind = contentType
}
- s, err := hugolib.NewSite(cfg)
+ s, err := hugolib.NewSite(*cfg)
if err != nil {
return newSystemError(err)
@@ -203,7 +205,7 @@
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 @@
}
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 {
--- a/commands/new_test.go
+++ b/commands/new_test.go
@@ -18,6 +18,7 @@
"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 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_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_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_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_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_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))
@@ -107,4 +108,15 @@
require.NoError(t, err)
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
+
}
--- a/commands/server.go
+++ b/commands/server.go
@@ -28,9 +28,9 @@
"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 @@
serverWatch bool
)
-//var serverCmdV *cobra.Command
-
var serverCmd = &cobra.Command{
Use: "server",
Aliases: []string{"serve"},
@@ -108,17 +106,17 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
// 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
}
--- a/commands/server_test.go
+++ b/commands/server_test.go
@@ -20,8 +20,6 @@
)
func TestFixURL(t *testing.T) {
- defer viper.Reset()
-
type data struct {
TestName string
CLIBaseURL string
@@ -44,12 +42,12 @@
}
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)
}
--- /dev/null
+++ b/config/configProvider.go
@@ -1,0 +1,40 @@
+// Copyright 2017-present 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 helpers implements general utility functions that work with
+// and on content. The helper functions defined here lay down the
+// foundation of how Hugo works with files and filepaths, and perform
+// string operations on content.
+package config
+
+// A cached version of the current ConfigProvider (language) and relatives. These globals
+// are unfortunate, but we still have some places that needs this that does
+// not have access to the site configuration.
+// These values will be set on initialization when rendering a new language.
+//
+// TODO(bep) Get rid of these.
+var (
+ currentConfigProvider Provider
+)
+
+// Provider provides the configuration settings for Hugo.
+type Provider interface {
+ GetString(key string) string
+ GetInt(key string) int
+ GetBool(key string) bool
+ GetStringMap(key string) map[string]interface{}
+ GetStringMapString(key string) map[string]string
+ Get(key string) interface{}
+ Set(key string, value interface{})
+ IsSet(key string) bool
+}
--- a/create/content.go
+++ b/create/content.go
@@ -29,7 +29,6 @@
"github.com/spf13/hugo/hugolib"
"github.com/spf13/hugo/parser"
jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
)
// NewContent creates a new content file in the content directory based upon the
@@ -37,7 +36,7 @@
func NewContent(s *hugolib.Site, kind, name string) (err error) {
jww.INFO.Println("attempting to create ", name, "of", kind)
- location := FindArchetype(s.Fs.Source, kind)
+ location := FindArchetype(s, kind)
var by []byte
@@ -67,23 +66,23 @@
return err
}
- if err = page.SetSourceMetaData(metadata, parser.FormatToLeadRune(viper.GetString("metaDataFormat"))); err != nil {
+ if err = page.SetSourceMetaData(metadata, parser.FormatToLeadRune(s.Cfg.GetString("metaDataFormat"))); err != nil {
return
}
page.SetSourceContent(psr.Content())
- if err = page.SafeSaveSourceAs(filepath.Join(viper.GetString("contentDir"), name)); err != nil {
+ if err = page.SafeSaveSourceAs(filepath.Join(s.Cfg.GetString("contentDir"), name)); err != nil {
return
}
- jww.FEEDBACK.Println(helpers.AbsPathify(filepath.Join(viper.GetString("contentDir"), name)), "created")
+ jww.FEEDBACK.Println(s.PathSpec.AbsPathify(filepath.Join(s.Cfg.GetString("contentDir"), name)), "created")
- editor := viper.GetString("newContentEditor")
+ editor := s.Cfg.GetString("newContentEditor")
if editor != "" {
jww.FEEDBACK.Printf("Editing %s with %q ...\n", name, editor)
- cmd := exec.Command(editor, helpers.AbsPathify(path.Join(viper.GetString("contentDir"), name)))
+ cmd := exec.Command(editor, s.PathSpec.AbsPathify(path.Join(s.Cfg.GetString("contentDir"), name)))
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -138,12 +137,7 @@
metadata["title"] = helpers.MakeTitle(helpers.Filename(name))
}
- // TOD(bep) what is this?
- if x := parser.FormatSanitize(viper.GetString("metaDataFormat")); x == "json" || x == "yaml" || x == "toml" {
- metadata["date"] = date.Format(time.RFC3339)
- } else {
- metadata["date"] = date
- }
+ metadata["date"] = date.Format(time.RFC3339)
return metadata, nil
}
@@ -151,13 +145,13 @@
// FindArchetype takes a given kind/archetype of content and returns an output
// path for that archetype. If no archetype is found, an empty string is
// returned.
-func FindArchetype(fs afero.Fs, kind string) (outpath string) {
- search := []string{helpers.AbsPathify(viper.GetString("archetypeDir"))}
+func FindArchetype(s *hugolib.Site, kind string) (outpath string) {
+ search := []string{s.PathSpec.AbsPathify(s.Cfg.GetString("archetypeDir"))}
- if viper.GetString("theme") != "" {
- themeDir := filepath.Join(helpers.AbsPathify(viper.GetString("themesDir")+"/"+viper.GetString("theme")), "/archetypes/")
- if _, err := fs.Stat(themeDir); os.IsNotExist(err) {
- jww.ERROR.Printf("Unable to find archetypes directory for theme %q at %q", viper.GetString("theme"), themeDir)
+ if s.Cfg.GetString("theme") != "" {
+ themeDir := filepath.Join(s.PathSpec.AbsPathify(s.Cfg.GetString("themesDir")+"/"+s.Cfg.GetString("theme")), "/archetypes/")
+ if _, err := s.Fs.Source.Stat(themeDir); os.IsNotExist(err) {
+ jww.ERROR.Printf("Unable to find archetypes directory for theme %q at %q", s.Cfg.GetString("theme"), themeDir)
} else {
search = append(search, themeDir)
}
@@ -177,7 +171,7 @@
for _, p := range pathsToCheck {
curpath := filepath.Join(x, p)
jww.DEBUG.Println("checking", curpath, "for archetypes")
- if exists, _ := helpers.Exists(curpath, fs); exists {
+ if exists, _ := helpers.Exists(curpath, s.Fs.Source); exists {
jww.INFO.Println("curpath: " + curpath)
return curpath
}
--- a/create/content_test.go
+++ b/create/content_test.go
@@ -19,6 +19,8 @@
"strings"
"testing"
+ "github.com/spf13/hugo/deps"
+
"github.com/spf13/hugo/hugolib"
"fmt"
@@ -33,7 +35,8 @@
)
func TestNewContent(t *testing.T) {
- initViper()
+ v := viper.New()
+ initViper(v)
cases := []struct {
kind string
@@ -48,14 +51,17 @@
}
for _, c := range cases {
- s, err := hugolib.NewEnglishSiteMem()
+ cfg, fs := newTestCfg()
+ h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs})
require.NoError(t, err)
- require.NoError(t, initFs(s.Fs))
+ require.NoError(t, initFs(fs))
+ s := h.Sites[0]
+
require.NoError(t, create.NewContent(s, c.kind, c.path))
fname := filepath.Join("content", filepath.FromSlash(c.path))
- content := readFileFromFs(t, s.Fs.Source, fname)
+ content := readFileFromFs(t, fs.Source, fname)
for i, v := range c.expected {
found := strings.Contains(content, v)
if !found {
@@ -65,14 +71,14 @@
}
}
-func initViper() {
- viper.Reset()
- viper.Set("metaDataFormat", "toml")
- viper.Set("archetypeDir", "archetypes")
- viper.Set("contentDir", "content")
- viper.Set("themesDir", "themes")
- viper.Set("layoutDir", "layouts")
- viper.Set("theme", "sample")
+func initViper(v *viper.Viper) {
+ v.Set("metaDataFormat", "toml")
+ v.Set("archetypeDir", "archetypes")
+ v.Set("contentDir", "content")
+ v.Set("themesDir", "themes")
+ v.Set("layoutDir", "layouts")
+ v.Set("i18nDir", "i18n")
+ v.Set("theme", "sample")
}
func initFs(fs *hugofs.Fs) error {
@@ -142,4 +148,17 @@
t.Fatalf("Failed to read file: %s", err)
}
return string(b)
+}
+
+func newTestCfg() (*viper.Viper, *hugofs.Fs) {
+
+ v := viper.New()
+ fs := hugofs.NewMem(v)
+
+ v.SetFs(fs.Source)
+
+ initViper(v)
+
+ return v, fs
+
}
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -5,6 +5,7 @@
"log"
"os"
+ "github.com/spf13/hugo/config"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/tplapi"
@@ -27,24 +28,40 @@
// The PathSpec to use
*helpers.PathSpec `json:"-"`
- templateProvider TemplateProvider
- WithTemplate func(templ tplapi.Template) error
+ // The ContentSpec to use
+ *helpers.ContentSpec `json:"-"`
- // TODO(bep) globals next in line: Viper
+ // The configuration to use
+ Cfg config.Provider `json:"-"`
+ // The translation func to use
+ Translate func(translationID string, args ...interface{}) string `json:"-"`
+
+ Language *helpers.Language
+
+ templateProvider ResourceProvider
+ WithTemplate func(templ tplapi.Template) error `json:"-"`
+
+ translationProvider ResourceProvider
}
-// Used to create and refresh, and clone the template.
-type TemplateProvider interface {
+// Used to create and refresh, and clone resources needed.
+type ResourceProvider interface {
Update(deps *Deps) error
Clone(deps *Deps) error
}
-func (d *Deps) LoadTemplates() error {
+func (d *Deps) LoadResources() error {
+ // Note that the translations need to be loaded before the templates.
+ if err := d.translationProvider.Update(d); err != nil {
+ return err
+ }
+
if err := d.templateProvider.Update(d); err != nil {
return err
}
d.Tmpl.PrintErrors()
+
return nil
}
@@ -58,6 +75,10 @@
panic("Must have a TemplateProvider")
}
+ if cfg.TranslationProvider == nil {
+ panic("Must have a TranslationProvider")
+ }
+
if cfg.Language == nil {
panic("Must have a Language")
}
@@ -67,16 +88,20 @@
}
if fs == nil {
- // Default to the production file systems.
- fs = hugofs.NewDefault()
+ // Default to the production file system.
+ fs = hugofs.NewDefault(cfg.Language)
}
d := &Deps{
- Fs: fs,
- Log: logger,
- templateProvider: cfg.TemplateProvider,
- WithTemplate: cfg.WithTemplate,
- PathSpec: helpers.NewPathSpec(fs, cfg.Language),
+ Fs: fs,
+ Log: logger,
+ templateProvider: cfg.TemplateProvider,
+ translationProvider: cfg.TranslationProvider,
+ WithTemplate: cfg.WithTemplate,
+ PathSpec: helpers.NewPathSpec(fs, cfg.Language),
+ ContentSpec: helpers.NewContentSpec(cfg.Language),
+ Cfg: cfg.Language,
+ Language: cfg.Language,
}
return d
@@ -87,6 +112,14 @@
func (d Deps) ForLanguage(l *helpers.Language) (*Deps, error) {
d.PathSpec = helpers.NewPathSpec(d.Fs, l)
+ d.ContentSpec = helpers.NewContentSpec(l)
+ d.Cfg = l
+ d.Language = l
+
+ if err := d.translationProvider.Clone(&d); err != nil {
+ return nil, err
+ }
+
if err := d.templateProvider.Clone(&d); err != nil {
return nil, err
}
@@ -109,7 +142,13 @@
// The language to use.
Language *helpers.Language
+ // The configuration to use.
+ Cfg config.Provider
+
// Template handling.
- TemplateProvider TemplateProvider
+ TemplateProvider ResourceProvider
WithTemplate func(templ tplapi.Template) error
+
+ // i18n handling.
+ TranslationProvider ResourceProvider
}
--- a/helpers/configProvider.go
+++ /dev/null
@@ -1,63 +1,0 @@
-// Copyright 2016-present 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 helpers implements general utility functions that work with
-// and on content. The helper functions defined here lay down the
-// foundation of how Hugo works with files and filepaths, and perform
-// string operations on content.
-package helpers
-
-import (
- "github.com/spf13/viper"
-)
-
-// A cached version of the current ConfigProvider (language) and relatives. These globals
-// are unfortunate, but we still have some places that needs this that does
-// not have access to the site configuration.
-// These values will be set on initialization when rendering a new language.
-//
-// TODO(bep) Get rid of these.
-var (
- currentConfigProvider ConfigProvider
-)
-
-// ConfigProvider provides the configuration settings for Hugo.
-type ConfigProvider interface {
- GetString(key string) string
- GetInt(key string) int
- GetBool(key string) bool
- GetStringMap(key string) map[string]interface{}
- GetStringMapString(key string) map[string]string
- Get(key string) interface{}
-}
-
-// Config returns the currently active Hugo config. This will be set
-// per site (language) rendered.
-func Config() ConfigProvider {
- if currentConfigProvider != nil {
- return currentConfigProvider
- }
- // Some tests rely on this. We will fix that, eventually.
- return viper.Get("currentContentLanguage").(ConfigProvider)
-}
-
-// InitConfigProviderForCurrentContentLanguage does what it says.
-func InitConfigProviderForCurrentContentLanguage() {
- currentConfigProvider = viper.Get("CurrentContentLanguage").(ConfigProvider)
-}
-
-// ResetConfigProvider is used in tests.
-func ResetConfigProvider() {
- currentConfigProvider = nil
-
-}
--- a/helpers/content.go
+++ b/helpers/content.go
@@ -24,12 +24,13 @@
"unicode"
"unicode/utf8"
+ "github.com/spf13/hugo/config"
+
"github.com/miekg/mmark"
"github.com/mitchellh/mapstructure"
"github.com/russross/blackfriday"
bp "github.com/spf13/hugo/bufferpool"
jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
"strings"
"sync"
@@ -41,6 +42,14 @@
// SummaryDivider denotes where content summarization should end. The default is "<!--more-->".
var SummaryDivider = []byte("<!--more-->")
+type ContentSpec struct {
+ cfg config.Provider
+}
+
+func NewContentSpec(cfg config.Provider) *ContentSpec {
+ return &ContentSpec{cfg}
+}
+
// Blackfriday holds configuration values for Blackfriday rendering.
type Blackfriday struct {
Smartypants bool
@@ -58,7 +67,7 @@
}
// NewBlackfriday creates a new Blackfriday filled with site config or some sane defaults.
-func NewBlackfriday(c ConfigProvider) *Blackfriday {
+func (c ContentSpec) NewBlackfriday() *Blackfriday {
defaultParam := map[string]interface{}{
"smartypants": true,
@@ -75,7 +84,7 @@
ToLowerMap(defaultParam)
- siteParam := c.GetStringMap("blackfriday")
+ siteParam := c.cfg.GetStringMap("blackfriday")
siteConfig := make(map[string]interface{})
@@ -187,10 +196,10 @@
}
// getHTMLRenderer creates a new Blackfriday HTML Renderer with the given configuration.
-func getHTMLRenderer(defaultFlags int, ctx *RenderingContext) blackfriday.Renderer {
+func (c ContentSpec) getHTMLRenderer(defaultFlags int, ctx *RenderingContext) blackfriday.Renderer {
renderParameters := blackfriday.HtmlRendererParameters{
- FootnoteAnchorPrefix: viper.GetString("footnoteAnchorPrefix"),
- FootnoteReturnLinkContents: viper.GetString("footnoteReturnLinkContents"),
+ FootnoteAnchorPrefix: c.cfg.GetString("footnoteAnchorPrefix"),
+ FootnoteReturnLinkContents: c.cfg.GetString("footnoteReturnLinkContents"),
}
b := len(ctx.DocumentID) != 0
@@ -265,21 +274,21 @@
return flags
}
-func markdownRender(ctx *RenderingContext) []byte {
+func (c ContentSpec) markdownRender(ctx *RenderingContext) []byte {
if ctx.RenderTOC {
return blackfriday.Markdown(ctx.Content,
- getHTMLRenderer(blackfriday.HTML_TOC, ctx),
+ c.getHTMLRenderer(blackfriday.HTML_TOC, ctx),
getMarkdownExtensions(ctx))
}
- return blackfriday.Markdown(ctx.Content, getHTMLRenderer(0, ctx),
+ return blackfriday.Markdown(ctx.Content, c.getHTMLRenderer(0, ctx),
getMarkdownExtensions(ctx))
}
// getMmarkHTMLRenderer creates a new mmark HTML Renderer with the given configuration.
-func getMmarkHTMLRenderer(defaultFlags int, ctx *RenderingContext) mmark.Renderer {
+func (c ContentSpec) getMmarkHTMLRenderer(defaultFlags int, ctx *RenderingContext) mmark.Renderer {
renderParameters := mmark.HtmlRendererParameters{
- FootnoteAnchorPrefix: viper.GetString("footnoteAnchorPrefix"),
- FootnoteReturnLinkContents: viper.GetString("footnoteReturnLinkContents"),
+ FootnoteAnchorPrefix: c.cfg.GetString("footnoteAnchorPrefix"),
+ FootnoteReturnLinkContents: c.cfg.GetString("footnoteReturnLinkContents"),
}
b := len(ctx.DocumentID) != 0
@@ -294,6 +303,7 @@
return &HugoMmarkHTMLRenderer{
mmark.HtmlRendererWithParameters(htmlFlags, "", "", renderParameters),
+ c.cfg,
}
}
@@ -321,8 +331,8 @@
return flags
}
-func mmarkRender(ctx *RenderingContext) []byte {
- return mmark.Parse(ctx.Content, getMmarkHTMLRenderer(0, ctx),
+func (c ContentSpec) mmarkRender(ctx *RenderingContext) []byte {
+ return mmark.Parse(ctx.Content, c.getMmarkHTMLRenderer(0, ctx),
getMmarkExtensions(ctx)).Bytes()
}
@@ -365,26 +375,28 @@
// RenderingContext holds contextual information, like content and configuration,
// for a given content rendering.
type RenderingContext struct {
- Content []byte
- PageFmt string
- DocumentID string
- DocumentName string
- Config *Blackfriday
- RenderTOC bool
- FileResolver FileResolverFunc
- LinkResolver LinkResolverFunc
- ConfigProvider ConfigProvider
- configInit sync.Once
+ Content []byte
+ PageFmt string
+ DocumentID string
+ DocumentName string
+ Config *Blackfriday
+ RenderTOC bool
+ FileResolver FileResolverFunc
+ LinkResolver LinkResolverFunc
+ Cfg config.Provider
+ configInit sync.Once
}
-func newViperProvidedRenderingContext() *RenderingContext {
- return &RenderingContext{ConfigProvider: viper.GetViper()}
+func newRenderingContext(cfg config.Provider) *RenderingContext {
+ return &RenderingContext{Cfg: cfg}
}
func (c *RenderingContext) getConfig() *Blackfriday {
+ // TODO(bep) get rid of this
c.configInit.Do(func() {
if c.Config == nil {
- c.Config = NewBlackfriday(c.ConfigProvider)
+ cs := NewContentSpec(c.Cfg)
+ c.Config = cs.NewBlackfriday()
}
})
return c.Config
@@ -391,16 +403,16 @@
}
// RenderBytes renders a []byte.
-func RenderBytes(ctx *RenderingContext) []byte {
+func (c ContentSpec) RenderBytes(ctx *RenderingContext) []byte {
switch ctx.PageFmt {
default:
- return markdownRender(ctx)
+ return c.markdownRender(ctx)
case "markdown":
- return markdownRender(ctx)
+ return c.markdownRender(ctx)
case "asciidoc":
return getAsciidocContent(ctx)
case "mmark":
- return mmarkRender(ctx)
+ return c.mmarkRender(ctx)
case "rst":
return getRstContent(ctx)
}
--- a/helpers/content_renderer.go
+++ b/helpers/content_renderer.go
@@ -19,8 +19,8 @@
"github.com/miekg/mmark"
"github.com/russross/blackfriday"
+ "github.com/spf13/hugo/config"
jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
)
type LinkResolverFunc func(ref string) (string, error)
@@ -33,49 +33,49 @@
blackfriday.Renderer
}
-func (renderer *HugoHTMLRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
- if viper.GetBool("pygmentsCodeFences") && (lang != "" || viper.GetBool("pygmentsCodeFencesGuessSyntax")) {
- opts := viper.GetString("pygmentsOptions")
+func (r *HugoHTMLRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
+ if r.Cfg.GetBool("pygmentsCodeFences") && (lang != "" || r.Cfg.GetBool("pygmentsCodeFencesGuessSyntax")) {
+ opts := r.Cfg.GetString("pygmentsOptions")
str := html.UnescapeString(string(text))
- out.WriteString(Highlight(str, lang, opts))
+ out.WriteString(Highlight(r.RenderingContext.Cfg, str, lang, opts))
} else {
- renderer.Renderer.BlockCode(out, text, lang)
+ r.Renderer.BlockCode(out, text, lang)
}
}
-func (renderer *HugoHTMLRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
- if renderer.LinkResolver == nil || bytes.HasPrefix(link, []byte("HAHAHUGOSHORTCODE")) {
+func (r *HugoHTMLRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
+ if r.LinkResolver == nil || bytes.HasPrefix(link, []byte("HAHAHUGOSHORTCODE")) {
// Use the blackfriday built in Link handler
- renderer.Renderer.Link(out, link, title, content)
+ r.Renderer.Link(out, link, title, content)
} else {
// set by SourceRelativeLinksEval
- newLink, err := renderer.LinkResolver(string(link))
+ newLink, err := r.LinkResolver(string(link))
if err != nil {
newLink = string(link)
jww.ERROR.Printf("LinkResolver: %s", err)
}
- renderer.Renderer.Link(out, []byte(newLink), title, content)
+ r.Renderer.Link(out, []byte(newLink), title, content)
}
}
-func (renderer *HugoHTMLRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
- if renderer.FileResolver == nil || bytes.HasPrefix(link, []byte("HAHAHUGOSHORTCODE")) {
+func (r *HugoHTMLRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
+ if r.FileResolver == nil || bytes.HasPrefix(link, []byte("HAHAHUGOSHORTCODE")) {
// Use the blackfriday built in Image handler
- renderer.Renderer.Image(out, link, title, alt)
+ r.Renderer.Image(out, link, title, alt)
} else {
// set by SourceRelativeLinksEval
- newLink, err := renderer.FileResolver(string(link))
+ newLink, err := r.FileResolver(string(link))
if err != nil {
newLink = string(link)
jww.ERROR.Printf("FileResolver: %s", err)
}
- renderer.Renderer.Image(out, []byte(newLink), title, alt)
+ r.Renderer.Image(out, []byte(newLink), title, alt)
}
}
// ListItem adds task list support to the Blackfriday renderer.
-func (renderer *HugoHTMLRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
- if !renderer.Config.TaskLists {
- renderer.Renderer.ListItem(out, text, flags)
+func (r *HugoHTMLRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
+ if !r.Config.TaskLists {
+ r.Renderer.ListItem(out, text, flags)
return
}
@@ -87,17 +87,17 @@
text = append([]byte(`<input type="checkbox" checked disabled class="task-list-item">`), text[3:]...)
}
- renderer.Renderer.ListItem(out, text, flags)
+ r.Renderer.ListItem(out, text, flags)
}
// List adds task list support to the Blackfriday renderer.
-func (renderer *HugoHTMLRenderer) List(out *bytes.Buffer, text func() bool, flags int) {
- if !renderer.Config.TaskLists {
- renderer.Renderer.List(out, text, flags)
+func (r *HugoHTMLRenderer) List(out *bytes.Buffer, text func() bool, flags int) {
+ if !r.Config.TaskLists {
+ r.Renderer.List(out, text, flags)
return
}
marker := out.Len()
- renderer.Renderer.List(out, text, flags)
+ r.Renderer.List(out, text, flags)
if out.Len() > marker {
list := out.Bytes()[marker:]
if bytes.Contains(list, []byte("task-list-item")) {
@@ -114,13 +114,14 @@
// Enabling Hugo to customise the rendering experience
type HugoMmarkHTMLRenderer struct {
mmark.Renderer
+ Cfg config.Provider
}
-func (renderer *HugoMmarkHTMLRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string, caption []byte, subfigure bool, callouts bool) {
- if viper.GetBool("pygmentsCodeFences") && (lang != "" || viper.GetBool("pygmentsCodeFencesGuessSyntax")) {
+func (r *HugoMmarkHTMLRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string, caption []byte, subfigure bool, callouts bool) {
+ if r.Cfg.GetBool("pygmentsCodeFences") && (lang != "" || r.Cfg.GetBool("pygmentsCodeFencesGuessSyntax")) {
str := html.UnescapeString(string(text))
- out.WriteString(Highlight(str, lang, ""))
+ out.WriteString(Highlight(r.Cfg, str, lang, ""))
} else {
- renderer.Renderer.BlockCode(out, text, lang, caption, subfigure, callouts)
+ r.Renderer.BlockCode(out, text, lang, caption, subfigure, callouts)
}
}
--- a/helpers/content_renderer_test.go
+++ b/helpers/content_renderer_test.go
@@ -22,9 +22,9 @@
)
// Renders a codeblock using Blackfriday
-func render(input string) string {
- ctx := newViperProvidedRenderingContext()
- render := getHTMLRenderer(0, ctx)
+func (c ContentSpec) render(input string) string {
+ ctx := newRenderingContext(c.cfg)
+ render := c.getHTMLRenderer(0, ctx)
buf := &bytes.Buffer{}
render.BlockCode(buf, []byte(input), "html")
@@ -32,9 +32,9 @@
}
// Renders a codeblock using Mmark
-func renderWithMmark(input string) string {
- ctx := newViperProvidedRenderingContext()
- render := getMmarkHTMLRenderer(0, ctx)
+func (c ContentSpec) renderWithMmark(input string) string {
+ ctx := newRenderingContext(c.cfg)
+ render := c.getMmarkHTMLRenderer(0, ctx)
buf := &bytes.Buffer{}
render.BlockCode(buf, []byte(input), "html", []byte(""), false, false)
@@ -59,16 +59,16 @@
{false, "<html></html>", `(?s)^<pre><code class="language-html">.*?</code></pre>\n$`},
}
- viper.Reset()
- defer viper.Reset()
+ for i, d := range data {
+ v := viper.New()
- viper.Set("pygmentsStyle", "monokai")
- viper.Set("pygmentsUseClasses", true)
+ v.Set("pygmentsStyle", "monokai")
+ v.Set("pygmentsUseClasses", true)
+ v.Set("pygmentsCodeFences", d.enabled)
- for i, d := range data {
- viper.Set("pygmentsCodeFences", d.enabled)
+ c := NewContentSpec(v)
- result := render(d.input)
+ result := c.render(d.input)
expectedRe, err := regexp.Compile(d.expected)
@@ -81,7 +81,7 @@
t.Errorf("Test %d failed. BlackFriday enabled:%t, Expected:\n%q got:\n%q", i, d.enabled, d.expected, result)
}
- result = renderWithMmark(d.input)
+ result = c.renderWithMmark(d.input)
matched = expectedRe.MatchString(result)
if !matched {
t.Errorf("Test %d failed. Mmark enabled:%t, Expected:\n%q got:\n%q", i, d.enabled, d.expected, result)
@@ -90,6 +90,8 @@
}
func TestBlackfridayTaskList(t *testing.T) {
+ c := newTestContentSpec()
+
for i, this := range []struct {
markdown string
taskListEnabled bool
@@ -118,11 +120,11 @@
</ul>
`},
} {
- blackFridayConfig := NewBlackfriday(viper.GetViper())
+ blackFridayConfig := c.NewBlackfriday()
blackFridayConfig.TaskLists = this.taskListEnabled
ctx := &RenderingContext{Content: []byte(this.markdown), PageFmt: "markdown", Config: blackFridayConfig}
- result := string(RenderBytes(ctx))
+ result := string(c.RenderBytes(ctx))
if result != this.expect {
t.Errorf("[%d] got \n%v but expected \n%v", i, result, this.expect)
--- a/helpers/content_test.go
+++ b/helpers/content_test.go
@@ -21,7 +21,6 @@
"github.com/miekg/mmark"
"github.com/russross/blackfriday"
- "github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)
@@ -152,8 +151,9 @@
}
func TestGetHTMLRendererFlags(t *testing.T) {
- ctx := newViperProvidedRenderingContext()
- renderer := getHTMLRenderer(blackfriday.HTML_USE_XHTML, ctx)
+ c := newTestContentSpec()
+ ctx := newRenderingContext(c.cfg)
+ renderer := c.getHTMLRenderer(blackfriday.HTML_USE_XHTML, ctx)
flags := renderer.GetFlags()
if flags&blackfriday.HTML_USE_XHTML != blackfriday.HTML_USE_XHTML {
t.Errorf("Test flag: %d was not found amongs set flags:%d; Result: %d", blackfriday.HTML_USE_XHTML, flags, flags&blackfriday.HTML_USE_XHTML)
@@ -161,6 +161,8 @@
}
func TestGetHTMLRendererAllFlags(t *testing.T) {
+ c := newTestContentSpec()
+
type data struct {
testFlag int
}
@@ -176,7 +178,7 @@
{blackfriday.HTML_SMARTYPANTS_LATEX_DASHES},
}
defaultFlags := blackfriday.HTML_USE_XHTML
- ctx := newViperProvidedRenderingContext()
+ ctx := newRenderingContext(c.cfg)
ctx.Config = ctx.getConfig()
ctx.Config.AngledQuotes = true
ctx.Config.Fractions = true
@@ -186,7 +188,7 @@
ctx.Config.SmartDashes = true
ctx.Config.Smartypants = true
ctx.Config.SourceRelativeLinksEval = true
- renderer := getHTMLRenderer(defaultFlags, ctx)
+ renderer := c.getHTMLRenderer(defaultFlags, ctx)
actualFlags := renderer.GetFlags()
var expectedFlags int
//OR-ing flags together...
@@ -199,12 +201,13 @@
}
func TestGetHTMLRendererAnchors(t *testing.T) {
- ctx := newViperProvidedRenderingContext()
+ c := newTestContentSpec()
+ ctx := newRenderingContext(c.cfg)
ctx.DocumentID = "testid"
ctx.Config = ctx.getConfig()
ctx.Config.PlainIDAnchors = false
- actualRenderer := getHTMLRenderer(0, ctx)
+ actualRenderer := c.getHTMLRenderer(0, ctx)
headerBuffer := &bytes.Buffer{}
footnoteBuffer := &bytes.Buffer{}
expectedFootnoteHref := []byte("href=\"#fn:testid:href\"")
@@ -223,11 +226,12 @@
}
func TestGetMmarkHTMLRenderer(t *testing.T) {
- ctx := newViperProvidedRenderingContext()
+ c := newTestContentSpec()
+ ctx := newRenderingContext(c.cfg)
ctx.DocumentID = "testid"
ctx.Config = ctx.getConfig()
ctx.Config.PlainIDAnchors = false
- actualRenderer := getMmarkHTMLRenderer(0, ctx)
+ actualRenderer := c.getMmarkHTMLRenderer(0, ctx)
headerBuffer := &bytes.Buffer{}
footnoteBuffer := &bytes.Buffer{}
@@ -247,7 +251,8 @@
}
func TestGetMarkdownExtensionsMasksAreRemovedFromExtensions(t *testing.T) {
- ctx := newViperProvidedRenderingContext()
+ c := newTestContentSpec()
+ ctx := newRenderingContext(c.cfg)
ctx.Config = ctx.getConfig()
ctx.Config.Extensions = []string{"headerId"}
ctx.Config.ExtensionsMask = []string{"noIntraEmphasis"}
@@ -262,7 +267,8 @@
type data struct {
testFlag int
}
- ctx := newViperProvidedRenderingContext()
+ c := newTestContentSpec()
+ ctx := newRenderingContext(c.cfg)
ctx.Config = ctx.getConfig()
ctx.Config.Extensions = []string{""}
ctx.Config.ExtensionsMask = []string{""}
@@ -294,7 +300,8 @@
}
func TestGetMarkdownExtensionsAddingFlagsThroughRenderingContext(t *testing.T) {
- ctx := newViperProvidedRenderingContext()
+ c := newTestContentSpec()
+ ctx := newRenderingContext(c.cfg)
ctx.Config = ctx.getConfig()
ctx.Config.Extensions = []string{"definitionLists"}
ctx.Config.ExtensionsMask = []string{""}
@@ -306,10 +313,11 @@
}
func TestGetMarkdownRenderer(t *testing.T) {
- ctx := newViperProvidedRenderingContext()
+ c := newTestContentSpec()
+ ctx := newRenderingContext(c.cfg)
ctx.Content = []byte("testContent")
ctx.Config = ctx.getConfig()
- actualRenderedMarkdown := markdownRender(ctx)
+ actualRenderedMarkdown := c.markdownRender(ctx)
expectedRenderedMarkdown := []byte("<p>testContent</p>\n")
if !bytes.Equal(actualRenderedMarkdown, expectedRenderedMarkdown) {
t.Errorf("Actual rendered Markdown (%s) did not match expected markdown (%s)", actualRenderedMarkdown, expectedRenderedMarkdown)
@@ -317,10 +325,11 @@
}
func TestGetMarkdownRendererWithTOC(t *testing.T) {
- ctx := &RenderingContext{RenderTOC: true, ConfigProvider: viper.GetViper()}
+ c := newTestContentSpec()
+ ctx := &RenderingContext{RenderTOC: true, Cfg: c.cfg}
ctx.Content = []byte("testContent")
ctx.Config = ctx.getConfig()
- actualRenderedMarkdown := markdownRender(ctx)
+ actualRenderedMarkdown := c.markdownRender(ctx)
expectedRenderedMarkdown := []byte("<nav>\n</nav>\n\n<p>testContent</p>\n")
if !bytes.Equal(actualRenderedMarkdown, expectedRenderedMarkdown) {
t.Errorf("Actual rendered Markdown (%s) did not match expected markdown (%s)", actualRenderedMarkdown, expectedRenderedMarkdown)
@@ -332,7 +341,8 @@
type data struct {
testFlag int
}
- ctx := newViperProvidedRenderingContext()
+ c := newTestContentSpec()
+ ctx := newRenderingContext(c.cfg)
ctx.Config = ctx.getConfig()
ctx.Config.Extensions = []string{"tables"}
ctx.Config.ExtensionsMask = []string{""}
@@ -361,10 +371,11 @@
}
func TestMmarkRender(t *testing.T) {
- ctx := newViperProvidedRenderingContext()
+ c := newTestContentSpec()
+ ctx := newRenderingContext(c.cfg)
ctx.Content = []byte("testContent")
ctx.Config = ctx.getConfig()
- actualRenderedMarkdown := mmarkRender(ctx)
+ actualRenderedMarkdown := c.mmarkRender(ctx)
expectedRenderedMarkdown := []byte("<p>testContent</p>\n")
if !bytes.Equal(actualRenderedMarkdown, expectedRenderedMarkdown) {
t.Errorf("Actual rendered Markdown (%s) did not match expected markdown (%s)", actualRenderedMarkdown, expectedRenderedMarkdown)
--- a/helpers/general.go
+++ b/helpers/general.go
@@ -32,7 +32,6 @@
bp "github.com/spf13/hugo/bufferpool"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag"
- "github.com/spf13/viper"
)
// FilePathSeparator as defined by os.Separator.
@@ -196,8 +195,8 @@
}
// ThemeSet checks whether a theme is in use or not.
-func ThemeSet() bool {
- return viper.GetString("theme") != ""
+func (p *PathSpec) ThemeSet() bool {
+ return p.theme != ""
}
type logPrinter interface {
--- a/helpers/language.go
+++ b/helpers/language.go
@@ -19,8 +19,7 @@
"sync"
"github.com/spf13/cast"
-
- "github.com/spf13/viper"
+ "github.com/spf13/hugo/config"
)
// These are the settings that should only be looked up in the global Viper
@@ -41,8 +40,10 @@
LanguageName string
Title string
Weight int
- params map[string]interface{}
- paramsInit sync.Once
+
+ Cfg config.Provider
+ params map[string]interface{}
+ paramsInit sync.Once
}
func (l *Language) String() string {
@@ -49,18 +50,18 @@
return l.Lang
}
-func NewLanguage(lang string) *Language {
- return &Language{Lang: lang, params: make(map[string]interface{})}
+func NewLanguage(lang string, cfg config.Provider) *Language {
+ return &Language{Lang: lang, Cfg: cfg, params: make(map[string]interface{})}
}
-func NewDefaultLanguage() *Language {
- defaultLang := viper.GetString("defaultContentLanguage")
+func NewDefaultLanguage(cfg config.Provider) *Language {
+ defaultLang := cfg.GetString("defaultContentLanguage")
if defaultLang == "" {
defaultLang = "en"
}
- return NewLanguage(defaultLang)
+ return NewLanguage(defaultLang, cfg)
}
type Languages []*Language
@@ -83,7 +84,7 @@
// Merge with global config.
// TODO(bep) consider making this part of a constructor func.
- globalParams := viper.GetStringMap("params")
+ globalParams := l.Cfg.GetStringMap("params")
for k, v := range globalParams {
if _, ok := l.params[k]; !ok {
l.params[k] = v
@@ -132,5 +133,28 @@
return v
}
}
- return viper.Get(key)
+ return l.Cfg.Get(key)
+}
+
+// Set sets the value for the key in the language's params.
+func (l *Language) Set(key string, value interface{}) {
+ if l == nil {
+ panic("language not set")
+ }
+ key = strings.ToLower(key)
+ l.params[key] = value
+}
+
+// IsSet checks whether the key is set in the language or the related config store.
+func (l *Language) IsSet(key string) bool {
+ key = strings.ToLower(key)
+
+ key = strings.ToLower(key)
+ if !globalOnlySettings[key] {
+ if _, ok := l.params[key]; ok {
+ return true
+ }
+ }
+ return l.Cfg.IsSet(key)
+
}
--- a/helpers/language_test.go
+++ b/helpers/language_test.go
@@ -21,11 +21,12 @@
)
func TestGetGlobalOnlySetting(t *testing.T) {
- lang := NewDefaultLanguage()
+ v := viper.New()
+ lang := NewDefaultLanguage(v)
lang.SetParam("defaultContentLanguageInSubdir", false)
lang.SetParam("paginatePath", "side")
- viper.Set("defaultContentLanguageInSubdir", true)
- viper.Set("paginatePath", "page")
+ v.Set("defaultContentLanguageInSubdir", true)
+ v.Set("paginatePath", "page")
require.True(t, lang.GetBool("defaultContentLanguageInSubdir"))
require.Equal(t, "side", lang.GetString("paginatePath"))
--- a/helpers/path.go
+++ b/helpers/path.go
@@ -24,7 +24,6 @@
"unicode"
"github.com/spf13/afero"
- "github.com/spf13/viper"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
)
@@ -153,32 +152,32 @@
// AbsPathify creates an absolute path if given a relative path. If already
// absolute, the path is just cleaned.
-func AbsPathify(inPath string) string {
+func (p *PathSpec) AbsPathify(inPath string) string {
if filepath.IsAbs(inPath) {
return filepath.Clean(inPath)
}
// TODO(bep): Consider moving workingDir to argument list
- return filepath.Clean(filepath.Join(viper.GetString("workingDir"), inPath))
+ return filepath.Clean(filepath.Join(p.workingDir, inPath))
}
// GetLayoutDirPath returns the absolute path to the layout file dir
// for the current Hugo project.
-func GetLayoutDirPath() string {
- return AbsPathify(viper.GetString("layoutDir"))
+func (p *PathSpec) GetLayoutDirPath() string {
+ return p.AbsPathify(p.layoutDir)
}
// GetStaticDirPath returns the absolute path to the static file dir
// for the current Hugo project.
-func GetStaticDirPath() string {
- return AbsPathify(viper.GetString("staticDir"))
+func (p *PathSpec) GetStaticDirPath() string {
+ return p.AbsPathify(p.staticDir)
}
// GetThemeDir gets the root directory of the current theme, if there is one.
// If there is no theme, returns the empty string.
-func GetThemeDir() string {
- if ThemeSet() {
- return AbsPathify(filepath.Join(viper.GetString("themesDir"), viper.GetString("theme")))
+func (p *PathSpec) GetThemeDir() string {
+ if p.ThemeSet() {
+ return p.AbsPathify(filepath.Join(p.themesDir, p.theme))
}
return ""
}
@@ -185,9 +184,9 @@
// GetRelativeThemeDir gets the relative root directory of the current theme, if there is one.
// If there is no theme, returns the empty string.
-func GetRelativeThemeDir() string {
- if ThemeSet() {
- return strings.TrimPrefix(filepath.Join(viper.GetString("themesDir"), viper.GetString("theme")), FilePathSeparator)
+func (p *PathSpec) GetRelativeThemeDir() string {
+ if p.ThemeSet() {
+ return strings.TrimPrefix(filepath.Join(p.themesDir, p.theme), FilePathSeparator)
}
return ""
}
@@ -211,13 +210,13 @@
}
func (p *PathSpec) getThemeDirPath(path string) (string, error) {
- if !ThemeSet() {
+ if !p.ThemeSet() {
return "", ErrThemeUndefined
}
- themeDir := filepath.Join(GetThemeDir(), path)
+ themeDir := filepath.Join(p.GetThemeDir(), path)
if _, err := p.fs.Source.Stat(themeDir); os.IsNotExist(err) {
- return "", fmt.Errorf("Unable to find %s directory for theme %s in %s", path, viper.GetString("theme"), themeDir)
+ return "", fmt.Errorf("Unable to find %s directory for theme %s in %s", path, p.theme, themeDir)
}
return themeDir, nil
@@ -235,7 +234,7 @@
// It does so by taking either the project's static path or the theme's static
// path into consideration.
func (p *PathSpec) MakeStaticPathRelative(inPath string) (string, error) {
- staticDir := GetStaticDirPath()
+ staticDir := p.GetStaticDirPath()
themeStaticDir := p.GetThemesDirPath()
return makePathRelative(inPath, staticDir, themeStaticDir)
@@ -360,20 +359,20 @@
}
// PaginateAliasPath creates a path used to access the aliases in the paginator.
-func PaginateAliasPath(base string, page int) string {
- paginatePath := Config().GetString("paginatePath")
- uglify := viper.GetBool("uglyURLs")
- var p string
+func (p *PathSpec) PaginateAliasPath(base string, page int) string {
+ paginatePath := p.paginatePath
+ uglify := p.uglyURLs
+ var pth string
if base != "" {
- p = filepath.FromSlash(fmt.Sprintf("/%s/%s/%d", base, paginatePath, page))
+ pth = filepath.FromSlash(fmt.Sprintf("/%s/%s/%d", base, paginatePath, page))
} else {
- p = filepath.FromSlash(fmt.Sprintf("/%s/%d", paginatePath, page))
+ pth = filepath.FromSlash(fmt.Sprintf("/%s/%d", paginatePath, page))
}
if uglify {
- p += ".html"
+ pth += ".html"
}
- return p
+ return pth
}
// GuessSection returns the section given a source path.
--- a/helpers/path_test.go
+++ b/helpers/path_test.go
@@ -34,15 +34,7 @@
"github.com/spf13/viper"
)
-func initCommonTestConfig() {
- viper.Set("currentContentLanguage", NewLanguage("en"))
-}
-
func TestMakePath(t *testing.T) {
- viper.Reset()
- defer viper.Reset()
- initCommonTestConfig()
-
tests := []struct {
input string
expected string
@@ -64,8 +56,10 @@
}
for _, test := range tests {
- viper.Set("removePathAccents", test.removeAccents)
- p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
+ v := viper.New()
+ l := NewDefaultLanguage(v)
+ v.Set("removePathAccents", test.removeAccents)
+ p := NewPathSpec(hugofs.NewMem(v), l)
output := p.MakePath(test.input)
if output != test.expected {
@@ -75,12 +69,10 @@
}
func TestMakePathSanitized(t *testing.T) {
- viper.Reset()
- defer viper.Reset()
- initCommonTestConfig()
+ v := viper.New()
+ l := NewDefaultLanguage(v)
+ p := NewPathSpec(hugofs.NewMem(v), l)
- p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
-
tests := []struct {
input string
expected string
@@ -102,13 +94,13 @@
}
func TestMakePathSanitizedDisablePathToLower(t *testing.T) {
- viper.Reset()
- defer viper.Reset()
+ v := viper.New()
- initCommonTestConfig()
- viper.Set("disablePathToLower", true)
- p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
+ v.Set("disablePathToLower", true)
+ l := NewDefaultLanguage(v)
+ p := NewPathSpec(hugofs.NewMem(v), l)
+
tests := []struct {
input string
expected string
@@ -553,9 +545,9 @@
for i, d := range data {
viper.Reset()
// todo see comment in AbsPathify
- viper.Set("workingDir", d.workingDir)
+ ps := newTestDefaultPathSpec("workingDir", d.workingDir)
- expected := AbsPathify(d.inPath)
+ expected := ps.AbsPathify(d.inPath)
if d.expected != expected {
t.Errorf("Test %d failed. Expected %q but got %q", i, d.expected, expected)
}
@@ -563,9 +555,9 @@
t.Logf("Running platform specific path tests for %s", runtime.GOOS)
if runtime.GOOS == "windows" {
for i, d := range windowsData {
- viper.Set("workingDir", d.workingDir)
+ ps := newTestDefaultPathSpec("workingDir", d.workingDir)
- expected := AbsPathify(d.inPath)
+ expected := ps.AbsPathify(d.inPath)
if d.expected != expected {
t.Errorf("Test %d failed. Expected %q but got %q", i, d.expected, expected)
}
@@ -572,9 +564,9 @@
}
} else {
for i, d := range unixData {
- viper.Set("workingDir", d.workingDir)
+ ps := newTestDefaultPathSpec("workingDir", d.workingDir)
- expected := AbsPathify(d.inPath)
+ expected := ps.AbsPathify(d.inPath)
if d.expected != expected {
t.Errorf("Test %d failed. Expected %q but got %q", i, d.expected, expected)
}
--- a/helpers/pathspec.go
+++ b/helpers/pathspec.go
@@ -16,6 +16,7 @@
import (
"fmt"
+ "github.com/spf13/hugo/config"
"github.com/spf13/hugo/hugofs"
)
@@ -26,11 +27,20 @@
uglyURLs bool
canonifyURLs bool
- currentContentLanguage *Language
+ language *Language
// pagination path handling
paginatePath string
+ baseURL string
+ theme string
+
+ // Directories
+ themesDir string
+ layoutDir string
+ workingDir string
+ staticDir string
+
// The PathSpec looks up its config settings in both the current language
// and then in the global Viper config.
// Some settings, the settings listed below, does not make sense to be set
@@ -45,31 +55,35 @@
}
func (p PathSpec) String() string {
- return fmt.Sprintf("PathSpec, language %q, prefix %q, multilingual: %T", p.currentContentLanguage.Lang, p.getLanguagePrefix(), p.multilingual)
+ return fmt.Sprintf("PathSpec, language %q, prefix %q, multilingual: %T", p.language.Lang, p.getLanguagePrefix(), p.multilingual)
}
-// NewPathSpec creats a new PathSpec from the given filesystems and ConfigProvider.
-func NewPathSpec(fs *hugofs.Fs, config ConfigProvider) *PathSpec {
+// NewPathSpec creats a new PathSpec from the given filesystems and Language.
+func NewPathSpec(fs *hugofs.Fs, cfg config.Provider) *PathSpec {
- currCl, ok := config.Get("currentContentLanguage").(*Language)
-
- if !ok {
- // TODO(bep) globals
- currCl = NewLanguage("en")
+ ps := &PathSpec{
+ fs: fs,
+ disablePathToLower: cfg.GetBool("disablePathToLower"),
+ removePathAccents: cfg.GetBool("removePathAccents"),
+ uglyURLs: cfg.GetBool("uglyURLs"),
+ canonifyURLs: cfg.GetBool("canonifyURLs"),
+ multilingual: cfg.GetBool("multilingual"),
+ defaultContentLanguageInSubdir: cfg.GetBool("defaultContentLanguageInSubdir"),
+ defaultContentLanguage: cfg.GetString("defaultContentLanguage"),
+ paginatePath: cfg.GetString("paginatePath"),
+ baseURL: cfg.GetString("baseURL"),
+ themesDir: cfg.GetString("themesDir"),
+ layoutDir: cfg.GetString("layoutDir"),
+ workingDir: cfg.GetString("workingDir"),
+ staticDir: cfg.GetString("staticDir"),
+ theme: cfg.GetString("theme"),
}
- return &PathSpec{
- fs: fs,
- disablePathToLower: config.GetBool("disablePathToLower"),
- removePathAccents: config.GetBool("removePathAccents"),
- uglyURLs: config.GetBool("uglyURLs"),
- canonifyURLs: config.GetBool("canonifyURLs"),
- multilingual: config.GetBool("multilingual"),
- defaultContentLanguageInSubdir: config.GetBool("defaultContentLanguageInSubdir"),
- defaultContentLanguage: config.GetString("defaultContentLanguage"),
- currentContentLanguage: currCl,
- paginatePath: config.GetString("paginatePath"),
+ if language, ok := cfg.(*Language); ok {
+ ps.language = language
}
+
+ return ps
}
// PaginatePath returns the configured root path used for paginator pages.
--- a/helpers/pathspec_test.go
+++ b/helpers/pathspec_test.go
@@ -23,17 +23,24 @@
)
func TestNewPathSpecFromConfig(t *testing.T) {
- viper.Set("disablePathToLower", true)
- viper.Set("removePathAccents", true)
- viper.Set("uglyURLs", true)
- viper.Set("multilingual", true)
- viper.Set("defaultContentLanguageInSubdir", true)
- viper.Set("defaultContentLanguage", "no")
- viper.Set("currentContentLanguage", NewLanguage("no"))
- viper.Set("canonifyURLs", true)
- viper.Set("paginatePath", "side")
+ v := viper.New()
+ l := NewLanguage("no", v)
+ v.Set("disablePathToLower", true)
+ v.Set("removePathAccents", true)
+ v.Set("uglyURLs", true)
+ v.Set("multilingual", true)
+ v.Set("defaultContentLanguageInSubdir", true)
+ v.Set("defaultContentLanguage", "no")
+ v.Set("canonifyURLs", true)
+ v.Set("paginatePath", "side")
+ v.Set("baseURL", "http://base.com")
+ v.Set("themesDir", "thethemes")
+ v.Set("layoutDir", "thelayouts")
+ v.Set("workingDir", "thework")
+ v.Set("staticDir", "thestatic")
+ v.Set("theme", "thetheme")
- p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
+ p := NewPathSpec(hugofs.NewMem(v), l)
require.True(t, p.canonifyURLs)
require.True(t, p.defaultContentLanguageInSubdir)
@@ -42,6 +49,13 @@
require.True(t, p.removePathAccents)
require.True(t, p.uglyURLs)
require.Equal(t, "no", p.defaultContentLanguage)
- require.Equal(t, "no", p.currentContentLanguage.Lang)
+ require.Equal(t, "no", p.language.Lang)
require.Equal(t, "side", p.paginatePath)
+
+ require.Equal(t, "http://base.com", p.baseURL)
+ require.Equal(t, "thethemes", p.themesDir)
+ require.Equal(t, "thelayouts", p.layoutDir)
+ require.Equal(t, "thework", p.workingDir)
+ require.Equal(t, "thestatic", p.staticDir)
+ require.Equal(t, "thetheme", p.theme)
}
--- a/helpers/pygments.go
+++ b/helpers/pygments.go
@@ -24,9 +24,9 @@
"sort"
"strings"
+ "github.com/spf13/hugo/config"
"github.com/spf13/hugo/hugofs"
jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
)
const pygmentsBin = "pygmentize"
@@ -41,13 +41,13 @@
}
// Highlight takes some code and returns highlighted code.
-func Highlight(code, lang, optsStr string) string {
+func Highlight(cfg config.Provider, code, lang, optsStr string) string {
if !HasPygments() {
jww.WARN.Println("Highlighting requires Pygments to be installed and in the path")
return code
}
- options, err := parsePygmentsOpts(optsStr)
+ options, err := parsePygmentsOpts(cfg, optsStr)
if err != nil {
jww.ERROR.Print(err.Error())
@@ -62,8 +62,8 @@
fs := hugofs.Os
- ignoreCache := viper.GetBool("ignoreCache")
- cacheDir := viper.GetString("cacheDir")
+ ignoreCache := cfg.GetBool("ignoreCache")
+ cacheDir := cfg.GetString("cacheDir")
var cachefile string
if !ignoreCache && cacheDir != "" {
@@ -195,19 +195,19 @@
return optionsStr
}
-func parseDefaultPygmentsOpts() (map[string]string, error) {
+func parseDefaultPygmentsOpts(cfg config.Provider) (map[string]string, error) {
options := make(map[string]string)
- err := parseOptions(options, viper.GetString("pygmentsOptions"))
+ err := parseOptions(options, cfg.GetString("pygmentsOptions"))
if err != nil {
return nil, err
}
- if viper.IsSet("pygmentsStyle") {
- options["style"] = viper.GetString("pygmentsStyle")
+ if cfg.IsSet("pygmentsStyle") {
+ options["style"] = cfg.GetString("pygmentsStyle")
}
- if viper.IsSet("pygmentsUseClasses") {
- if viper.GetBool("pygmentsUseClasses") {
+ if cfg.IsSet("pygmentsUseClasses") {
+ if cfg.GetBool("pygmentsUseClasses") {
options["noclasses"] = "false"
} else {
options["noclasses"] = "true"
@@ -222,8 +222,8 @@
return options, nil
}
-func parsePygmentsOpts(in string) (string, error) {
- options, err := parseDefaultPygmentsOpts()
+func parsePygmentsOpts(cfg config.Provider, in string) (string, error) {
+ options, err := parseDefaultPygmentsOpts(cfg)
if err != nil {
return "", err
}
--- a/helpers/pygments_test.go
+++ b/helpers/pygments_test.go
@@ -34,11 +34,12 @@
{"boo=invalid", "foo", false, false},
{"style", "foo", false, false},
} {
- viper.Reset()
- viper.Set("pygmentsStyle", this.pygmentsStyle)
- viper.Set("pygmentsUseClasses", this.pygmentsUseClasses)
- result1, err := parsePygmentsOpts(this.in)
+ v := viper.New()
+ v.Set("pygmentsStyle", this.pygmentsStyle)
+ v.Set("pygmentsUseClasses", this.pygmentsUseClasses)
+
+ result1, err := parsePygmentsOpts(v, this.in)
if b, ok := this.expect1.(bool); ok && !b {
if err == nil {
t.Errorf("[%d] parsePygmentArgs didn't return an expected error", i)
@@ -70,19 +71,19 @@
{"style=foo,noclasses=false", nil, nil, "style=override,noclasses=override"},
{"style=foo,noclasses=false", "override", false, "style=override,noclasses=override"},
} {
- viper.Reset()
+ v := viper.New()
- viper.Set("pygmentsOptions", this.pygmentsOptions)
+ v.Set("pygmentsOptions", this.pygmentsOptions)
if s, ok := this.pygmentsStyle.(string); ok {
- viper.Set("pygmentsStyle", s)
+ v.Set("pygmentsStyle", s)
}
if b, ok := this.pygmentsUseClasses.(bool); ok {
- viper.Set("pygmentsUseClasses", b)
+ v.Set("pygmentsUseClasses", b)
}
- result, err := parsePygmentsOpts(this.in)
+ result, err := parsePygmentsOpts(v, this.in)
if err != nil {
t.Errorf("[%d] parsePygmentArgs failed: %s", i, err)
continue
--- /dev/null
+++ b/helpers/testhelpers_test.go
@@ -1,0 +1,37 @@
+package helpers
+
+import (
+ "github.com/spf13/viper"
+
+ "github.com/spf13/hugo/hugofs"
+)
+
+func newTestPathSpec(fs *hugofs.Fs, v *viper.Viper) *PathSpec {
+ l := NewDefaultLanguage(v)
+ return NewPathSpec(fs, l)
+}
+
+func newTestDefaultPathSpec(configKeyValues ...interface{}) *PathSpec {
+ v := viper.New()
+ fs := hugofs.NewMem(v)
+ cfg := newTestCfg(fs)
+
+ for i := 0; i < len(configKeyValues); i += 2 {
+ cfg.Set(configKeyValues[i].(string), configKeyValues[i+1])
+ }
+ return newTestPathSpec(fs, cfg)
+}
+
+func newTestCfg(fs *hugofs.Fs) *viper.Viper {
+ v := viper.New()
+
+ v.SetFs(fs.Source)
+
+ return v
+
+}
+
+func newTestContentSpec() *ContentSpec {
+ v := viper.New()
+ return NewContentSpec(v)
+}
--- a/helpers/url.go
+++ b/helpers/url.go
@@ -21,7 +21,6 @@
"github.com/PuerkitoBio/purell"
jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
)
type pathBridge struct {
@@ -158,7 +157,7 @@
return in
}
- baseURL := viper.GetString("baseURL")
+ baseURL := p.baseURL
if strings.HasPrefix(in, "/") {
p, err := url.Parse(baseURL)
if err != nil {
@@ -200,7 +199,7 @@
defaultLang := p.defaultContentLanguage
defaultInSubDir := p.defaultContentLanguageInSubdir
- currentLang := p.currentContentLanguage.Lang
+ currentLang := p.language.Lang
if currentLang == "" || (currentLang == defaultLang && !defaultInSubDir) {
return ""
}
@@ -220,7 +219,7 @@
// RelURL creates a URL relative to the BaseURL root.
// Note: The result URL will not include the context root if canonifyURLs is enabled.
func (p *PathSpec) RelURL(in string, addLanguage bool) string {
- baseURL := viper.GetString("baseURL")
+ baseURL := p.baseURL
canonifyURLs := p.canonifyURLs
if (!strings.HasPrefix(in, baseURL) && strings.HasPrefix(in, "http")) || strings.HasPrefix(in, "//") {
return in
--- a/helpers/url_test.go
+++ b/helpers/url_test.go
@@ -25,9 +25,10 @@
)
func TestURLize(t *testing.T) {
- initCommonTestConfig()
- p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
+ v := viper.New()
+ l := NewDefaultLanguage(v)
+ p := NewPathSpec(hugofs.NewMem(v), l)
tests := []struct {
input string
@@ -62,11 +63,10 @@
}
func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool, lang string) {
- viper.Reset()
- viper.Set("multilingual", multilingual)
- viper.Set("currentContentLanguage", NewLanguage(lang))
- viper.Set("defaultContentLanguage", "en")
- viper.Set("defaultContentLanguageInSubdir", defaultInSubDir)
+ v := viper.New()
+ v.Set("multilingual", multilingual)
+ v.Set("defaultContentLanguage", "en")
+ v.Set("defaultContentLanguageInSubdir", defaultInSubDir)
tests := []struct {
input string
@@ -86,10 +86,10 @@
{"http//foo", "http://base/path", "http://base/path/MULTIhttp/foo"},
}
- p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
-
for _, test := range tests {
- viper.Set("baseURL", test.baseURL)
+ v.Set("baseURL", test.baseURL)
+ l := NewLanguage(lang, v)
+ p := NewPathSpec(hugofs.NewMem(v), l)
output := p.AbsURL(test.input, addLanguage)
expected := test.expected
@@ -138,11 +138,10 @@
}
func doTestRelURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool, lang string) {
- viper.Reset()
- viper.Set("multilingual", multilingual)
- viper.Set("currentContentLanguage", NewLanguage(lang))
- viper.Set("defaultContentLanguage", "en")
- viper.Set("defaultContentLanguageInSubdir", defaultInSubDir)
+ v := viper.New()
+ v.Set("multilingual", multilingual)
+ v.Set("defaultContentLanguage", "en")
+ v.Set("defaultContentLanguageInSubdir", defaultInSubDir)
tests := []struct {
input string
@@ -165,9 +164,10 @@
}
for i, test := range tests {
- viper.Set("baseURL", test.baseURL)
- viper.Set("canonifyURLs", test.canonify)
- p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
+ v.Set("baseURL", test.baseURL)
+ v.Set("canonifyURLs", test.canonify)
+ l := NewLanguage(lang, v)
+ p := NewPathSpec(hugofs.NewMem(v), l)
output := p.RelURL(test.input, addLanguage)
@@ -252,8 +252,10 @@
}
for i, d := range data {
- viper.Set("uglyURLs", d.ugly)
- p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
+ v := viper.New()
+ v.Set("uglyURLs", d.ugly)
+ l := NewDefaultLanguage(v)
+ p := NewPathSpec(hugofs.NewMem(v), l)
output := p.URLPrep(d.input)
if d.output != output {
--- a/hugofs/fs.go
+++ b/hugofs/fs.go
@@ -16,7 +16,7 @@
import (
"github.com/spf13/afero"
- "github.com/spf13/viper"
+ "github.com/spf13/hugo/config"
)
// Os points to an Os Afero file system.
@@ -39,30 +39,37 @@
// NewDefault creates a new Fs with the OS file system
// as source and destination file systems.
-func NewDefault() *Fs {
+func NewDefault(cfg config.Provider) *Fs {
fs := &afero.OsFs{}
- return newFs(fs)
+ return newFs(fs, cfg)
}
// NewDefault creates a new Fs with the MemMapFs
// as source and destination file systems.
// Useful for testing.
-func NewMem() *Fs {
+func NewMem(cfg config.Provider) *Fs {
fs := &afero.MemMapFs{}
- return newFs(fs)
+ return newFs(fs, cfg)
}
-func newFs(base afero.Fs) *Fs {
+// NewFrom creates a new Fs based on the provided Afero Fs
+// as source and destination file systems.
+// Useful for testing.
+func NewFrom(fs afero.Fs, cfg config.Provider) *Fs {
+ return newFs(fs, cfg)
+}
+
+func newFs(base afero.Fs, cfg config.Provider) *Fs {
return &Fs{
Source: base,
Destination: base,
Os: &afero.OsFs{},
- WorkingDir: getWorkingDirFs(base),
+ WorkingDir: getWorkingDirFs(base, cfg),
}
}
-func getWorkingDirFs(base afero.Fs) *afero.BasePathFs {
- workingDir := viper.GetString("workingDir")
+func getWorkingDirFs(base afero.Fs, cfg config.Provider) *afero.BasePathFs {
+ workingDir := cfg.GetString("workingDir")
if workingDir != "" {
return afero.NewBasePathFs(afero.NewReadOnlyFs(base), workingDir).(*afero.BasePathFs)
--- a/hugofs/fs_test.go
+++ b/hugofs/fs_test.go
@@ -22,11 +22,9 @@
)
func TestNewDefault(t *testing.T) {
- viper.Reset()
- defer viper.Reset()
+ v := viper.New()
+ f := NewDefault(v)
- f := NewDefault()
-
assert.NotNil(t, f.Source)
assert.IsType(t, new(afero.OsFs), f.Source)
assert.NotNil(t, f.Destination)
@@ -39,11 +37,9 @@
}
func TestNewMem(t *testing.T) {
- viper.Reset()
- defer viper.Reset()
+ v := viper.New()
+ f := NewMem(v)
- f := NewMem()
-
assert.NotNil(t, f.Source)
assert.IsType(t, new(afero.MemMapFs), f.Source)
assert.NotNil(t, f.Destination)
@@ -53,12 +49,11 @@
}
func TestWorkingDir(t *testing.T) {
- viper.Reset()
- defer viper.Reset()
+ v := viper.New()
- viper.Set("workingDir", "/a/b/")
+ v.Set("workingDir", "/a/b/")
- f := NewMem()
+ f := NewMem(v)
assert.NotNil(t, f.WorkingDir)
assert.IsType(t, new(afero.BasePathFs), f.WorkingDir)
--- a/hugolib/alias_test.go
+++ b/hugolib/alias_test.go
@@ -18,7 +18,6 @@
"testing"
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/hugofs"
"github.com/stretchr/testify/require"
)
@@ -33,31 +32,37 @@
const aliasTemplate = "<html><body>ALIASTEMPLATE</body></html>"
func TestAlias(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
- fs := hugofs.NewMem()
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
writeSource(t, fs, filepath.Join("content", "page.md"), pageWithAlias)
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), basicTemplate)
- buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
// the real page
- assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
+ th.assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
// the alias redirector
- assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "<meta http-equiv=\"refresh\" content=\"0; ")
+ th.assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "<meta http-equiv=\"refresh\" content=\"0; ")
}
func TestAliasTemplate(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
- fs := hugofs.NewMem()
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
writeSource(t, fs, filepath.Join("content", "page.md"), pageWithAlias)
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), basicTemplate)
writeSource(t, fs, filepath.Join("layouts", "alias.html"), aliasTemplate)
- sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
+ sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
@@ -64,7 +69,7 @@
require.NoError(t, sites.Build(BuildCfg{}))
// the real page
- assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
+ th.assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
// the alias redirector
- assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "ALIASTEMPLATE")
+ th.assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "ALIASTEMPLATE")
}
--- a/hugolib/case_insensitive_test.go
+++ b/hugolib/case_insensitive_test.go
@@ -19,10 +19,10 @@
"strings"
"testing"
- "github.com/spf13/viper"
-
+ "github.com/spf13/afero"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
+ "github.com/stretchr/testify/require"
)
var (
@@ -111,26 +111,28 @@
`
)
-func caseMixingTestsWriteCommonSources(t *testing.T, fs *hugofs.Fs) {
- writeSource(t, fs, filepath.Join("content", "sect1", "page1.md"), caseMixingPage1)
- writeSource(t, fs, filepath.Join("content", "sect2", "page2.md"), caseMixingPage2)
- writeSource(t, fs, filepath.Join("content", "sect1", "page1.en.md"), caseMixingPage1En)
+func caseMixingTestsWriteCommonSources(t *testing.T, fs afero.Fs) {
+ writeToFs(t, fs, filepath.Join("content", "sect1", "page1.md"), caseMixingPage1)
+ writeToFs(t, fs, filepath.Join("content", "sect2", "page2.md"), caseMixingPage2)
+ writeToFs(t, fs, filepath.Join("content", "sect1", "page1.en.md"), caseMixingPage1En)
- writeSource(t, fs, "layouts/shortcodes/shortcode.html", `
+ writeToFs(t, fs, "layouts/shortcodes/shortcode.html", `
Shortcode Page: {{ .Page.Params.COLOR }}|{{ .Page.Params.Colors.Blue }}
Shortcode Site: {{ .Page.Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
`)
- writeSource(t, fs, "layouts/partials/partial.html", `
+ writeToFs(t, fs, "layouts/partials/partial.html", `
Partial Page: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}
Partial Site: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
`)
- writeSource(t, fs, "config.toml", caseMixingSiteConfigTOML)
+ writeToFs(t, fs, "config.toml", caseMixingSiteConfigTOML)
}
func TestCaseInsensitiveConfigurationVariations(t *testing.T) {
+ t.Parallel()
+
// See issues 2615, 1129, 2590 and maybe some others
// Also see 2598
//
@@ -143,22 +145,22 @@
// language: new and overridden values, in regular fields and nested paramsmap
// page frontmatter: regular fields, blackfriday config, param with nested map
- testCommonResetState()
+ mm := afero.NewMemMapFs()
- depsCfg := newTestDepsConfig()
- viper.SetFs(depsCfg.Fs.Source)
+ caseMixingTestsWriteCommonSources(t, mm)
- caseMixingTestsWriteCommonSources(t, depsCfg.Fs)
+ cfg, err := LoadConfig(mm, "", "config.toml")
+ require.NoError(t, err)
- if err := LoadGlobalConfig("", "config.toml"); err != nil {
- t.Fatalf("Failed to load config: %s", err)
- }
+ fs := hugofs.NewFrom(mm, cfg)
- writeSource(t, depsCfg.Fs, filepath.Join("layouts", "_default", "baseof.html"), `
+ th := testHelper{cfg}
+
+ writeSource(t, fs, filepath.Join("layouts", "_default", "baseof.html"), `
Block Page Colors: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}
{{ block "main" . }}default{{end}}`)
- writeSource(t, depsCfg.Fs, filepath.Join("layouts", "sect2", "single.html"), `
+ writeSource(t, fs, filepath.Join("layouts", "sect2", "single.html"), `
{{ define "main"}}
Page Colors: {{ .Params.CoLOR }}|{{ .Params.Colors.Blue }}
Site Colors: {{ .Site.Params.COlOR }}|{{ .Site.Params.COLORS.YELLOW }}
@@ -167,7 +169,7 @@
{{ end }}
`)
- writeSource(t, depsCfg.Fs, filepath.Join("layouts", "_default", "single.html"), `
+ writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `
Page Title: {{ .Title }}
Site Title: {{ .Site.Title }}
Site Lang Mood: {{ .Site.Language.Params.MOoD }}
@@ -177,7 +179,7 @@
{{ partial "partial.html" . }}
`)
- sites, err := NewHugoSitesFromConfiguration(depsCfg)
+ sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@@ -189,7 +191,7 @@
t.Fatalf("Failed to build sites: %s", err)
}
- assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
+ th.assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
"Page Colors: red|heavenly",
"Site Colors: green|yellow",
"Site Lang Mood: Happy",
@@ -202,7 +204,7 @@
"«Hi»", // angled quotes
)
- assertFileContent(t, sites.Fs, filepath.Join("public", "en", "sect1", "page1", "index.html"), true,
+ th.assertFileContent(t, sites.Fs, filepath.Join("public", "en", "sect1", "page1", "index.html"), true,
"Site Colors: Pink|golden",
"Page Colors: black|bluesy",
"Site Lang Mood: Thoughtful",
@@ -211,7 +213,7 @@
"“Hi”",
)
- assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect2", "page2", "index.html"), true,
+ th.assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect2", "page2", "index.html"), true,
"Page Colors: black|sky",
"Site Colors: green|yellow",
"Shortcode Page: black|sky",
@@ -222,6 +224,8 @@
}
func TestCaseInsensitiveConfigurationForAllTemplateEngines(t *testing.T) {
+ t.Parallel()
+
noOp := func(s string) string {
return s
}
@@ -252,17 +256,17 @@
func doTestCaseInsensitiveConfigurationForTemplateEngine(t *testing.T, suffix string, templateFixer func(s string) string) {
- testCommonResetState()
+ mm := afero.NewMemMapFs()
- fs := hugofs.NewMem()
- viper.SetFs(fs.Source)
+ caseMixingTestsWriteCommonSources(t, mm)
- caseMixingTestsWriteCommonSources(t, fs)
+ cfg, err := LoadConfig(mm, "", "config.toml")
+ require.NoError(t, err)
- if err := LoadGlobalConfig("", "config.toml"); err != nil {
- t.Fatalf("Failed to load config: %s", err)
- }
+ fs := hugofs.NewFrom(mm, cfg)
+ th := testHelper{cfg}
+
t.Log("Testing", suffix)
templTemplate := `
@@ -280,7 +284,7 @@
writeSource(t, fs, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
- sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
+ sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@@ -292,7 +296,7 @@
t.Fatalf("Failed to build sites: %s", err)
}
- assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
+ th.assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
"Page Colors: red|heavenly",
"Site Colors: green|yellow",
"Shortcode Page: red|heavenly",
--- a/hugolib/config.go
+++ b/hugolib/config.go
@@ -16,94 +16,100 @@
import (
"fmt"
+ "github.com/spf13/afero"
"github.com/spf13/hugo/helpers"
"github.com/spf13/viper"
)
-// LoadGlobalConfig loads Hugo configuration into the global Viper.
-func LoadGlobalConfig(relativeSourcePath, configFilename string) error {
+// LoadConfig loads Hugo configuration into a new Viper and then adds
+// a set of defaults.
+func LoadConfig(fs afero.Fs, relativeSourcePath, configFilename string) (*viper.Viper, error) {
+ v := viper.New()
+ v.SetFs(fs)
if relativeSourcePath == "" {
relativeSourcePath = "."
}
- viper.AutomaticEnv()
- viper.SetEnvPrefix("hugo")
- viper.SetConfigFile(configFilename)
+ v.AutomaticEnv()
+ v.SetEnvPrefix("hugo")
+ v.SetConfigFile(configFilename)
// See https://github.com/spf13/viper/issues/73#issuecomment-126970794
if relativeSourcePath == "" {
- viper.AddConfigPath(".")
+ v.AddConfigPath(".")
} else {
- viper.AddConfigPath(relativeSourcePath)
+ v.AddConfigPath(relativeSourcePath)
}
- err := viper.ReadInConfig()
+ err := v.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigParseError); ok {
- return err
+ return nil, err
}
- return 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, 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)
}
- viper.RegisterAlias("indexes", "taxonomies")
+ v.RegisterAlias("indexes", "taxonomies")
- loadDefaultSettings()
+ loadDefaultSettingsFor(v)
- return nil
+ return v, nil
}
-func loadDefaultSettings() {
- viper.SetDefault("cleanDestinationDir", false)
- viper.SetDefault("watch", false)
- viper.SetDefault("metaDataFormat", "toml")
- viper.SetDefault("disable404", false)
- viper.SetDefault("disableRSS", false)
- viper.SetDefault("disableSitemap", false)
- viper.SetDefault("disableRobotsTXT", false)
- viper.SetDefault("contentDir", "content")
- viper.SetDefault("layoutDir", "layouts")
- viper.SetDefault("staticDir", "static")
- viper.SetDefault("archetypeDir", "archetypes")
- viper.SetDefault("publishDir", "public")
- viper.SetDefault("dataDir", "data")
- viper.SetDefault("i18nDir", "i18n")
- viper.SetDefault("themesDir", "themes")
- viper.SetDefault("defaultLayout", "post")
- viper.SetDefault("buildDrafts", false)
- viper.SetDefault("buildFuture", false)
- viper.SetDefault("buildExpired", false)
- viper.SetDefault("uglyURLs", false)
- viper.SetDefault("verbose", false)
- viper.SetDefault("ignoreCache", false)
- viper.SetDefault("canonifyURLs", false)
- viper.SetDefault("relativeURLs", false)
- viper.SetDefault("removePathAccents", false)
- viper.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
- viper.SetDefault("permalinks", make(PermalinkOverrides, 0))
- viper.SetDefault("sitemap", Sitemap{Priority: -1, Filename: "sitemap.xml"})
- viper.SetDefault("defaultExtension", "html")
- viper.SetDefault("pygmentsStyle", "monokai")
- viper.SetDefault("pygmentsUseClasses", false)
- viper.SetDefault("pygmentsCodeFences", false)
- viper.SetDefault("pygmentsOptions", "")
- viper.SetDefault("disableLiveReload", false)
- viper.SetDefault("pluralizeListTitles", true)
- viper.SetDefault("preserveTaxonomyNames", false)
- viper.SetDefault("forceSyncStatic", false)
- viper.SetDefault("footnoteAnchorPrefix", "")
- viper.SetDefault("footnoteReturnLinkContents", "")
- viper.SetDefault("newContentEditor", "")
- viper.SetDefault("paginate", 10)
- viper.SetDefault("paginatePath", "page")
- viper.SetDefault("blackfriday", helpers.NewBlackfriday(viper.GetViper()))
- viper.SetDefault("rSSUri", "index.xml")
- viper.SetDefault("sectionPagesMenu", "")
- viper.SetDefault("disablePathToLower", false)
- viper.SetDefault("hasCJKLanguage", false)
- viper.SetDefault("enableEmoji", false)
- viper.SetDefault("pygmentsCodeFencesGuessSyntax", false)
- viper.SetDefault("useModTimeAsFallback", false)
- viper.SetDefault("currentContentLanguage", helpers.NewDefaultLanguage())
- viper.SetDefault("defaultContentLanguage", "en")
- viper.SetDefault("defaultContentLanguageInSubdir", false)
- viper.SetDefault("enableMissingTranslationPlaceholders", false)
- viper.SetDefault("enableGitInfo", false)
+func loadDefaultSettingsFor(v *viper.Viper) {
+
+ c := helpers.NewContentSpec(v)
+
+ v.SetDefault("cleanDestinationDir", false)
+ v.SetDefault("watch", false)
+ v.SetDefault("metaDataFormat", "toml")
+ v.SetDefault("disable404", false)
+ v.SetDefault("disableRSS", false)
+ v.SetDefault("disableSitemap", false)
+ v.SetDefault("disableRobotsTXT", false)
+ v.SetDefault("contentDir", "content")
+ v.SetDefault("layoutDir", "layouts")
+ v.SetDefault("staticDir", "static")
+ v.SetDefault("archetypeDir", "archetypes")
+ v.SetDefault("publishDir", "public")
+ v.SetDefault("dataDir", "data")
+ v.SetDefault("i18nDir", "i18n")
+ v.SetDefault("themesDir", "themes")
+ v.SetDefault("defaultLayout", "post")
+ v.SetDefault("buildDrafts", false)
+ v.SetDefault("buildFuture", false)
+ v.SetDefault("buildExpired", false)
+ v.SetDefault("uglyURLs", false)
+ v.SetDefault("verbose", false)
+ v.SetDefault("ignoreCache", false)
+ v.SetDefault("canonifyURLs", false)
+ v.SetDefault("relativeURLs", false)
+ v.SetDefault("removePathAccents", false)
+ v.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
+ v.SetDefault("permalinks", make(PermalinkOverrides, 0))
+ v.SetDefault("sitemap", Sitemap{Priority: -1, Filename: "sitemap.xml"})
+ v.SetDefault("defaultExtension", "html")
+ v.SetDefault("pygmentsStyle", "monokai")
+ v.SetDefault("pygmentsUseClasses", false)
+ v.SetDefault("pygmentsCodeFences", false)
+ v.SetDefault("pygmentsOptions", "")
+ v.SetDefault("disableLiveReload", false)
+ v.SetDefault("pluralizeListTitles", true)
+ v.SetDefault("preserveTaxonomyNames", false)
+ v.SetDefault("forceSyncStatic", false)
+ v.SetDefault("footnoteAnchorPrefix", "")
+ v.SetDefault("footnoteReturnLinkContents", "")
+ v.SetDefault("newContentEditor", "")
+ v.SetDefault("paginate", 10)
+ v.SetDefault("paginatePath", "page")
+ v.SetDefault("blackfriday", c.NewBlackfriday())
+ v.SetDefault("rSSUri", "index.xml")
+ v.SetDefault("sectionPagesMenu", "")
+ v.SetDefault("disablePathToLower", false)
+ v.SetDefault("hasCJKLanguage", false)
+ v.SetDefault("enableEmoji", false)
+ v.SetDefault("pygmentsCodeFencesGuessSyntax", false)
+ v.SetDefault("useModTimeAsFallback", false)
+ v.SetDefault("defaultContentLanguage", "en")
+ v.SetDefault("defaultContentLanguageInSubdir", false)
+ v.SetDefault("enableMissingTranslationPlaceholders", false)
+ v.SetDefault("enableGitInfo", false)
}
--- a/hugolib/config_test.go
+++ b/hugolib/config_test.go
@@ -16,15 +16,14 @@
import (
"testing"
- "github.com/spf13/hugo/helpers"
-
- "github.com/spf13/hugo/hugofs"
- "github.com/spf13/viper"
+ "github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
-func TestLoadGlobalConfig(t *testing.T) {
+func TestLoadConfig(t *testing.T) {
+ t.Parallel()
+
// Add a random config variable for testing.
// side = page in Norwegian.
configContent := `
@@ -31,13 +30,14 @@
PaginatePath = "side"
`
- fs := hugofs.NewMem()
- viper.SetFs(fs.Source)
+ mm := afero.NewMemMapFs()
- writeSource(t, fs, "hugo.toml", configContent)
+ writeToFs(t, mm, "hugo.toml", configContent)
- require.NoError(t, LoadGlobalConfig("", "hugo.toml"))
- assert.Equal(t, "side", helpers.Config().GetString("paginatePath"))
+ cfg, err := LoadConfig(mm, "", "hugo.toml")
+ require.NoError(t, err)
+
+ assert.Equal(t, "side", cfg.GetString("paginatePath"))
// default
- assert.Equal(t, "layouts", viper.GetString("layoutDir"))
+ assert.Equal(t, "layouts", cfg.GetString("layoutDir"))
}
--- a/hugolib/datafiles_test.go
+++ b/hugolib/datafiles_test.go
@@ -19,6 +19,13 @@
"strings"
"testing"
+ "io/ioutil"
+ "log"
+ "os"
+
+ "github.com/spf13/hugo/deps"
+ jww "github.com/spf13/jwalterweatherman"
+
"github.com/spf13/hugo/parser"
"github.com/spf13/hugo/source"
"github.com/stretchr/testify/require"
@@ -25,9 +32,11 @@
)
func TestDataDirJSON(t *testing.T) {
+ t.Parallel()
+
sources := []source.ByteSource{
- {Name: filepath.FromSlash("test/foo.json"), Content: []byte(`{ "bar": "foofoo" }`)},
- {Name: filepath.FromSlash("test.json"), Content: []byte(`{ "hello": [ { "world": "foo" } ] }`)},
+ {Name: filepath.FromSlash("data/test/foo.json"), Content: []byte(`{ "bar": "foofoo" }`)},
+ {Name: filepath.FromSlash("data/test.json"), Content: []byte(`{ "hello": [ { "world": "foo" } ] }`)},
}
expected, err := parser.HandleJSONMetaData([]byte(`{ "test": { "hello": [{ "world": "foo" }] , "foo": { "bar":"foofoo" } } }`))
@@ -36,12 +45,14 @@
t.Fatalf("Error %s", err)
}
- doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: sources}})
+ doTestDataDir(t, expected, sources)
}
func TestDataDirToml(t *testing.T) {
+ t.Parallel()
+
sources := []source.ByteSource{
- {Name: filepath.FromSlash("test/kung.toml"), Content: []byte("[foo]\nbar = 1")},
+ {Name: filepath.FromSlash("data/test/kung.toml"), Content: []byte("[foo]\nbar = 1")},
}
expected, err := parser.HandleTOMLMetaData([]byte("[test]\n[test.kung]\n[test.kung.foo]\nbar = 1"))
@@ -50,78 +61,102 @@
t.Fatalf("Error %s", err)
}
- doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: sources}})
+ doTestDataDir(t, expected, sources)
}
func TestDataDirYAMLWithOverridenValue(t *testing.T) {
+ t.Parallel()
+
sources := []source.ByteSource{
// filepath.Walk walks the files in lexical order, '/' comes before '.'. Simulate this:
- {Name: filepath.FromSlash("a.yaml"), Content: []byte("a: 1")},
- {Name: filepath.FromSlash("test/v1.yaml"), Content: []byte("v1-2: 2")},
- {Name: filepath.FromSlash("test/v2.yaml"), Content: []byte("v2:\n- 2\n- 3")},
- {Name: filepath.FromSlash("test.yaml"), Content: []byte("v1: 1")},
+ {Name: filepath.FromSlash("data/a.yaml"), Content: []byte("a: 1")},
+ {Name: filepath.FromSlash("data/test/v1.yaml"), Content: []byte("v1-2: 2")},
+ {Name: filepath.FromSlash("data/test/v2.yaml"), Content: []byte("v2:\n- 2\n- 3")},
+ {Name: filepath.FromSlash("data/test.yaml"), Content: []byte("v1: 1")},
}
expected := map[string]interface{}{"a": map[string]interface{}{"a": 1},
"test": map[string]interface{}{"v1": map[string]interface{}{"v1-2": 2}, "v2": map[string]interface{}{"v2": []interface{}{2, 3}}}}
- doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: sources}})
+ doTestDataDir(t, expected, sources)
}
// issue 892
func TestDataDirMultipleSources(t *testing.T) {
- s1 := []source.ByteSource{
- {Name: filepath.FromSlash("test/first.toml"), Content: []byte("bar = 1")},
- }
+ t.Parallel()
- s2 := []source.ByteSource{
- {Name: filepath.FromSlash("test/first.toml"), Content: []byte("bar = 2")},
- {Name: filepath.FromSlash("test/second.toml"), Content: []byte("tender = 2")},
+ sources := []source.ByteSource{
+ {Name: filepath.FromSlash("data/test/first.toml"), Content: []byte("bar = 1")},
+ {Name: filepath.FromSlash("themes/mytheme/data/test/first.toml"), Content: []byte("bar = 2")},
+ {Name: filepath.FromSlash("data/test/second.toml"), Content: []byte("tender = 2")},
}
expected, _ := parser.HandleTOMLMetaData([]byte("[test.first]\nbar = 1\n[test.second]\ntender=2"))
- doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: s1}, &source.InMemorySource{ByteSource: s2}})
+ doTestDataDir(t, expected, sources,
+ "theme", "mytheme")
}
func TestDataDirUnknownFormat(t *testing.T) {
+ t.Parallel()
+
sources := []source.ByteSource{
- {Name: filepath.FromSlash("test.roml"), Content: []byte("boo")},
+ {Name: filepath.FromSlash("data/test.roml"), Content: []byte("boo")},
}
- s, err := NewSiteDefaultLang()
- require.NoError(t, err)
- require.NoError(t, s.loadData([]source.Input{&source.InMemorySource{ByteSource: sources}}))
+ doTestDataDir(t, true, sources)
}
-func doTestDataDir(t *testing.T, expected interface{}, sources []source.Input) {
- s, err := NewSiteDefaultLang()
- require.NoError(t, err)
- require.NoError(t, s.loadData(sources))
+func doTestDataDir(t *testing.T, expected interface{}, sources []source.ByteSource, configKeyValues ...interface{}) {
+ var (
+ cfg, fs = newTestCfg()
+ )
- if !reflect.DeepEqual(expected, s.Data) {
+ for i := 0; i < len(configKeyValues); i += 2 {
+ cfg.Set(configKeyValues[i].(string), configKeyValues[i+1])
+ }
+
+ var (
+ logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+ depsCfg = deps.DepsCfg{Fs: fs, Cfg: cfg, Logger: logger}
+ )
+
+ writeSource(t, fs, filepath.Join("content", "dummy.md"), "content")
+ writeSourcesToSource(t, "", fs, sources...)
+
+ expectBuildError := false
+
+ if ok, shouldFail := expected.(bool); ok && shouldFail {
+ expectBuildError = true
+ }
+
+ s := buildSingleSiteExpected(t, expectBuildError, depsCfg, BuildCfg{SkipRender: true})
+
+ if !expectBuildError && !reflect.DeepEqual(expected, s.Data) {
t.Errorf("Expected structure\n%#v got\n%#v", expected, s.Data)
}
}
func TestDataFromShortcode(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
- cfg := newTestDepsConfig()
+ var (
+ cfg, fs = newTestCfg()
+ )
- writeSource(t, cfg.Fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
- writeSource(t, cfg.Fs, "layouts/_default/single.html", `
+ writeSource(t, fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
+ writeSource(t, fs, "layouts/_default/single.html", `
* Slogan from template: {{ .Site.Data.hugo.slogan }}
* {{ .Content }}`)
- writeSource(t, cfg.Fs, "layouts/shortcodes/d.html", `{{ .Page.Site.Data.hugo.slogan }}`)
- writeSource(t, cfg.Fs, "content/c.md", `---
+ writeSource(t, fs, "layouts/shortcodes/d.html", `{{ .Page.Site.Data.hugo.slogan }}`)
+ writeSource(t, fs, "content/c.md", `---
---
Slogan from shortcode: {{< d >}}
`)
- buildSingleSite(t, cfg, BuildCfg{})
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
- content := readSource(t, cfg.Fs, "public/c/index.html")
+ content := readSource(t, fs, "public/c/index.html")
require.True(t, strings.Contains(content, "Slogan from template: Hugo Rocks!"), content)
require.True(t, strings.Contains(content, "Slogan from shortcode: Hugo Rocks!"), content)
--- a/hugolib/embedded_shortcodes_test.go
+++ b/hugolib/embedded_shortcodes_test.go
@@ -17,33 +17,25 @@
"encoding/json"
"fmt"
"html/template"
- "net/url"
- "os"
- "regexp"
"strings"
"testing"
- "io/ioutil"
- "log"
"path/filepath"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/tplapi"
- jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
-var logger = jww.NewNotepad(jww.LevelFatal, jww.LevelFatal, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
-
const (
baseURL = "http://foo/bar"
)
func TestShortcodeCrossrefs(t *testing.T) {
+ t.Parallel()
+
for _, relative := range []bool{true, false} {
doTestShortcodeCrossrefs(t, relative)
}
@@ -50,9 +42,12 @@
}
func doTestShortcodeCrossrefs(t *testing.T, relative bool) {
- testCommonResetState()
- viper.Set("baseURL", baseURL)
+ var (
+ cfg, fs = newTestCfg()
+ )
+ cfg.Set("baseURL", baseURL)
+
var refShortcode string
var expectedBase string
@@ -67,13 +62,11 @@
path := filepath.FromSlash("blog/post.md")
in := fmt.Sprintf(`{{< %s "%s" >}}`, refShortcode, path)
- fs := hugofs.NewMem()
-
writeSource(t, fs, "content/"+path, simplePageWithURL+": "+in)
expected := fmt.Sprintf(`%s/simple/url/`, expectedBase)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
require.Len(t, s.RegularPages, 1)
@@ -85,209 +78,236 @@
}
func TestShortcodeHighlight(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
if !helpers.HasPygments() {
t.Skip("Skip test as Pygments is not installed")
}
- viper.Set("pygmentsStyle", "bw")
- viper.Set("pygmentsUseClasses", false)
- for i, this := range []struct {
+ for _, this := range []struct {
in, expected string
}{
- {`
-{{< highlight java >}}
+ {`{{< highlight java >}}
void do();
{{< /highlight >}}`,
- "(?s)^\n<div class=\"highlight\" style=\"background: #ffffff\"><pre style=\"line-height: 125%\">.*?void</span> do().*?</pre></div>\n$",
+ "(?s)<div class=\"highlight\" style=\"background: #ffffff\"><pre style=\"line-height: 125%\">.*?void</span> do().*?</pre></div>\n",
},
- {`
-{{< highlight java "style=friendly" >}}
+ {`{{< highlight java "style=friendly" >}}
void do();
{{< /highlight >}}`,
- "(?s)^\n<div class=\"highlight\" style=\"background: #f0f0f0\"><pre style=\"line-height: 125%\">.*?void</span>.*?do</span>.*?().*?</pre></div>\n$",
+ "(?s)<div class=\"highlight\" style=\"background: #f0f0f0\"><pre style=\"line-height: 125%\">.*?void</span>.*?do</span>.*?().*?</pre></div>\n",
},
} {
- p, _ := pageFromString(simplePage, "simple.md")
- output, err := HandleShortcodes(this.in, p)
- if err != nil {
- t.Fatalf("[%d] Handle shortcode error", i)
- }
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- matched, err := regexp.MatchString(this.expected, output)
+ cfg.Set("pygmentsStyle", "bw")
+ cfg.Set("pygmentsUseClasses", false)
- if err != nil {
- t.Fatalf("[%d] Regexp error", i)
- }
+ writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`---
+title: Shorty
+---
+%s`, this.in))
+ writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`)
- if !matched {
- t.Errorf("[%d] unexpected rendering, got %s\n", i, output)
- }
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
+
+ th.assertFileContentRegexp(t, fs, filepath.Join("public", "simple", "index.html"), false, this.expected)
+
}
}
func TestShortcodeFigure(t *testing.T) {
- for i, this := range []struct {
+ t.Parallel()
+
+ for _, this := range []struct {
in, expected string
}{
{
`{{< figure src="/img/hugo-logo.png" >}}`,
- "(?s)^\n<figure >.*?<img src=\"/img/hugo-logo.png\" />.*?</figure>\n$",
+ "(?s)\n<figure >.*?<img src=\"/img/hugo-logo.png\" />.*?</figure>\n",
},
{
// set alt
`{{< figure src="/img/hugo-logo.png" alt="Hugo logo" >}}`,
- "(?s)^\n<figure >.*?<img src=\"/img/hugo-logo.png\" alt=\"Hugo logo\" />.*?</figure>\n$",
+ "(?s)\n<figure >.*?<img src=\"/img/hugo-logo.png\" alt=\"Hugo logo\" />.*?</figure>\n",
},
// set title
{
`{{< figure src="/img/hugo-logo.png" title="Hugo logo" >}}`,
- "(?s)^\n<figure >.*?<img src=\"/img/hugo-logo.png\" />.*?<figcaption>.*?<h4>Hugo logo</h4>.*?</figcaption>.*?</figure>\n$",
+ "(?s)\n<figure >.*?<img src=\"/img/hugo-logo.png\" />.*?<figcaption>.*?<h4>Hugo logo</h4>.*?</figcaption>.*?</figure>\n",
},
// set attr and attrlink
{
`{{< figure src="/img/hugo-logo.png" attr="Hugo logo" attrlink="/img/hugo-logo.png" >}}`,
- "(?s)^\n<figure >.*?<img src=\"/img/hugo-logo.png\" />.*?<figcaption>.*?<p>.*?<a href=\"/img/hugo-logo.png\">.*?Hugo logo.*?</a>.*?</p>.*?</figcaption>.*?</figure>\n$",
+ "(?s)\n<figure >.*?<img src=\"/img/hugo-logo.png\" />.*?<figcaption>.*?<p>.*?<a href=\"/img/hugo-logo.png\">.*?Hugo logo.*?</a>.*?</p>.*?</figcaption>.*?</figure>\n",
},
} {
- p, _ := pageFromString(simplePage, "simple.md")
- output, err := HandleShortcodes(this.in, p)
- matched, err := regexp.MatchString(this.expected, output)
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- if err != nil {
- t.Fatalf("[%d] Regexp error", i)
- }
+ writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`---
+title: Shorty
+---
+%s`, this.in))
+ writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`)
- if !matched {
- t.Errorf("[%d] unexpected rendering, got %s\n", i, output)
- }
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
+
+ th.assertFileContentRegexp(t, fs, filepath.Join("public", "simple", "index.html"), false, this.expected)
+
}
}
func TestShortcodeSpeakerdeck(t *testing.T) {
- for i, this := range []struct {
+ t.Parallel()
+
+ for _, this := range []struct {
in, expected string
}{
{
`{{< speakerdeck 4e8126e72d853c0060001f97 >}}`,
- "(?s)^<script async class='speakerdeck-embed' data-id='4e8126e72d853c0060001f97'.*?>.*?</script>$",
+ "(?s)<script async class='speakerdeck-embed' data-id='4e8126e72d853c0060001f97'.*?>.*?</script>",
},
} {
- p, _ := pageFromString(simplePage, "simple.md")
- output, err := HandleShortcodes(this.in, p)
- matched, err := regexp.MatchString(this.expected, output)
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- if err != nil {
- t.Fatalf("[%d] Regexp error", i)
- }
+ writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`---
+title: Shorty
+---
+%s`, this.in))
+ writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`)
- if !matched {
- t.Errorf("[%d] unexpected rendering, got %s\n", i, output)
- }
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
+
+ th.assertFileContentRegexp(t, fs, filepath.Join("public", "simple", "index.html"), false, this.expected)
}
}
func TestShortcodeYoutube(t *testing.T) {
- for i, this := range []struct {
+ t.Parallel()
+
+ for _, this := range []struct {
in, expected string
}{
{
`{{< youtube w7Ft2ymGmfc >}}`,
- "(?s)^\n<div style=\".*?\">.*?<iframe src=\"//www.youtube.com/embed/w7Ft2ymGmfc\" style=\".*?\" allowfullscreen frameborder=\"0\">.*?</iframe>.*?</div>\n$",
+ "(?s)\n<div style=\".*?\">.*?<iframe src=\"//www.youtube.com/embed/w7Ft2ymGmfc\" style=\".*?\" allowfullscreen frameborder=\"0\">.*?</iframe>.*?</div>\n",
},
// set class
{
`{{< youtube w7Ft2ymGmfc video>}}`,
- "(?s)^\n<div class=\"video\">.*?<iframe src=\"//www.youtube.com/embed/w7Ft2ymGmfc\" allowfullscreen frameborder=\"0\">.*?</iframe>.*?</div>\n$",
+ "(?s)\n<div class=\"video\">.*?<iframe src=\"//www.youtube.com/embed/w7Ft2ymGmfc\" allowfullscreen frameborder=\"0\">.*?</iframe>.*?</div>\n",
},
// set class and autoplay (using named params)
{
`{{< youtube id="w7Ft2ymGmfc" class="video" autoplay="true" >}}`,
- "(?s)^\n<div class=\"video\">.*?<iframe src=\"//www.youtube.com/embed/w7Ft2ymGmfc\\?autoplay=1\".*?allowfullscreen frameborder=\"0\">.*?</iframe>.*?</div>$",
+ "(?s)\n<div class=\"video\">.*?<iframe src=\"//www.youtube.com/embed/w7Ft2ymGmfc\\?autoplay=1\".*?allowfullscreen frameborder=\"0\">.*?</iframe>.*?</div>",
},
} {
- p, _ := pageFromString(simplePage, "simple.md")
- output, err := HandleShortcodes(this.in, p)
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- matched, err := regexp.MatchString(this.expected, output)
+ writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`---
+title: Shorty
+---
+%s`, this.in))
+ writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`)
- if err != nil {
- t.Fatalf("[%d] Regexp error", i)
- }
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
- if !matched {
- t.Errorf("[%d] unexpected rendering, got %s\n", i, output)
- }
+ th.assertFileContentRegexp(t, fs, filepath.Join("public", "simple", "index.html"), false, this.expected)
}
+
}
func TestShortcodeVimeo(t *testing.T) {
- for i, this := range []struct {
+ t.Parallel()
+
+ for _, this := range []struct {
in, expected string
}{
{
`{{< vimeo 146022717 >}}`,
- "(?s)^\n<div style=\".*?\">.*?<iframe src=\"//player.vimeo.com/video/146022717\" style=\".*?\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>\n$",
+ "(?s)\n<div style=\".*?\">.*?<iframe src=\"//player.vimeo.com/video/146022717\" style=\".*?\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>\n",
},
// set class
{
`{{< vimeo 146022717 video >}}`,
- "(?s)^\n<div class=\"video\">.*?<iframe src=\"//player.vimeo.com/video/146022717\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>\n$",
+ "(?s)\n<div class=\"video\">.*?<iframe src=\"//player.vimeo.com/video/146022717\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>\n",
},
// set class (using named params)
{
`{{< vimeo id="146022717" class="video" >}}`,
- "(?s)^<div class=\"video\">.*?<iframe src=\"//player.vimeo.com/video/146022717\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>$",
+ "(?s)^<div class=\"video\">.*?<iframe src=\"//player.vimeo.com/video/146022717\" webkitallowfullscreen mozallowfullscreen allowfullscreen>.*?</iframe>.*?</div>",
},
} {
- p, _ := pageFromString(simplePage, "simple.md")
- output, err := HandleShortcodes(this.in, p)
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- matched, err := regexp.MatchString(this.expected, output)
+ writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`---
+title: Shorty
+---
+%s`, this.in))
+ writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`)
- if err != nil {
- t.Fatalf("[%d] Regexp error", i)
- }
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
- if !matched {
- t.Errorf("[%d] unexpected rendering, got %s\n", i, output)
- }
+ th.assertFileContentRegexp(t, fs, filepath.Join("public", "simple", "index.html"), false, this.expected)
+
}
}
func TestShortcodeGist(t *testing.T) {
- for i, this := range []struct {
+ t.Parallel()
+
+ for _, this := range []struct {
in, expected string
}{
{
`{{< gist spf13 7896402 >}}`,
- "(?s)^<script src=\"//gist.github.com/spf13/7896402.js\"></script>$",
+ "(?s)^<script src=\"//gist.github.com/spf13/7896402.js\"></script>",
},
{
`{{< gist spf13 7896402 "img.html" >}}`,
- "(?s)^<script src=\"//gist.github.com/spf13/7896402.js\\?file=img.html\"></script>$",
+ "(?s)^<script src=\"//gist.github.com/spf13/7896402.js\\?file=img.html\"></script>",
},
} {
- p, _ := pageFromString(simplePage, "simple.md")
- output, err := HandleShortcodes(this.in, p)
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- matched, err := regexp.MatchString(this.expected, output)
+ writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`---
+title: Shorty
+---
+%s`, this.in))
+ writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`)
- if err != nil {
- t.Fatalf("[%d] Regexp error", i)
- }
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
- if !matched {
- t.Errorf("[%d] unexpected rendering, got %s\n", i, output)
- }
+ th.assertFileContentRegexp(t, fs, filepath.Join("public", "simple", "index.html"), false, this.expected)
+
}
}
func TestShortcodeTweet(t *testing.T) {
+ t.Parallel()
+
for i, this := range []struct {
in, resp, expected string
}{
@@ -294,7 +314,7 @@
{
`{{< tweet 666616452582129664 >}}`,
`{"url":"https:\/\/twitter.com\/spf13\/status\/666616452582129664","author_name":"Steve Francia","author_url":"https:\/\/twitter.com\/spf13","html":"\u003Cblockquote class=\"twitter-tweet\"\u003E\u003Cp lang=\"en\" dir=\"ltr\"\u003EHugo 0.15 will have 30%+ faster render times thanks to this commit \u003Ca href=\"https:\/\/t.co\/FfzhM8bNhT\"\u003Ehttps:\/\/t.co\/FfzhM8bNhT\u003C\/a\u003E \u003Ca href=\"https:\/\/twitter.com\/hashtag\/gohugo?src=hash\"\u003E#gohugo\u003C\/a\u003E \u003Ca href=\"https:\/\/twitter.com\/hashtag\/golang?src=hash\"\u003E#golang\u003C\/a\u003E \u003Ca href=\"https:\/\/t.co\/ITbMNU2BUf\"\u003Ehttps:\/\/t.co\/ITbMNU2BUf\u003C\/a\u003E\u003C\/p\u003E— Steve Francia (@spf13) \u003Ca href=\"https:\/\/twitter.com\/spf13\/status\/666616452582129664\"\u003ENovember 17, 2015\u003C\/a\u003E\u003C\/blockquote\u003E\n\u003Cscript async src=\"\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"\u003E\u003C\/script\u003E","width":550,"height":null,"type":"rich","cache_age":"3153600000","provider_name":"Twitter","provider_url":"https:\/\/twitter.com","version":"1.0"}`,
- `(?s)^<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Hugo 0.15 will have 30%. faster render times thanks to this commit <a href="https://t.co/FfzhM8bNhT">https://t.co/FfzhM8bNhT</a> <a href="https://twitter.com/hashtag/gohugo.src=hash">#gohugo</a> <a href="https://twitter.com/hashtag/golang.src=hash">#golang</a> <a href="https://t.co/ITbMNU2BUf">https://t.co/ITbMNU2BUf</a></p>— Steve Francia .@spf13. <a href="https://twitter.com/spf13/status/666616452582129664">November 17, 2015</a></blockquote>.*?<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>$`,
+ `(?s)^<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Hugo 0.15 will have 30%. faster render times thanks to this commit <a href="https://t.co/FfzhM8bNhT">https://t.co/FfzhM8bNhT</a> <a href="https://twitter.com/hashtag/gohugo.src=hash">#gohugo</a> <a href="https://twitter.com/hashtag/golang.src=hash">#golang</a> <a href="https://t.co/ITbMNU2BUf">https://t.co/ITbMNU2BUf</a></p>— Steve Francia .@spf13. <a href="https://twitter.com/spf13/status/666616452582129664">November 17, 2015</a></blockquote>.*?<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>`,
},
} {
// overload getJSON to return mock API response from Twitter
@@ -310,28 +330,32 @@
},
}
- p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error {
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
+
+ withTemplate := func(templ tplapi.Template) error {
templ.Funcs(tweetFuncMap)
return nil
- })
+ }
- cacheFileID := viper.GetString("cacheDir") + url.QueryEscape("https://api.twitter.com/1/statuses/oembed.json?id=666616452582129664")
- defer os.Remove(cacheFileID)
- output, err := HandleShortcodes(this.in, p)
+ writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`---
+title: Shorty
+---
+%s`, this.in))
+ writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`)
- matched, err := regexp.MatchString(this.expected, output)
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{})
- if err != nil {
- t.Fatalf("[%d] Regexp error", i)
- }
+ th.assertFileContentRegexp(t, fs, filepath.Join("public", "simple", "index.html"), false, this.expected)
- if !matched {
- t.Errorf("[%d] unexpected rendering, got %s\n", i, output)
- }
}
}
func TestShortcodeInstagram(t *testing.T) {
+ t.Parallel()
+
for i, this := range []struct {
in, hidecaption, resp, expected string
}{
@@ -339,15 +363,13 @@
`{{< instagram BMokmydjG-M >}}`,
`0`,
`{"provider_url": "https://www.instagram.com", "media_id": "1380514280986406796_25025320", "author_name": "instagram", "height": null, "thumbnail_url": "https://scontent-amt2-1.cdninstagram.com/t51.2885-15/s640x640/sh0.08/e35/15048135_1880160212214218_7827880881132929024_n.jpg?ig_cache_key=MTM4MDUxNDI4MDk4NjQwNjc5Ng%3D%3D.2", "thumbnail_width": 640, "thumbnail_height": 640, "provider_name": "Instagram", "title": "Today, we\u2019re introducing a few new tools to help you make your story even more fun: Boomerang and mentions. We\u2019re also starting to test links inside some stories.\nBoomerang lets you turn everyday moments into something fun and unexpected. Now you can easily take a Boomerang right inside Instagram. Swipe right from your feed to open the stories camera. A new format picker under the record button lets you select \u201cBoomerang\u201d mode.\nYou can also now share who you\u2019re with or who you\u2019re thinking of by mentioning them in your story. When you add text to your story, type \u201c@\u201d followed by a username and select the person you\u2019d like to mention. Their username will appear underlined in your story. And when someone taps the mention, they'll see a pop-up that takes them to that profile.\nYou may begin to spot \u201cSee More\u201d links at the bottom of some stories. This is a test that lets verified accounts add links so it\u2019s easy to learn more. From your favorite chefs\u2019 recipes to articles from top journalists or concert dates from the musicians you love, tap \u201cSee More\u201d or swipe up to view the link right inside the app.\nTo learn more about today\u2019s updates, check out help.instagram.com.\nThese updates for Instagram Stories are available as part of Instagram version 9.7 available for iOS in the Apple App Store, for Android in Google Play and for Windows 10 in the Windows Store.", "html": "\u003cblockquote class=\"instagram-media\" data-instgrm-captioned data-instgrm-version=\"7\" style=\" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:658px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);\"\u003e\u003cdiv style=\"padding:8px;\"\u003e \u003cdiv style=\" background:#F8F8F8; line-height:0; margin-top:40px; padding:50.0% 0; text-align:center; width:100%;\"\u003e \u003cdiv style=\" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;\"\u003e\u003c/div\u003e\u003c/div\u003e \u003cp style=\" margin:8px 0 0 0; padding:0 4px;\"\u003e \u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#000; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none; word-wrap:break-word;\" target=\"_blank\"\u003eToday, we\u2019re introducing a few new tools to help you make your story even more fun: Boomerang and mentions. We\u2019re also starting to test links inside some stories. Boomerang lets you turn everyday moments into something fun and unexpected. Now you can easily take a Boomerang right inside Instagram. Swipe right from your feed to open the stories camera. A new format picker under the record button lets you select \u201cBoomerang\u201d mode. You can also now share who you\u2019re with or who you\u2019re thinking of by mentioning them in your story. When you add text to your story, type \u201c@\u201d followed by a username and select the person you\u2019d like to mention. Their username will appear underlined in your story. And when someone ta
\ No newline at end of file
-ps the mention, they\u0026#39;ll see a pop-up that takes them to that profile. You may begin to spot \u201cSee More\u201d links at the bottom of some stories. This is a test that lets verified accounts add links so it\u2019s easy to learn more. From your favorite chefs\u2019 recipes to articles from top journalists or concert dates from the musicians you love, tap \u201cSee More\u201d or swipe up to view the link right inside the app. To learn more about today\u2019s updates, check out help.instagram.com. These updates for Instagram Stories are available as part of Instagram version 9.7 available for iOS in the Apple App Store, for Android in Google Play and for Windows 10 in the Windows Store.\u003c/a\u003e\u003c/p\u003e \u003cp style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003eA photo posted by Instagram (@instagram) on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
- `<blockquote class="instagram-media" data-instgrm-captioned data-instgrm-version="7" style=" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:658px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);"><div style="padding:8px;"> <div style=" background:#F8F8F8; line-height:0; margin-top:40px; padding:50.0% 0; text-align:center; width:100%;"> <div style=" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div> <p style=" margin:8px 0 0 0; padding:0 4px;"> <a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#000; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none; word-wrap:break-word;" target="_blank">Today, we’re introducing a few new tools to help you make your story even more fun: Boomerang and mentions. We’re also starting to test links inside some stories. Boomerang lets you turn everyday moments into something fun and unexpected. Now you can easily take a Boomerang right inside Instagram. Swipe right from your feed to open the stories camera. A new format picker under the record button lets you select “Boomerang” mode. You can also now share who you’re with or who you’re thinking of by mentioning them in your story. When you add text to your story, type “@” followed by a username and select the person you’d like to mention. Their username will appear underlined in your story. And when someone taps the mention, they'll see a pop-up that takes them to that profile. You may begin to spot “See More” links at the bottom of some stories. This is a test that lets verified accounts add links so it’s easy to learn more. From your favorite chefs’ recipes to articles from top journalists or concert dates from the musicians you love, tap “See More” or swipe up to view the link right inside the app. To learn more about today’s updates, check out help.instagram.com. These updates for Instagram Stories are available as part of Instagram version 9.7 available for iOS in the Apple App Store, for Android in Google Play and for Windows 10 in the Windows Store.</a></p> <p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;">A photo posted by Instagram (@instagram) on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
+ps the mention, they\u0026#39;ll see a pop-up that takes them to that profile. You may begin to spot \u201cSee More\u201d links at the bottom of some stories. This is a test that lets verified accounts add links so it\u2019s easy to learn more. From your favorite chefs\u2019 recipes to articles from top journalists or concert dates from the musicians you love, tap \u201cSee More\u201d or swipe up to view the link right inside the app. To learn more about today\u2019s updates, check out help.instagram.com. These updates for Instagram Stories are available as part of Instagram version 9.7 available for iOS in the Apple App Store, for Android in Google Play and for Windows 10 in the Windows Store.\u003c/a\u003e\u003c/p\u003e \u003cp style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003eA photo posted by Instagram (@instagram) on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
’re with or who you’re thinking of by mentioning them in your story. When you add text to your story, type “@” followed by a username and select the person you’d like to mention. Their username will appear underlined in your story. And when someone taps the mention, they'll see a pop-up that takes them to that profile. You may begin to spot “See More” links at the bottom of some stories. This is a test that lets verified accounts add links so it’s easy to learn more. From your favorite chefs’ recipes to articles from top journalists or concert dates from the musicians you love, tap “See More” or swipe up to view the link right inside the app. To learn more about today’s updates, check out help.instagram.com. These updates for Instagram Stories are available as part of Instagram version 9.7 available for iOS in the Apple App Store, for Android in Google Play and for Windows 10 in the Windows Store.</a></p> <p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;">A photo posted by Instagram (@instagram) on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
},
{
`{{< instagram BMokmydjG-M hidecaption >}}`,
-MzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;\"\u003e\u003c/div\u003e\u003c/div\u003e\u003cp style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
- `<blockquote class="instagram-media" data-instgrm-version="7" style=" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:658px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);"><div style="padding:8px;"> <div style=" background:#F8F8F8; line-height:0; margin-top:40px; padding:50.0% 0; text-align:center; width:100%;"> <div style=" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
+MzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;\"\u003e\u003c/div\u003e\u003c/div\u003e\u003cp style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
8px;"> <div style=" background:#F8F8F8; line-height:0; margin-top:40px; padding:50.0% 0; text-align:center; width:100%;"> <div style=" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
},
@@ -363,21 +385,25 @@
pQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
},
-Fhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
+4px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
+ `(?s)<blockquote class="instagram-media" data-instgrm-version="7" style=" background:#FFF; border:0; .*<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
+ },
+ } {
+ // overload getJSON to return mock API response from Instagram
+ instagramFuncMap := template.FuncMap{
yuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
-/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
+ href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
yy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
-y8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
-<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
- },
+=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
+ `(?s)<blockquote class="instagram-media" data-instgrm-version="7" style=" background:#FFF; border:0; .*<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
+ },
+ } {
+ // overload getJSON to return mock API response from Instagram
ily:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
-ly:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
-<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
- },
+\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
argin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
-rgin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
-<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
- },
+y:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
+ `(?s)<blockquote class="instagram-media" data-instgrm-version="7" style=" background:#FFF; border:0; .*<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
--- a/hugolib/gitinfo.go
+++ b/hugolib/gitinfo.go
@@ -20,17 +20,16 @@
"github.com/bep/gitmap"
"github.com/spf13/hugo/helpers"
- "github.com/spf13/viper"
)
func (h *HugoSites) assembleGitInfo() {
- if !viper.GetBool("enableGitInfo") {
+ if !h.Cfg.GetBool("enableGitInfo") {
return
}
var (
- workingDir = viper.GetString("workingDir")
- contentDir = viper.GetString("contentDir")
+ workingDir = h.Cfg.GetString("workingDir")
+ contentDir = h.Cfg.GetString("contentDir")
)
gitRepo, err := gitmap.Map(workingDir, "")
--- a/hugolib/handler_page.go
+++ b/hugolib/handler_page.go
@@ -19,7 +19,6 @@
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/source"
- "github.com/spf13/viper"
)
func init() {
@@ -43,6 +42,13 @@
return HandledResult{file: f, err: err}
}
+ // In a multilanguage setup, we use the first site to
+ // do the initial processing.
+ // That site may be different than where the page will end up,
+ // so we do the assignment here.
+ // We should clean up this, but that will have to wait.
+ s.assignSiteByLanguage(page)
+
return HandledResult{file: f, page: page, err: err}
}
@@ -117,7 +123,7 @@
// TODO(bep) these page handlers need to be re-evaluated, as it is hard to
// process a page in isolation. See the new preRender func.
- if viper.GetBool("enableEmoji") {
+ if p.s.Cfg.GetBool("enableEmoji") {
p.workContent = helpers.Emojify(p.workContent)
}
--- a/hugolib/handler_test.go
+++ b/hugolib/handler_test.go
@@ -19,18 +19,18 @@
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/hugofs"
- "github.com/spf13/viper"
)
func TestDefaultHandler(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
- viper.Set("defaultExtension", "html")
- viper.Set("verbose", true)
- viper.Set("uglyURLs", true)
+ var (
+ cfg, fs = newTestCfg()
+ )
- fs := hugofs.NewMem()
+ cfg.Set("defaultExtension", "html")
+ cfg.Set("verbose", true)
+ cfg.Set("uglyURLs", true)
writeSource(t, fs, filepath.FromSlash("content/sect/doc1.html"), "---\nmarkup: markdown\n---\n# title\nsome *content*")
writeSource(t, fs, filepath.FromSlash("content/sect/doc2.html"), "<!doctype html><html><body>more content</body></html>")
@@ -46,7 +46,7 @@
writeSource(t, fs, filepath.FromSlash("head"), "<head><script src=\"script.js\"></script></head>")
writeSource(t, fs, filepath.FromSlash("head_abs"), "<head><script src=\"/script.js\"></script></head")
- buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
tests := []struct {
doc string
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -22,9 +22,7 @@
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
- "github.com/spf13/viper"
-
- "github.com/spf13/hugo/source"
+ "github.com/spf13/hugo/i18n"
"github.com/spf13/hugo/tpl"
"github.com/spf13/hugo/tplapi"
)
@@ -48,7 +46,7 @@
return nil, errors.New("Cannot provide Language in Cfg when sites are provided")
}
- langConfig, err := newMultiLingualFromSites(sites...)
+ langConfig, err := newMultiLingualFromSites(cfg.Cfg, sites...)
if err != nil {
return nil, err
@@ -62,6 +60,9 @@
s.owner = h
}
+ // TODO(bep)
+ cfg.Cfg.Set("multilingual", sites[0].multilingualEnabled())
+
applyDepsIfNeeded(cfg, sites...)
h.Deps = sites[0].Deps
@@ -70,11 +71,14 @@
}
func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
-
if cfg.TemplateProvider == nil {
cfg.TemplateProvider = tpl.DefaultTemplateProvider
}
+ if cfg.TranslationProvider == nil {
+ cfg.TranslationProvider = i18n.NewTranslationProvider()
+ }
+
var (
d *deps.Deps
err error
@@ -89,7 +93,9 @@
cfg.Language = s.Language
cfg.WithTemplate = s.withSiteTemplates(cfg.WithTemplate)
d = deps.New(cfg)
- if err := d.LoadTemplates(); err != nil {
+ s.Deps = d
+
+ if err := d.LoadResources(); err != nil {
return err
}
@@ -98,16 +104,16 @@
if err != nil {
return err
}
+ s.Deps = d
}
- s.Deps = d
+
}
return nil
}
-// NewHugoSitesFromConfiguration creates HugoSites from the global Viper config.
-// TODO(bep) globals rename this when all the globals are gone.
-func NewHugoSitesFromConfiguration(cfg deps.DepsCfg) (*HugoSites, error) {
+// NewHugoSites creates HugoSites from the given config.
+func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
sites, err := createSitesFromConfig(cfg)
if err != nil {
return nil, err
@@ -141,10 +147,10 @@
sites []*Site
)
- multilingual := viper.GetStringMap("languages")
+ multilingual := cfg.Cfg.GetStringMap("languages")
if len(multilingual) == 0 {
- l := helpers.NewDefaultLanguage()
+ l := helpers.NewDefaultLanguage(cfg.Cfg)
cfg.Language = l
s, err := newSite(cfg)
if err != nil {
@@ -156,7 +162,7 @@
if len(multilingual) > 0 {
var err error
- languages, err := toSortedLanguages(multilingual)
+ languages, err := toSortedLanguages(cfg.Cfg, multilingual)
if err != nil {
return nil, fmt.Errorf("Failed to parse multilingual config: %s", err)
@@ -190,7 +196,7 @@
func (h *HugoSites) createSitesFromConfig() error {
- depsCfg := deps.DepsCfg{Fs: h.Fs}
+ depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: h.Cfg}
sites, err := createSitesFromConfig(depsCfg)
if err != nil {
@@ -197,7 +203,7 @@
return err
}
- langConfig, err := newMultiLingualFromSites(sites...)
+ langConfig, err := newMultiLingualFromSites(depsCfg.Cfg, sites...)
if err != nil {
return err
@@ -251,12 +257,12 @@
return nil
}
- if viper.GetBool("disableSitemap") {
+ if h.Cfg.GetBool("disableSitemap") {
return nil
}
// TODO(bep) DRY
- sitemapDefault := parseSitemap(viper.GetStringMap("sitemap"))
+ sitemapDefault := parseSitemap(h.Cfg.GetStringMap("sitemap"))
s := h.Sites[0]
@@ -398,6 +404,24 @@
return nil
}
+func (s *Site) assignSiteByLanguage(p *Page) {
+
+ pageLang := p.Lang()
+
+ if pageLang == "" {
+ panic("Page language missing: " + p.Title)
+ }
+
+ for _, site := range s.owner.Sites {
+ if strings.HasPrefix(site.Language.Lang, pageLang) {
+ p.s = site
+ p.Site = &site.Info
+ return
+ }
+ }
+
+}
+
func (h *HugoSites) setupTranslations() {
master := h.Sites[0]
@@ -410,11 +434,11 @@
shouldBuild := p.shouldBuild()
for i, site := range h.Sites {
- if strings.HasPrefix(site.Language.Lang, p.Lang()) {
+ // The site is assigned by language when read.
+ if site == p.s {
site.updateBuildStats(p)
if shouldBuild {
site.Pages = append(site.Pages, p)
- p.Site = &site.Info
}
}
@@ -576,33 +600,4 @@
func (h *HugoSites) findAllPagesByKindNotIn(kind string) Pages {
return h.findPagesByKindNotIn(kind, h.Sites[0].AllPages)
-}
-
-// Convenience func used in tests.
-func newHugoSitesFromSourceAndLanguages(input []source.ByteSource, languages helpers.Languages, cfg deps.DepsCfg) (*HugoSites, error) {
- if len(languages) == 0 {
- panic("Must provide at least one language")
- }
-
- first := &Site{
- Language: languages[0],
- Source: &source.InMemorySource{ByteSource: input},
- }
- if len(languages) == 1 {
- return newHugoSites(cfg, first)
- }
-
- sites := make([]*Site, len(languages))
- sites[0] = first
- for i := 1; i < len(languages); i++ {
- sites[i] = &Site{Language: languages[i]}
- }
-
- return newHugoSites(cfg, sites...)
-
-}
-
-// Convenience func used in tests.
-func newHugoSitesDefaultLanguage(cfg deps.DepsCfg) (*HugoSites, error) {
- return newHugoSitesFromSourceAndLanguages(nil, helpers.Languages{helpers.NewDefaultLanguage()}, cfg)
}
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -121,6 +121,7 @@
// but that seems like a daunting task.
// So for now, if there are more than one site (language),
// we pre-process the first one, then configure all the sites based on that.
+
firstSite := h.Sites[0]
if len(events) > 0 {
@@ -169,9 +170,6 @@
}
for _, s := range h.Sites {
- if err := s.setCurrentLanguageConfig(); err != nil {
- return err
- }
s.preparePagesForRender(config)
}
--- a/hugolib/hugo_sites_build_test.go
+++ b/hugolib/hugo_sites_build_test.go
@@ -14,40 +14,23 @@
"github.com/fortytw2/leaktest"
"github.com/fsnotify/fsnotify"
"github.com/spf13/afero"
+ "github.com/spf13/hugo/config"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
- // jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
- "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testSiteConfig struct {
- DefaultContentLanguage string
- Fs *hugofs.Fs
+ DefaultContentLanguage string
+ DefaultContentLanguageInSubdir bool
+ Fs afero.Fs
}
-func init() {
- testCommonResetState()
-}
-
-func testCommonResetState() {
- viper.Reset()
- // TODO(bep) globals viper viper.SetFs(hugofs.Source())
- viper.Set("currentContentLanguage", helpers.NewLanguage("en"))
- helpers.ResetConfigProvider()
- loadDefaultSettings()
-
- // Default is false, but true is easier to use as default in tests
- viper.Set("defaultContentLanguageInSubdir", true)
-
-}
-
-// TODO(bep) globals this currently fails because of a configuration dependency that will be resolved when we get rid of the global Viper.
-func _TestMultiSitesMainLangInRoot(t *testing.T) {
-
+func TestMultiSitesMainLangInRoot(t *testing.T) {
+ t.Parallel()
for _, b := range []bool{true, false} {
doTestMultiSitesMainLangInRoot(t, b)
}
@@ -54,12 +37,12 @@
}
func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) {
- testCommonResetState()
- viper.Set("defaultContentLanguageInSubdir", defaultInSubDir)
- fs := hugofs.NewMem()
- siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
+ siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: defaultInSubDir}
+
sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
+ th := testHelper{sites.Cfg}
+ fs := sites.Fs
err := sites.Build(BuildCfg{})
@@ -80,7 +63,6 @@
require.Equal(t, "", frSite.Info.LanguagePrefix)
}
- fmt.Println(">>>", enSite.PathSpec)
require.Equal(t, "/blog/en/foo", enSite.PathSpec.RelURL("foo", true))
doc1en := enSite.RegularPages[0]
@@ -94,71 +76,75 @@
frPerm := doc1fr.Permalink()
frRelPerm := doc1fr.RelPermalink()
// Main language in root
- require.Equal(t, replaceDefaultContentLanguageValue("http://example.com/blog/fr/sect/doc1/", defaultInSubDir), frPerm)
- require.Equal(t, replaceDefaultContentLanguageValue("/blog/fr/sect/doc1/", defaultInSubDir), frRelPerm)
+ require.Equal(t, th.replaceDefaultContentLanguageValue("http://example.com/blog/fr/sect/doc1/", defaultInSubDir), frPerm)
+ require.Equal(t, th.replaceDefaultContentLanguageValue("/blog/fr/sect/doc1/", defaultInSubDir), frRelPerm)
- assertFileContent(t, fs, "public/fr/sect/doc1/index.html", defaultInSubDir, "Single", "Bonjour")
- assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", defaultInSubDir, "Single", "Hello")
+ th.assertFileContent(t, fs, "public/fr/sect/doc1/index.html", defaultInSubDir, "Single", "Bonjour")
+ th.assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", defaultInSubDir, "Single", "Hello")
// Check home
if defaultInSubDir {
// should have a redirect on top level.
- assertFileContent(t, fs, "public/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`)
+ th.assertFileContent(t, fs, "public/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`)
} else {
// should have redirect back to root
- assertFileContent(t, fs, "public/fr/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`)
+ th.assertFileContent(t, fs, "public/fr/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`)
}
- assertFileContent(t, fs, "public/fr/index.html", defaultInSubDir, "Home", "Bonjour")
- assertFileContent(t, fs, "public/en/index.html", defaultInSubDir, "Home", "Hello")
+ th.assertFileContent(t, fs, "public/fr/index.html", defaultInSubDir, "Home", "Bonjour")
+ th.assertFileContent(t, fs, "public/en/index.html", defaultInSubDir, "Home", "Hello")
// Check list pages
- assertFileContent(t, fs, "public/fr/sect/index.html", defaultInSubDir, "List", "Bonjour")
- assertFileContent(t, fs, "public/en/sect/index.html", defaultInSubDir, "List", "Hello")
- assertFileContent(t, fs, "public/fr/plaques/frtag1/index.html", defaultInSubDir, "List", "Bonjour")
- assertFileContent(t, fs, "public/en/tags/tag1/index.html", defaultInSubDir, "List", "Hello")
+ th.assertFileContent(t, fs, "public/fr/sect/index.html", defaultInSubDir, "List", "Bonjour")
+ th.assertFileContent(t, fs, "public/en/sect/index.html", defaultInSubDir, "List", "Hello")
+ th.assertFileContent(t, fs, "public/fr/plaques/frtag1/index.html", defaultInSubDir, "List", "Bonjour")
+ th.assertFileContent(t, fs, "public/en/tags/tag1/index.html", defaultInSubDir, "List", "Hello")
// Check sitemaps
// Sitemaps behaves different: In a multilanguage setup there will always be a index file and
// one sitemap in each lang folder.
- assertFileContent(t, fs, "public/sitemap.xml", true,
+ th.assertFileContent(t, fs, "public/sitemap.xml", true,
"<loc>http://example.com/blog/en/sitemap.xml</loc>",
"<loc>http://example.com/blog/fr/sitemap.xml</loc>")
if defaultInSubDir {
- assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/fr/</loc>")
+ th.assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/fr/</loc>")
} else {
- assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/</loc>")
+ th.assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/</loc>")
}
- assertFileContent(t, fs, "public/en/sitemap.xml", true, "<loc>http://example.com/blog/en/</loc>")
+ th.assertFileContent(t, fs, "public/en/sitemap.xml", true, "<loc>http://example.com/blog/en/</loc>")
// Check rss
- assertFileContent(t, fs, "public/fr/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/index.xml"`)
- assertFileContent(t, fs, "public/en/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/index.xml"`)
- assertFileContent(t, fs, "public/fr/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/sect/index.xml"`)
- assertFileContent(t, fs, "public/en/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/sect/index.xml"`)
- assertFileContent(t, fs, "public/fr/plaques/frtag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/plaques/frtag1/index.xml"`)
- assertFileContent(t, fs, "public/en/tags/tag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`)
+ th.assertFileContent(t, fs, "public/fr/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/index.xml"`)
+ th.assertFileContent(t, fs, "public/en/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/index.xml"`)
+ th.assertFileContent(t, fs, "public/fr/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/sect/index.xml"`)
+ th.assertFileContent(t, fs, "public/en/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/sect/index.xml"`)
+ th.assertFileContent(t, fs, "public/fr/plaques/frtag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/plaques/frtag1/index.xml"`)
+ th.assertFileContent(t, fs, "public/en/tags/tag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`)
// Check paginators
- assertFileContent(t, fs, "public/fr/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/"`)
- assertFileContent(t, fs, "public/en/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/"`)
- assertFileContent(t, fs, "public/fr/page/2/index.html", defaultInSubDir, "Home Page 2", "Bonjour", "http://example.com/blog/fr/")
- assertFileContent(t, fs, "public/en/page/2/index.html", defaultInSubDir, "Home Page 2", "Hello", "http://example.com/blog/en/")
- assertFileContent(t, fs, "public/fr/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/sect/"`)
- assertFileContent(t, fs, "public/en/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/sect/"`)
- assertFileContent(t, fs, "public/fr/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/sect/")
- assertFileContent(t, fs, "public/en/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/sect/")
- assertFileContent(t, fs, "public/fr/plaques/frtag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/plaques/frtag1/"`)
- assertFileContent(t, fs, "public/en/tags/tag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`)
- assertFileContent(t, fs, "public/fr/plaques/frtag1/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/plaques/frtag1/")
- assertFileContent(t, fs, "public/en/tags/tag1/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/")
+ th.assertFileContent(t, fs, "public/fr/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/"`)
+ th.assertFileContent(t, fs, "public/en/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/"`)
+ th.assertFileContent(t, fs, "public/fr/page/2/index.html", defaultInSubDir, "Home Page 2", "Bonjour", "http://example.com/blog/fr/")
+ th.assertFileContent(t, fs, "public/en/page/2/index.html", defaultInSubDir, "Home Page 2", "Hello", "http://example.com/blog/en/")
+ th.assertFileContent(t, fs, "public/fr/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/sect/"`)
+ th.assertFileContent(t, fs, "public/en/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/sect/"`)
+ th.assertFileContent(t, fs, "public/fr/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/sect/")
+ th.assertFileContent(t, fs, "public/en/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/sect/")
+ th.assertFileContent(t, fs, "public/fr/plaques/frtag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/plaques/frtag1/"`)
+ th.assertFileContent(t, fs, "public/en/tags/tag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`)
+ th.assertFileContent(t, fs, "public/fr/plaques/frtag1/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/plaques/frtag1/")
+ th.assertFileContent(t, fs, "public/en/tags/tag1/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/")
// nn (Nynorsk) and nb (Bokmål) have custom pagePath: side ("page" in Norwegian)
- assertFileContent(t, fs, "public/nn/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nn/"`)
- assertFileContent(t, fs, "public/nb/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nb/"`)
+ th.assertFileContent(t, fs, "public/nn/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nn/"`)
+ th.assertFileContent(t, fs, "public/nb/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nb/"`)
}
-func replaceDefaultContentLanguageValue(value string, defaultInSubDir bool) string {
- replace := viper.GetString("defaultContentLanguage") + "/"
+type testHelper struct {
+ Cfg config.Provider
+}
+
+func (th testHelper) replaceDefaultContentLanguageValue(value string, defaultInSubDir bool) string {
+ replace := th.Cfg.GetString("defaultContentLanguage") + "/"
if !defaultInSubDir {
value = strings.Replace(value, replace, "", 1)
@@ -167,36 +153,33 @@
}
-func assertFileContent(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
- filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
+func (th testHelper) assertFileContent(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
+ filename = th.replaceDefaultContentLanguageValue(filename, defaultInSubDir)
content := readDestination(t, fs, filename)
for _, match := range matches {
- match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
+ match = th.replaceDefaultContentLanguageValue(match, defaultInSubDir)
require.True(t, strings.Contains(content, match), fmt.Sprintf("File no match for\n%q in\n%q:\n%s", strings.Replace(match, "%", "%%", -1), filename, strings.Replace(content, "%", "%%", -1)))
}
}
-func assertFileContentRegexp(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
- filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
+func (th testHelper) assertFileContentRegexp(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
+ filename = th.replaceDefaultContentLanguageValue(filename, defaultInSubDir)
content := readDestination(t, fs, filename)
for _, match := range matches {
- match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
+ match = th.replaceDefaultContentLanguageValue(match, defaultInSubDir)
r := regexp.MustCompile(match)
- require.True(t, r.MatchString(content), fmt.Sprintf("File no match for\n%q in\n%q:\n%s", match, filename, content))
+ require.True(t, r.MatchString(content), fmt.Sprintf("File no match for\n%q in\n%q:\n%s", strings.Replace(match, "%", "%%", -1), filename, strings.Replace(content, "%", "%%", -1)))
}
}
func TestMultiSitesWithTwoLanguages(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ mm := afero.NewMemMapFs()
- viper.Set("defaultContentLanguage", "nn")
+ writeToFs(t, mm, "config.toml", `
- fs := hugofs.NewMem()
+defaultContentLanguage = "nn"
- depsCfg := deps.DepsCfg{Fs: fs}
- viper.SetFs(depsCfg.Fs.Source)
-
- writeSource(t, depsCfg.Fs, "config.toml", `
[languages]
[languages.nn]
languageName = "Nynorsk"
@@ -210,12 +193,13 @@
`,
)
- if err := LoadGlobalConfig("", "config.toml"); err != nil {
- t.Fatalf("Failed to load config: %s", err)
- }
+ cfg, err := LoadConfig(mm, "", "config.toml")
+ require.NoError(t, err)
- sites, err := NewHugoSitesFromConfiguration(depsCfg)
+ fs := hugofs.NewFrom(mm, cfg)
+ sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
+
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
}
@@ -238,13 +222,15 @@
//
func TestMultiSitesBuild(t *testing.T) {
+ t.Parallel()
+
for _, config := range []struct {
content string
suffix string
}{
{multiSiteTOMLConfigTemplate, "toml"},
- {multiSiteYAMLConfig, "yml"},
- {multiSiteJSONConfig, "json"},
+ {multiSiteYAMLConfigTemplate, "yml"},
+ {multiSiteJSONConfigTemplate, "json"},
} {
doTestMultiSitesBuild(t, config.content, config.suffix)
}
@@ -251,12 +237,13 @@
}
func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
- defer leaktest.Check(t)()
- testCommonResetState()
- fs := hugofs.NewMem()
- siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
+ siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: true}
sites := createMultiTestSitesForConfig(t, siteConfig, configTemplate, configSuffix)
+ require.Len(t, sites.Sites, 4)
+
+ fs := sites.Fs
+ th := testHelper{sites.Cfg}
err := sites.Build(BuildCfg{})
if err != nil {
@@ -263,75 +250,80 @@
t.Fatalf("Failed to build sites: %s", err)
}
+ // Check site config
+ for _, s := range sites.Sites {
+ require.True(t, s.Info.defaultContentLanguageInSubdir, s.Info.Title)
+ }
+
enSite := sites.Sites[0]
enSiteHome := enSite.getPage(KindHome)
require.True(t, enSiteHome.IsTranslated())
- assert.Equal(t, "en", enSite.Language.Lang)
+ require.Equal(t, "en", enSite.Language.Lang)
if len(enSite.RegularPages) != 4 {
t.Fatal("Expected 4 english pages")
}
- assert.Len(t, enSite.Source.Files(), 14, "should have 13 source files")
- assert.Len(t, enSite.AllPages, 28, "should have 28 total pages (including translations and index types)")
+ require.Len(t, enSite.Source.Files(), 14, "should have 13 source files")
+ require.Len(t, enSite.AllPages, 28, "should have 28 total pages (including translations and index types)")
doc1en := enSite.RegularPages[0]
permalink := doc1en.Permalink()
- assert.NoError(t, err, "permalink call failed")
- assert.Equal(t, "http://example.com/blog/en/sect/doc1-slug/", permalink, "invalid doc1.en permalink")
- assert.Len(t, doc1en.Translations(), 1, "doc1-en should have one translation, excluding itself")
+ require.NoError(t, err, "permalink call failed")
+ require.Equal(t, "http://example.com/blog/en/sect/doc1-slug/", permalink, "invalid doc1.en permalink")
+ require.Len(t, doc1en.Translations(), 1, "doc1-en should have one translation, excluding itself")
doc2 := enSite.RegularPages[1]
permalink = doc2.Permalink()
- assert.NoError(t, err, "permalink call failed")
- assert.Equal(t, "http://example.com/blog/en/sect/doc2/", permalink, "invalid doc2 permalink")
+ require.NoError(t, err, "permalink call failed")
+ require.Equal(t, "http://example.com/blog/en/sect/doc2/", permalink, "invalid doc2 permalink")
doc3 := enSite.RegularPages[2]
permalink = doc3.Permalink()
- assert.NoError(t, err, "permalink call failed")
+ require.NoError(t, err, "permalink call failed")
// Note that /superbob is a custom URL set in frontmatter.
// We respect that URL literally (it can be /search.json)
// and do no not do any language code prefixing.
- assert.Equal(t, "http://example.com/blog/superbob", permalink, "invalid doc3 permalink")
+ require.Equal(t, "http://example.com/blog/superbob", permalink, "invalid doc3 permalink")
- assert.Equal(t, "/superbob", doc3.URL(), "invalid url, was specified on doc3")
- assertFileContent(t, fs, "public/superbob/index.html", true, "doc3|Hello|en")
- assert.Equal(t, doc2.Next, doc3, "doc3 should follow doc2, in .Next")
+ require.Equal(t, "/superbob", doc3.URL(), "invalid url, was specified on doc3")
+ th.assertFileContent(t, fs, "public/superbob/index.html", true, "doc3|Hello|en")
+ require.Equal(t, doc2.Next, doc3, "doc3 should follow doc2, in .Next")
doc1fr := doc1en.Translations()[0]
permalink = doc1fr.Permalink()
- assert.NoError(t, err, "permalink call failed")
- assert.Equal(t, "http://example.com/blog/fr/sect/doc1/", permalink, "invalid doc1fr permalink")
+ require.NoError(t, err, "permalink call failed")
+ require.Equal(t, "http://example.com/blog/fr/sect/doc1/", permalink, "invalid doc1fr permalink")
- assert.Equal(t, doc1en.Translations()[0], doc1fr, "doc1-en should have doc1-fr as translation")
- assert.Equal(t, doc1fr.Translations()[0], doc1en, "doc1-fr should have doc1-en as translation")
- assert.Equal(t, "fr", doc1fr.Language().Lang)
+ require.Equal(t, doc1en.Translations()[0], doc1fr, "doc1-en should have doc1-fr as translation")
+ require.Equal(t, doc1fr.Translations()[0], doc1en, "doc1-fr should have doc1-en as translation")
+ require.Equal(t, "fr", doc1fr.Language().Lang)
doc4 := enSite.AllPages[4]
permalink = doc4.Permalink()
- assert.Equal(t, "http://example.com/blog/fr/sect/doc4/", permalink, "invalid doc4 permalink")
- assert.Equal(t, "/blog/fr/sect/doc4/", doc4.URL())
+ require.Equal(t, "http://example.com/blog/fr/sect/doc4/", permalink, "invalid doc4 permalink")
+ require.Equal(t, "/blog/fr/sect/doc4/", doc4.URL())
- assert.Len(t, doc4.Translations(), 0, "found translations for doc4")
+ require.Len(t, doc4.Translations(), 0, "found translations for doc4")
doc5 := enSite.AllPages[5]
permalink = doc5.Permalink()
- assert.Equal(t, "http://example.com/blog/fr/somewhere/else/doc5", permalink, "invalid doc5 permalink")
+ require.Equal(t, "http://example.com/blog/fr/somewhere/else/doc5", permalink, "invalid doc5 permalink")
// Taxonomies and their URLs
- assert.Len(t, enSite.Taxonomies, 1, "should have 1 taxonomy")
+ require.Len(t, enSite.Taxonomies, 1, "should have 1 taxonomy")
tags := enSite.Taxonomies["tags"]
- assert.Len(t, tags, 2, "should have 2 different tags")
- assert.Equal(t, tags["tag1"][0].Page, doc1en, "first tag1 page should be doc1")
+ require.Len(t, tags, 2, "should have 2 different tags")
+ require.Equal(t, tags["tag1"][0].Page, doc1en, "first tag1 page should be doc1")
frSite := sites.Sites[1]
- assert.Equal(t, "fr", frSite.Language.Lang)
- assert.Len(t, frSite.RegularPages, 3, "should have 3 pages")
- assert.Len(t, frSite.AllPages, 28, "should have 28 total pages (including translations and nodes)")
+ require.Equal(t, "fr", frSite.Language.Lang)
+ require.Len(t, frSite.RegularPages, 3, "should have 3 pages")
+ require.Len(t, frSite.AllPages, 28, "should have 28 total pages (including translations and nodes)")
for _, frenchPage := range frSite.RegularPages {
- assert.Equal(t, "fr", frenchPage.Lang())
+ require.Equal(t, "fr", frenchPage.Lang())
}
// Check redirect to main language, French
@@ -339,12 +331,12 @@
require.True(t, strings.Contains(languageRedirect, "0; url=http://example.com/blog/fr"), languageRedirect)
// check home page content (including data files rendering)
- assertFileContent(t, fs, "public/en/index.html", true, "Home Page 1", "Hello", "Hugo Rocks!")
- assertFileContent(t, fs, "public/fr/index.html", true, "Home Page 1", "Bonjour", "Hugo Rocks!")
+ th.assertFileContent(t, fs, "public/en/index.html", true, "Home Page 1", "Hello", "Hugo Rocks!")
+ th.assertFileContent(t, fs, "public/fr/index.html", true, "Home Page 1", "Bonjour", "Hugo Rocks!")
// check single page content
- assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
- assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
+ th.assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
+ th.assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
// Check node translations
homeEn := enSite.getPage(KindHome)
@@ -397,9 +389,9 @@
readDestination(t, fs, "public/en/tags/tag1/index.html")
// Check Blackfriday config
- assert.True(t, strings.Contains(string(doc1fr.Content), "«"), string(doc1fr.Content))
- assert.False(t, strings.Contains(string(doc1en.Content), "«"), string(doc1en.Content))
- assert.True(t, strings.Contains(string(doc1en.Content), "“"), string(doc1en.Content))
+ require.True(t, strings.Contains(string(doc1fr.Content), "«"), string(doc1fr.Content))
+ require.False(t, strings.Contains(string(doc1en.Content), "«"), string(doc1en.Content))
+ require.True(t, strings.Contains(string(doc1en.Content), "“"), string(doc1en.Content))
// Check that the drafts etc. are not built/processed/rendered.
assertShouldNotBuild(t, sites)
@@ -415,13 +407,14 @@
}
func TestMultiSitesRebuild(t *testing.T) {
-
+ // t.Parallel() not supported, see https://github.com/fortytw2/leaktest/issues/4
defer leaktest.Check(t)()
- testCommonResetState()
- fs := hugofs.NewMem()
- siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
+
+ siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: true}
sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
+ fs := sites.Fs
cfg := BuildCfg{Watching: true}
+ th := testHelper{sites.Cfg}
err := sites.Build(cfg)
@@ -442,12 +435,12 @@
require.Len(t, frSite.RegularPages, 3)
// Verify translations
- assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Hello")
- assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Bonjour")
+ th.assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Hello")
+ th.assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Bonjour")
// check single page content
- assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
- assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
+ th.assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
+ th.assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
for i, this := range []struct {
preFunc func(t *testing.T)
@@ -585,8 +578,8 @@
require.Len(t, enSite.RegularPages, 5)
require.Len(t, enSite.AllPages, 30)
require.Len(t, frSite.RegularPages, 4)
- assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Modified Shortcode: Salut")
- assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Modified Shortcode: Hello")
+ th.assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Modified Shortcode: Salut")
+ th.assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Modified Shortcode: Hello")
},
},
} {
@@ -630,9 +623,8 @@
}
func TestAddNewLanguage(t *testing.T) {
- testCommonResetState()
- fs := hugofs.NewMem()
- siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
+ t.Parallel()
+ siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: true}
sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
cfg := BuildCfg{}
@@ -643,6 +635,8 @@
t.Fatalf("Failed to build sites: %s", err)
}
+ fs := sites.Fs
+
newConfig := multiSiteTOMLConfigTemplate + `
[Languages.sv]
@@ -657,7 +651,7 @@
writeSource(t, fs, "multilangconfig.toml", newConfig)
// Watching does not work with in-memory fs, so we trigger a reload manually
- require.NoError(t, viper.ReadInConfig())
+ require.NoError(t, sites.Cfg.(*helpers.Language).Cfg.(*viper.Viper).ReadInConfig())
err = sites.Build(BuildCfg{CreateSitesFromConfig: true})
if err != nil {
@@ -694,11 +688,15 @@
}
func TestChangeDefaultLanguage(t *testing.T) {
- testCommonResetState()
- viper.Set("defaultContentLanguageInSubdir", false)
- fs := hugofs.NewMem()
- sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}, multiSiteTOMLConfigTemplate)
+ t.Parallel()
+ mf := afero.NewMemMapFs()
+
+ sites := createMultiTestSites(t, testSiteConfig{Fs: mf, DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: false}, multiSiteTOMLConfigTemplate)
+
+ require.Equal(t, mf, sites.Fs.Source)
+
cfg := BuildCfg{}
+ th := testHelper{sites.Cfg}
err := sites.Build(cfg)
@@ -706,16 +704,19 @@
t.Fatalf("Failed to build sites: %s", err)
}
- assertFileContent(t, fs, "public/sect/doc1/index.html", true, "Single", "Bonjour")
- assertFileContent(t, fs, "public/en/sect/doc2/index.html", true, "Single", "Hello")
+ fs := sites.Fs
- newConfig := createConfig(t, testSiteConfig{DefaultContentLanguage: "en"}, multiSiteTOMLConfigTemplate)
+ th.assertFileContent(t, fs, "public/sect/doc1/index.html", false, "Single", "Bonjour")
+ th.assertFileContent(t, fs, "public/en/sect/doc2/index.html", false, "Single", "Hello")
+ newConfig := createConfig(t, testSiteConfig{Fs: mf, DefaultContentLanguage: "en", DefaultContentLanguageInSubdir: false}, multiSiteTOMLConfigTemplate)
+
// replace the config
writeSource(t, fs, "multilangconfig.toml", newConfig)
// Watching does not work with in-memory fs, so we trigger a reload manually
- require.NoError(t, viper.ReadInConfig())
+ // This does not look pretty, so we should think of something else.
+ require.NoError(t, th.Cfg.(*helpers.Language).Cfg.(*viper.Viper).ReadInConfig())
err = sites.Build(BuildCfg{CreateSitesFromConfig: true})
if err != nil {
@@ -723,19 +724,19 @@
}
// Default language is now en, so that should now be the "root" language
- assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Bonjour")
- assertFileContent(t, fs, "public/sect/doc2/index.html", true, "Single", "Hello")
+ th.assertFileContent(t, fs, "public/fr/sect/doc1/index.html", false, "Single", "Bonjour")
+ th.assertFileContent(t, fs, "public/sect/doc2/index.html", false, "Single", "Hello")
}
func TestTableOfContentsInShortcodes(t *testing.T) {
- testCommonResetState()
- fs := hugofs.NewMem()
+ t.Parallel()
+ mf := afero.NewMemMapFs()
- writeSource(t, fs, "layouts/shortcodes/toc.html", tocShortcode)
- writeSource(t, fs, "content/post/simple.en.md", tocPageSimple)
- writeSource(t, fs, "content/post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings)
+ writeToFs(t, mf, "layouts/shortcodes/toc.html", tocShortcode)
+ writeToFs(t, mf, "content/post/simple.en.md", tocPageSimple)
+ writeToFs(t, mf, "content/post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings)
- sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "en", Fs: fs}, multiSiteTOMLConfigTemplate)
+ sites := createMultiTestSites(t, testSiteConfig{Fs: mf, DefaultContentLanguage: "en", DefaultContentLanguageInSubdir: true}, multiSiteTOMLConfigTemplate)
cfg := BuildCfg{}
@@ -745,8 +746,11 @@
t.Fatalf("Failed to build sites: %s", err)
}
- assertFileContent(t, fs, "public/en/post/simple/index.html", true, tocPageSimpleExpected)
- assertFileContent(t, fs, "public/en/post/withSCInHeading/index.html", true, tocPageWithShortcodesInHeadingsExpected)
+ th := testHelper{sites.Cfg}
+ fs := sites.Fs
+
+ th.assertFileContent(t, fs, "public/en/post/simple/index.html", true, tocPageSimpleExpected)
+ th.assertFileContent(t, fs, "public/en/post/withSCInHeading/index.html", true, tocPageWithShortcodesInHeadingsExpected)
}
var tocShortcode = `
@@ -836,6 +840,7 @@
paginate = 1
defaultContentLanguage = "{{ .DefaultContentLanguage }}"
+defaultContentLanguageInSubdir = {{ .DefaultContentLanguageInSubdir }}
[permalinks]
other = "/somewhere/else/:filename"
@@ -886,7 +891,7 @@
lag = "lag"
`
-var multiSiteYAMLConfig = `
+var multiSiteYAMLConfigTemplate = `
defaultExtension: "html"
baseURL: "http://example.com/blog"
disableSitemap: false
@@ -894,7 +899,8 @@
rssURI: "index.xml"
paginate: 1
-defaultContentLanguage: "fr"
+defaultContentLanguage: "{{ .DefaultContentLanguage }}"
+defaultContentLanguageInSubdir: {{ .DefaultContentLanguageInSubdir }}
permalinks:
other: "/somewhere/else/:filename"
@@ -945,7 +951,7 @@
`
-var multiSiteJSONConfig = `
+var multiSiteJSONConfigTemplate = `
{
"defaultExtension": "html",
"baseURL": "http://example.com/blog",
@@ -953,7 +959,8 @@
"disableRSS": false,
"rssURI": "index.xml",
"paginate": 1,
- "defaultContentLanguage": "fr",
+ "defaultContentLanguage": "{{ .DefaultContentLanguage }}",
+ "defaultContentLanguageInSubdir": true,
"permalinks": {
"other": "/somewhere/else/:filename"
},
@@ -1026,11 +1033,12 @@
func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, configTemplate, configSuffix string) *HugoSites {
- depsCfg := deps.DepsCfg{Fs: siteConfig.Fs}
configContent := createConfig(t, siteConfig, configTemplate)
+ mf := siteConfig.Fs
+
// Add some layouts
- if err := afero.WriteFile(depsCfg.Fs.Source,
+ if err := afero.WriteFile(mf,
filepath.Join("layouts", "_default/single.html"),
[]byte("Single: {{ .Title }}|{{ i18n \"hello\" }}|{{.Lang}}|{{ .Content }}"),
0755); err != nil {
@@ -1037,7 +1045,7 @@
t.Fatalf("Failed to write layout file: %s", err)
}
- if err := afero.WriteFile(depsCfg.Fs.Source,
+ if err := afero.WriteFile(mf,
filepath.Join("layouts", "_default/list.html"),
[]byte("{{ $p := .Paginator }}List Page {{ $p.PageNumber }}: {{ .Title }}|{{ i18n \"hello\" }}|{{ .Permalink }}"),
0755); err != nil {
@@ -1044,7 +1052,7 @@
t.Fatalf("Failed to write layout file: %s", err)
}
- if err := afero.WriteFile(depsCfg.Fs.Source,
+ if err := afero.WriteFile(mf,
filepath.Join("layouts", "index.html"),
[]byte("{{ $p := .Paginator }}Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}"),
0755); err != nil {
@@ -1052,7 +1060,7 @@
}
// Add a shortcode
- if err := afero.WriteFile(depsCfg.Fs.Source,
+ if err := afero.WriteFile(mf,
filepath.Join("layouts", "shortcodes", "shortcode.html"),
[]byte("Shortcode: {{ i18n \"hello\" }}"),
0755); err != nil {
@@ -1060,7 +1068,7 @@
}
// Add some language files
- if err := afero.WriteFile(depsCfg.Fs.Source,
+ if err := afero.WriteFile(mf,
filepath.Join("i18n", "en.yaml"),
[]byte(`
- id: hello
@@ -1069,7 +1077,7 @@
0755); err != nil {
t.Fatalf("Failed to write language file: %s", err)
}
- if err := afero.WriteFile(depsCfg.Fs.Source,
+ if err := afero.WriteFile(mf,
filepath.Join("i18n", "fr.yaml"),
[]byte(`
- id: hello
@@ -1223,26 +1231,25 @@
}
configFile := "multilangconfig." + configSuffix
- writeSource(t, depsCfg.Fs, configFile, configContent)
+ writeToFs(t, mf, configFile, configContent)
- viper.SetFs(depsCfg.Fs.Source)
+ cfg, err := LoadConfig(mf, "", configFile)
+ require.NoError(t, err)
- if err := LoadGlobalConfig("", configFile); err != nil {
- t.Fatalf("Failed to load config: %s", err)
- }
+ fs := hugofs.NewFrom(mf, cfg)
// Hugo support using ByteSource's directly (for testing),
// but to make it more real, we write them to the mem file system.
for _, s := range sources {
- if err := afero.WriteFile(depsCfg.Fs.Source, filepath.Join("content", s.Name), s.Content, 0755); err != nil {
+ if err := afero.WriteFile(mf, filepath.Join("content", s.Name), s.Content, 0755); err != nil {
t.Fatalf("Failed to write file: %s", err)
}
}
// Add some data
- writeSource(t, depsCfg.Fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
+ writeSource(t, fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
- sites, err := NewHugoSitesFromConfiguration(depsCfg)
+ sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg}) //, Logger: newDebugLogger()})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@@ -1252,11 +1259,19 @@
t.Fatalf("Got %d sites", len(sites.Sites))
}
+ if sites.Fs.Source != mf {
+ t.Fatal("FS mismatch")
+ }
+
return sites
}
func writeSource(t *testing.T, fs *hugofs.Fs, filename, content string) {
- if err := afero.WriteFile(fs.Source, filepath.FromSlash(filename), []byte(content), 0755); err != nil {
+ writeToFs(t, fs.Source, filename, content)
+}
+
+func writeToFs(t *testing.T, fs afero.Fs, filename, content string) {
+ if err := afero.WriteFile(fs, filepath.FromSlash(filename), []byte(content), 0755); err != nil {
t.Fatalf("Failed to write file: %s", err)
}
}
--- a/hugolib/i18n.go
+++ /dev/null
@@ -1,42 +1,0 @@
-// Copyright 2016 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 (
- "fmt"
-
- "github.com/nicksnyder/go-i18n/i18n/bundle"
- "github.com/spf13/hugo/source"
- "github.com/spf13/hugo/tpl"
-)
-
-func (s *Site) loadI18n(sources []source.Input) error {
- s.Log.DEBUG.Printf("Load I18n from %q", sources)
-
- i18nBundle := bundle.New()
-
- for _, currentSource := range sources {
- for _, r := range currentSource.Files() {
- err := i18nBundle.ParseTranslationFileBytes(r.LogicalName(), r.Bytes())
- if err != nil {
- return fmt.Errorf("Failed to load translations in file %q: %s", r.LogicalName(), err)
- }
- }
- }
-
- tpl.SetI18nTfuncs(i18nBundle)
-
- return nil
-
-}
--- a/hugolib/menu_test.go
+++ b/hugolib/menu_test.go
@@ -20,14 +20,10 @@
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/hugofs"
-
"path/filepath"
toml "github.com/pelletier/go-toml"
"github.com/spf13/hugo/source"
- "github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -187,7 +183,7 @@
// Issue 817 - identifier should trump everything
func TestPageMenuWithIdentifier(t *testing.T) {
-
+ t.Parallel()
toml := []source.ByteSource{
{Name: "sect/doc1.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i1")},
{Name: "sect/doc2.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i2")},
@@ -206,7 +202,6 @@
}
func doTestPageMenuWithIdentifier(t *testing.T, menuPageSources []source.ByteSource) {
- testCommonResetState()
s := setupMenuTests(t, menuPageSources)
@@ -225,7 +220,7 @@
// Issue 817 contd - name should be second identifier in
func TestPageMenuWithDuplicateName(t *testing.T) {
-
+ t.Parallel()
toml := []source.ByteSource{
{Name: "sect/doc1.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n1")},
{Name: "sect/doc2.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n2")},
@@ -244,7 +239,6 @@
}
func doTestPageMenuWithDuplicateName(t *testing.T, menuPageSources []source.ByteSource) {
- testCommonResetState()
s := setupMenuTests(t, menuPageSources)
@@ -262,8 +256,7 @@
}
func TestPageMenu(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
s := setupMenuTests(t, menuPageSources)
if len(s.RegularPages) != 3 {
@@ -312,8 +305,7 @@
}
func TestMenuURL(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
s := setupMenuTests(t, menuPageSources)
for i, this := range []struct {
@@ -342,8 +334,7 @@
// Issue #1934
func TestYAMLMenuWithMultipleEntries(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
ps1 := []byte(`---
title: "Yaml 1"
weight: 5
@@ -373,7 +364,7 @@
// issue #719
func TestMenuWithUnicodeURLs(t *testing.T) {
-
+ t.Parallel()
for _, canonifyURLs := range []bool{true, false} {
doTestMenuWithUnicodeURLs(t, canonifyURLs)
}
@@ -380,12 +371,9 @@
}
func doTestMenuWithUnicodeURLs(t *testing.T, canonifyURLs bool) {
- testCommonResetState()
- viper.Set("canonifyURLs", canonifyURLs)
+ s := setupMenuTests(t, menuPageSources, "canonifyURLs", canonifyURLs)
- s := setupMenuTests(t, menuPageSources)
-
unicodeRussian := findTestMenuEntryByID(s, "unicode", "unicode-russian")
expected := "/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0"
@@ -399,19 +387,18 @@
// Issue #1114
func TestSectionPagesMenu(t *testing.T) {
-
+ t.Parallel()
doTestSectionPagesMenu(true, t)
doTestSectionPagesMenu(false, t)
}
func doTestSectionPagesMenu(canonifyURLs bool, t *testing.T) {
- testCommonResetState()
- viper.Set("sectionPagesMenu", "spm")
+ s := setupMenuTests(t, menuPageSectionsSources,
+ "sectionPagesMenu", "spm",
+ "canonifyURLs", canonifyURLs,
+ )
- viper.Set("canonifyURLs", canonifyURLs)
- s := setupMenuTests(t, menuPageSectionsSources)
-
require.Equal(t, 3, len(s.Sections))
firstSectionPages := s.Sections["first"]
@@ -463,6 +450,8 @@
}
func TestTaxonomyNodeMenu(t *testing.T) {
+ t.Parallel()
+
type taxRenderInfo struct {
key string
singular string
@@ -469,11 +458,8 @@
plural string
}
- testCommonResetState()
+ s := setupMenuTests(t, menuPageSources, "canonifyURLs", true)
- viper.Set("canonifyURLs", true)
- s := setupMenuTests(t, menuPageSources)
-
for i, this := range []struct {
menu string
taxInfo taxRenderInfo
@@ -512,8 +498,7 @@
}
func TestMenuLimit(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
s := setupMenuTests(t, menuPageSources)
m := *s.Menus["main"]
@@ -528,7 +513,7 @@
}
func TestMenuSortByN(t *testing.T) {
-
+ t.Parallel()
for i, this := range []struct {
sortFunc func(p Menu) Menu
assertFunc func(p Menu) bool
@@ -554,13 +539,12 @@
}
func TestHomeNodeMenu(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ s := setupMenuTests(t, menuPageSources,
+ "canonifyURLs", true,
+ "uglyURLs", false,
+ )
- viper.Set("canonifyURLs", true)
- viper.Set("uglyURLs", false)
-
- s := setupMenuTests(t, menuPageSources)
-
home := s.getPage(KindHome)
homeMenuEntry := &MenuEntry{Name: home.Title, URL: home.URL()}
@@ -596,6 +580,7 @@
}
func TestHopefullyUniqueID(t *testing.T) {
+ t.Parallel()
assert.Equal(t, "i", (&MenuEntry{Identifier: "i", URL: "u", Name: "n"}).hopefullyUniqueID())
assert.Equal(t, "u", (&MenuEntry{Identifier: "", URL: "u", Name: "n"}).hopefullyUniqueID())
assert.Equal(t, "n", (&MenuEntry{Identifier: "", URL: "", Name: "n"}).hopefullyUniqueID())
@@ -602,6 +587,7 @@
}
func TestAddMenuEntryChild(t *testing.T) {
+ t.Parallel()
root := &MenuEntry{Weight: 1}
root.addChild(&MenuEntry{Weight: 2})
root.addChild(&MenuEntry{Weight: 1})
@@ -667,38 +653,28 @@
return found
}
-func setupTestMenuState(t *testing.T) {
- menus, err := tomlToMap(confMenu1)
+func setupMenuTests(t *testing.T, pageSources []source.ByteSource, configKeyValues ...interface{}) *Site {
- if err != nil {
- t.Fatalf("Unable to read menus: %v", err)
- }
+ var (
+ cfg, fs = newTestCfg()
+ )
- viper.Set("menu", menus["menu"])
- viper.Set("baseURL", "http://foo.local/Zoo/")
-}
+ menus, err := tomlToMap(confMenu1)
+ require.NoError(t, err)
-func setupMenuTests(t *testing.T, pageSources []source.ByteSource) *Site {
+ cfg.Set("menu", menus["menu"])
+ cfg.Set("baseURL", "http://foo.local/Zoo/")
- setupTestMenuState(t)
+ for i := 0; i < len(configKeyValues); i += 2 {
+ cfg.Set(configKeyValues[i].(string), configKeyValues[i+1])
+ }
- fs := hugofs.NewMem()
-
for _, src := range pageSources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
- return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
-
-}
-
-func createTestSite(pageSources []source.ByteSource) *Site {
-
- return &Site{
- Source: &source.InMemorySource{ByteSource: pageSources},
- Language: helpers.NewDefaultLanguage(),
- }
+ return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
}
--- a/hugolib/multilingual.go
+++ b/hugolib/multilingual.go
@@ -22,6 +22,7 @@
"fmt"
"github.com/spf13/cast"
+ "github.com/spf13/hugo/config"
"github.com/spf13/hugo/helpers"
)
@@ -44,7 +45,7 @@
return ml.langMap[lang]
}
-func newMultiLingualFromSites(sites ...*Site) (*Multilingual, error) {
+func newMultiLingualFromSites(cfg config.Provider, sites ...*Site) (*Multilingual, error) {
languages := make(helpers.Languages, len(sites))
for i, s := range sites {
@@ -54,12 +55,14 @@
languages[i] = s.Language
}
- return &Multilingual{Languages: languages, DefaultLang: helpers.NewDefaultLanguage()}, nil
+ defaultLang := cfg.GetString("defaultContentLanguage")
-}
+ if defaultLang == "" {
+ defaultLang = "en"
+ }
-func newMultiLingualDefaultLanguage() *Multilingual {
- return newMultiLingualForLanguage(helpers.NewDefaultLanguage())
+ return &Multilingual{Languages: languages, DefaultLang: helpers.NewLanguage(defaultLang, cfg)}, nil
+
}
func newMultiLingualForLanguage(language *helpers.Language) *Multilingual {
@@ -77,7 +80,7 @@
return s.owner.multilingual != nil && s.owner.multilingual.enabled()
}
-func toSortedLanguages(l map[string]interface{}) (helpers.Languages, error) {
+func toSortedLanguages(cfg config.Provider, l map[string]interface{}) (helpers.Languages, error) {
langs := make(helpers.Languages, len(l))
i := 0
@@ -88,7 +91,7 @@
return nil, fmt.Errorf("Language config is not a map: %T", langConf)
}
- language := helpers.NewLanguage(lang)
+ language := helpers.NewLanguage(lang, cfg)
for loki, v := range langsMap {
switch loki {
--- a/hugolib/node_as_page_test.go
+++ b/hugolib/node_as_page_test.go
@@ -21,9 +21,10 @@
"time"
+ "github.com/spf13/afero"
+
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
- "github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
@@ -35,6 +36,7 @@
*/
func TestNodesAsPage(t *testing.T) {
+ t.Parallel()
for _, preserveTaxonomyNames := range []bool{false, true} {
for _, ugly := range []bool{true, false} {
doTestNodeAsPage(t, ugly, preserveTaxonomyNames)
@@ -54,26 +56,25 @@
*/
- testCommonResetState()
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- viper.Set("uglyURLs", ugly)
- viper.Set("preserveTaxonomyNames", preserveTaxonomyNames)
+ cfg.Set("uglyURLs", ugly)
+ cfg.Set("preserveTaxonomyNames", preserveTaxonomyNames)
- viper.Set("paginate", 1)
- viper.Set("title", "Hugo Rocks")
- viper.Set("rssURI", "customrss.xml")
+ cfg.Set("paginate", 1)
+ cfg.Set("title", "Hugo Rocks")
+ cfg.Set("rssURI", "customrss.xml")
- depsCfg := newTestDepsConfig()
+ writeLayoutsForNodeAsPageTests(t, fs)
+ writeNodePagesForNodeAsPageTests(t, fs, "")
- viper.SetFs(depsCfg.Fs.Source)
+ writeRegularPagesForNodeAsPageTests(t, fs)
- writeLayoutsForNodeAsPageTests(t, depsCfg.Fs)
- writeNodePagesForNodeAsPageTests(t, depsCfg.Fs, "")
+ sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
- writeRegularPagesForNodeAsPageTests(t, depsCfg.Fs)
-
- sites, err := NewHugoSitesFromConfiguration(depsCfg)
-
require.NoError(t, err)
require.NoError(t, sites.Build(BuildCfg{}))
@@ -80,7 +81,7 @@
// date order: home, sect1, sect2, cat/hugo, cat/web, categories
- assertFileContent(t, depsCfg.Fs, filepath.Join("public", "index.html"), false,
+ th.assertFileContent(t, fs, filepath.Join("public", "index.html"), false,
"Index Title: Home Sweet Home!",
"Home <strong>Content!</strong>",
"# Pages: 4",
@@ -89,7 +90,7 @@
"GetPage: Section1 ",
)
- assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect1", "regular1"), false, "Single Title: Page 01", "Content Page 01")
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect1", "regular1"), false, "Single Title: Page 01", "Content Page 01")
nodes := sites.findAllPagesByKindNotIn(KindPage)
@@ -115,17 +116,17 @@
require.True(t, first.IsPage())
// Check Home paginator
- assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "page", "2"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "page", "2"), false,
"Pag: Page 02")
// Check Sections
- assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect1"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect1"), false,
"Section Title: Section", "Section1 <strong>Content!</strong>",
"Date: 2009-01-04",
"Lastmod: 2009-01-05",
)
- assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect2"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect2"), false,
"Section Title: Section", "Section2 <strong>Content!</strong>",
"Date: 2009-01-06",
"Lastmod: 2009-01-07",
@@ -132,7 +133,7 @@
)
// Check Sections paginator
- assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect1", "page", "2"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect1", "page", "2"), false,
"Pag: Page 02")
sections := sites.findAllPagesByKind(KindSection)
@@ -140,13 +141,13 @@
require.Len(t, sections, 2)
// Check taxonomy lists
- assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "hugo"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories", "hugo"), false,
"Taxonomy Title: Taxonomy Hugo", "Taxonomy Hugo <strong>Content!</strong>",
"Date: 2009-01-08",
"Lastmod: 2009-01-09",
)
- assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "hugo-rocks"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories", "hugo-rocks"), false,
"Taxonomy Title: Taxonomy Hugo Rocks",
)
@@ -156,7 +157,7 @@
require.NotNil(t, web)
require.Len(t, web.Data["Pages"].(Pages), 4)
- assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "web"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories", "web"), false,
"Taxonomy Title: Taxonomy Web",
"Taxonomy Web <strong>Content!</strong>",
"Date: 2009-01-10",
@@ -164,12 +165,12 @@
)
// Check taxonomy list paginator
- assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "hugo", "page", "2"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories", "hugo", "page", "2"), false,
"Taxonomy Title: Taxonomy Hugo",
"Pag: Page 02")
// Check taxonomy terms
- assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories"), false,
"Taxonomy Terms Title: Taxonomy Term Categories", "Taxonomy Term Categories <strong>Content!</strong>", "k/v: hugo",
"Date: 2009-01-14",
"Lastmod: 2009-01-15",
@@ -178,15 +179,16 @@
// There are no pages to paginate over in the taxonomy terms.
// RSS
- assertFileContent(t, depsCfg.Fs, filepath.Join("public", "customrss.xml"), false, "Recent content in Home Sweet Home! on Hugo Rocks", "<rss")
- assertFileContent(t, depsCfg.Fs, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Section1 on Hugo Rocks", "<rss")
- assertFileContent(t, depsCfg.Fs, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Section2 on Hugo Rocks", "<rss")
- assertFileContent(t, depsCfg.Fs, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Taxonomy Hugo on Hugo Rocks", "<rss")
- assertFileContent(t, depsCfg.Fs, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Taxonomy Web on Hugo Rocks", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "customrss.xml"), false, "Recent content in Home Sweet Home! on Hugo Rocks", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Section1 on Hugo Rocks", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Section2 on Hugo Rocks", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Taxonomy Hugo on Hugo Rocks", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Taxonomy Web on Hugo Rocks", "<rss")
}
func TestNodesWithNoContentFile(t *testing.T) {
+ t.Parallel()
for _, ugly := range []bool{false, true} {
doTestNodesWithNoContentFile(t, ugly)
}
@@ -193,19 +195,21 @@
}
func doTestNodesWithNoContentFile(t *testing.T, ugly bool) {
- testCommonResetState()
- viper.Set("uglyURLs", ugly)
- viper.Set("paginate", 1)
- viper.Set("title", "Hugo Rocks!")
- viper.Set("rssURI", "customrss.xml")
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- fs := hugofs.NewMem()
+ cfg.Set("uglyURLs", ugly)
+ cfg.Set("paginate", 1)
+ cfg.Set("title", "Hugo Rocks!")
+ cfg.Set("rssURI", "customrss.xml")
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
- sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
+ sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
@@ -222,7 +226,7 @@
require.Len(t, homePage.Pages, 4)
require.True(t, homePage.Path() == "")
- assertFileContent(t, fs, filepath.Join("public", "index.html"), false,
+ th.assertFileContent(t, fs, filepath.Join("public", "index.html"), false,
"Index Title: Hugo Rocks!",
"Date: 2010-06-12",
"Lastmod: 2010-06-13",
@@ -229,7 +233,7 @@
)
// Taxonomy list
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories", "hugo"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories", "hugo"), false,
"Taxonomy Title: Hugo",
"Date: 2010-06-12",
"Lastmod: 2010-06-13",
@@ -236,7 +240,7 @@
)
// Taxonomy terms
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories"), false,
"Taxonomy Terms Title: Categories",
)
@@ -254,13 +258,13 @@
}
// Sections
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect1"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect1"), false,
"Section Title: Sect1s",
"Date: 2010-06-12",
"Lastmod: 2010-06-13",
)
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect2"), false,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect2"), false,
"Section Title: Sect2s",
"Date: 2008-07-06",
"Lastmod: 2008-07-09",
@@ -267,15 +271,16 @@
)
// RSS
- assertFileContent(t, fs, filepath.Join("public", "customrss.xml"), false, "Hugo Rocks!", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Sect1s on Hugo Rocks!", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Sect2s on Hugo Rocks!", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Hugo on Hugo Rocks!", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Web on Hugo Rocks!", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "customrss.xml"), false, "Hugo Rocks!", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Sect1s on Hugo Rocks!", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Sect2s on Hugo Rocks!", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Hugo on Hugo Rocks!", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Web on Hugo Rocks!", "<rss")
}
func TestNodesAsPageMultilingual(t *testing.T) {
+ t.Parallel()
for _, ugly := range []bool{false, true} {
doTestNodesAsPageMultilingual(t, ugly)
}
@@ -283,15 +288,9 @@
func doTestNodesAsPageMultilingual(t *testing.T, ugly bool) {
- testCommonResetState()
+ mf := afero.NewMemMapFs()
- fs := hugofs.NewMem()
-
- viper.Set("uglyURLs", ugly)
-
- viper.SetFs(fs.Source)
-
- writeSource(t, fs, "config.toml",
+ writeToFs(t, mf, "config.toml",
`
paginage = 1
title = "Hugo Multilingual Rocks!"
@@ -317,6 +316,13 @@
title = "Deutsche Hugo"
`)
+ cfg, err := LoadConfig(mf, "", "config.toml")
+ require.NoError(t, err)
+
+ cfg.Set("uglyURLs", ugly)
+
+ fs := hugofs.NewFrom(mf, cfg)
+
writeLayoutsForNodeAsPageTests(t, fs)
for _, lang := range []string{"nn", "en"} {
@@ -323,11 +329,9 @@
writeRegularPagesForNodeAsPageTestsWithLang(t, fs, lang)
}
- if err := LoadGlobalConfig("", "config.toml"); err != nil {
- t.Fatalf("Failed to load config: %s", err)
- }
+ th := testHelper{cfg}
- sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
+ sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@@ -372,64 +376,66 @@
require.Equal(t, expetedPermalink(ugly, "/en/sect1/"), enSect.Permalink())
- assertFileContent(t, fs, filepath.Join("public", "nn", "index.html"), true,
+ th.assertFileContent(t, fs, filepath.Join("public", "nn", "index.html"), true,
"Index Title: Hugo på norsk")
- assertFileContent(t, fs, filepath.Join("public", "en", "index.html"), true,
+ th.assertFileContent(t, fs, filepath.Join("public", "en", "index.html"), true,
"Index Title: Home Sweet Home!", "<strong>Content!</strong>")
- assertFileContent(t, fs, filepath.Join("public", "de", "index.html"), true,
+ th.assertFileContent(t, fs, filepath.Join("public", "de", "index.html"), true,
"Index Title: Home Sweet Home!", "<strong>Content!</strong>")
// Taxonomy list
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories", "hugo"), true,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories", "hugo"), true,
"Taxonomy Title: Hugo")
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories", "hugo"), true,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories", "hugo"), true,
"Taxonomy Title: Taxonomy Hugo")
// Taxonomy terms
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories"), true,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories"), true,
"Taxonomy Terms Title: Categories")
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories"), true,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories"), true,
"Taxonomy Terms Title: Taxonomy Term Categories")
// Sections
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1"), true,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1"), true,
"Section Title: Sect1s")
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect2"), true,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect2"), true,
"Section Title: Sect2s")
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1"), true,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1"), true,
"Section Title: Section1")
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect2"), true,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect2"), true,
"Section Title: Section2")
// Regular pages
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1", "regular1"), true,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1", "regular1"), true,
"Single Title: Page 01")
- assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1", "regular2"), true,
+ th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1", "regular2"), true,
"Single Title: Page 02")
// RSS
- assertFileContent(t, fs, filepath.Join("public", "nn", "customrss.xml"), true, "Hugo på norsk", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "nn", "sect1", "customrss.xml"), true, "Recent content in Sect1s on Hugo på norsk", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "nn", "sect2", "customrss.xml"), true, "Recent content in Sect2s on Hugo på norsk", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "nn", "categories", "hugo", "customrss.xml"), true, "Recent content in Hugo on Hugo på norsk", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "nn", "categories", "web", "customrss.xml"), true, "Recent content in Web on Hugo på norsk", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "nn", "customrss.xml"), true, "Hugo på norsk", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "nn", "sect1", "customrss.xml"), true, "Recent content in Sect1s on Hugo på norsk", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "nn", "sect2", "customrss.xml"), true, "Recent content in Sect2s on Hugo på norsk", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "nn", "categories", "hugo", "customrss.xml"), true, "Recent content in Hugo on Hugo på norsk", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "nn", "categories", "web", "customrss.xml"), true, "Recent content in Web on Hugo på norsk", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "en", "customrss.xml"), true, "Recent content in Home Sweet Home! on Hugo in English", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "en", "sect1", "customrss.xml"), true, "Recent content in Section1 on Hugo in English", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "en", "sect2", "customrss.xml"), true, "Recent content in Section2 on Hugo in English", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "en", "categories", "hugo", "customrss.xml"), true, "Recent content in Taxonomy Hugo on Hugo in English", "<rss")
- assertFileContent(t, fs, filepath.Join("public", "en", "categories", "web", "customrss.xml"), true, "Recent content in Taxonomy Web on Hugo in English", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "en", "customrss.xml"), true, "Recent content in Home Sweet Home! on Hugo in English", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "en", "sect1", "customrss.xml"), true, "Recent content in Section1 on Hugo in English", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "en", "sect2", "customrss.xml"), true, "Recent content in Section2 on Hugo in English", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "en", "categories", "hugo", "customrss.xml"), true, "Recent content in Taxonomy Hugo on Hugo in English", "<rss")
+ th.assertFileContent(t, fs, filepath.Join("public", "en", "categories", "web", "customrss.xml"), true, "Recent content in Taxonomy Web on Hugo in English", "<rss")
}
func TestNodesWithTaxonomies(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- fs := hugofs.NewMem()
+ cfg.Set("paginate", 1)
+ cfg.Set("title", "Hugo Rocks!")
- viper.Set("paginate", 1)
- viper.Set("title", "Hugo Rocks!")
-
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
@@ -442,25 +448,27 @@
---
`)
- h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
+ h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
require.NoError(t, h.Build(BuildCfg{}))
- assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy Title: Hugo", "# Pages: 5")
- assertFileContent(t, fs, filepath.Join("public", "categories", "home", "index.html"), true, "Taxonomy Title: Home", "# Pages: 1")
+ th.assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy Title: Hugo", "# Pages: 5")
+ th.assertFileContent(t, fs, filepath.Join("public", "categories", "home", "index.html"), true, "Taxonomy Title: Home", "# Pages: 1")
}
func TestNodesWithMenu(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- viper.Set("paginate", 1)
- viper.Set("title", "Hugo Rocks!")
+ cfg.Set("paginate", 1)
+ cfg.Set("title", "Hugo Rocks!")
- fs := hugofs.NewMem()
-
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
@@ -488,27 +496,29 @@
---
`)
- h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
+ h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
require.NoError(t, h.Build(BuildCfg{}))
- assertFileContent(t, fs, filepath.Join("public", "index.html"), true, "Home With Menu", "Home Menu Item: Go Home!: /")
- assertFileContent(t, fs, filepath.Join("public", "sect1", "index.html"), true, "Sect1 With Menu", "Section Menu Item: Go Sect1!: /sect1/")
- assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy With Menu", "Taxonomy Menu Item: Go Tax Hugo!: /categories/hugo/")
+ th.assertFileContent(t, fs, filepath.Join("public", "index.html"), true, "Home With Menu", "Home Menu Item: Go Home!: /")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect1", "index.html"), true, "Sect1 With Menu", "Section Menu Item: Go Sect1!: /sect1/")
+ th.assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy With Menu", "Taxonomy Menu Item: Go Tax Hugo!: /categories/hugo/")
}
func TestNodesWithAlias(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- fs := hugofs.NewMem()
+ cfg.Set("paginate", 1)
+ cfg.Set("baseURL", "http://base/")
+ cfg.Set("title", "Hugo Rocks!")
- viper.Set("paginate", 1)
- viper.Set("baseURL", "http://base/")
- viper.Set("title", "Hugo Rocks!")
-
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
@@ -519,25 +529,27 @@
---
`)
- h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
+ h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
require.NoError(t, h.Build(BuildCfg{}))
- assertFileContent(t, fs, filepath.Join("public", "index.html"), true, "Home With Alias")
- assertFileContent(t, fs, filepath.Join("public", "my", "new", "home.html"), true, "content=\"0; url=http://base/")
+ th.assertFileContent(t, fs, filepath.Join("public", "index.html"), true, "Home With Alias")
+ th.assertFileContent(t, fs, filepath.Join("public", "my", "new", "home.html"), true, "content=\"0; url=http://base/")
}
func TestNodesWithSectionWithIndexPageOnly(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- fs := hugofs.NewMem()
+ cfg.Set("paginate", 1)
+ cfg.Set("title", "Hugo Rocks!")
- viper.Set("paginate", 1)
- viper.Set("title", "Hugo Rocks!")
-
writeLayoutsForNodeAsPageTests(t, fs)
writeSource(t, fs, filepath.Join("content", "sect", "_index.md"), `---
@@ -546,25 +558,27 @@
My Section Content
`)
- h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
+ h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
require.NoError(t, h.Build(BuildCfg{}))
- assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
}
func TestNodesWithURLs(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- fs := hugofs.NewMem()
+ cfg.Set("paginate", 1)
+ cfg.Set("title", "Hugo Rocks!")
+ cfg.Set("baseURL", "http://bep.is/base/")
- viper.Set("paginate", 1)
- viper.Set("title", "Hugo Rocks!")
- viper.Set("baseURL", "http://bep.is/base/")
-
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
@@ -575,13 +589,13 @@
My Section Content
`)
- h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
+ h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
require.NoError(t, h.Build(BuildCfg{}))
- assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
s := h.Sites[0]
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -39,7 +39,6 @@
"github.com/spf13/cast"
bp "github.com/spf13/hugo/bufferpool"
"github.com/spf13/hugo/source"
- "github.com/spf13/viper"
)
var (
@@ -195,8 +194,11 @@
scratch *Scratch
+ // It would be tempting to use the language set on the Site, but in they way we do
+ // multi-site processing, these values may differ during the initial page processing.
language *helpers.Language
- lang string
+
+ lang string
}
// pageInit lazy initializes different parts of the page. It is extracted
@@ -518,10 +520,11 @@
return p.Site.SourceRelativeLinkFile(ref, p)
}
}
- return helpers.RenderBytes(&helpers.RenderingContext{
+
+ return p.s.ContentSpec.RenderBytes(&helpers.RenderingContext{
Content: content, RenderTOC: true, PageFmt: p.determineMarkupType(),
- ConfigProvider: p.Language(),
- DocumentID: p.UniqueID(), DocumentName: p.Path(),
+ Cfg: p.Language(),
+ DocumentID: p.UniqueID(), DocumentName: p.Path(),
Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn})
}
@@ -532,7 +535,7 @@
if p.Language() == nil {
panic(fmt.Sprintf("nil language for %s with source lang %s", p.BaseFileName(), p.lang))
}
- p.renderingConfig = helpers.NewBlackfriday(p.Language())
+ p.renderingConfig = p.s.ContentSpec.NewBlackfriday()
if err := mapstructure.Decode(pageParam, p.renderingConfig); err != nil {
p.s.Log.FATAL.Printf("Failed to get rendering config for %s:\n%s", p.BaseFileName(), err.Error())
@@ -544,15 +547,18 @@
}
func (s *Site) newPage(filename string) *Page {
+ sp := source.NewSourceSpec(s.Cfg, s.Fs)
page := Page{
pageInit: &pageInit{},
Kind: kindFromFilename(filename),
contentType: "",
- Source: Source{File: *source.NewFile(filename)},
+ Source: Source{File: *sp.NewFile(filename)},
Keywords: []string{}, Sitemap: Sitemap{Priority: -1},
Params: make(map[string]interface{}),
translations: make(Pages, 0),
sections: sectionsFromFilename(filename),
+ Site: &s.Info,
+ s: s,
}
s.Log.DEBUG.Println("Reading from", page.File.Path())
@@ -799,7 +805,7 @@
if p.extension != "" {
return p.extension
}
- return viper.GetString("defaultExtension")
+ return p.s.Cfg.GetString("defaultExtension")
}
// AllTranslations returns all translations, including the current Page.
@@ -832,8 +838,8 @@
}
func (p *Page) shouldBuild() bool {
- return shouldBuild(viper.GetBool("buildFuture"), viper.GetBool("buildExpired"),
- viper.GetBool("buildDrafts"), p.Draft, p.PublishDate, p.ExpiryDate)
+ return shouldBuild(p.s.Cfg.GetBool("buildFuture"), p.s.Cfg.GetBool("buildExpired"),
+ p.s.Cfg.GetBool("buildDrafts"), p.Draft, p.PublishDate, p.ExpiryDate)
}
func shouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool,
@@ -886,7 +892,7 @@
func (p *Page) RelPermalink() string {
link := p.getPermalink()
- if viper.GetBool("canonifyURLs") {
+ if p.s.Cfg.GetBool("canonifyURLs") {
// replacements for relpermalink with baseURL on the form http://myhost.com/sub/ will fail later on
// have to return the URL relative from baseURL
relpath, err := helpers.GetRelativePath(link.String(), string(p.Site.BaseURL))
@@ -1047,8 +1053,8 @@
p.Draft = !*published
}
- if p.Date.IsZero() && viper.GetBool("useModTimeAsFallback") {
- fi, err := p.s.Fs.Source.Stat(filepath.Join(helpers.AbsPathify(viper.GetString("contentDir")), p.File.Path()))
+ if p.Date.IsZero() && p.s.Cfg.GetBool("useModTimeAsFallback") {
+ fi, err := p.s.Fs.Source.Stat(filepath.Join(p.s.PathSpec.AbsPathify(p.s.Cfg.GetString("contentDir")), p.File.Path()))
if err == nil {
p.Date = fi.ModTime()
}
@@ -1060,7 +1066,7 @@
if isCJKLanguage != nil {
p.isCJKLanguage = *isCJKLanguage
- } else if viper.GetBool("hasCJKLanguage") {
+ } else if p.s.Cfg.GetBool("hasCJKLanguage") {
if cjk.Match(p.rawContent) {
p.isCJKLanguage = true
} else {
@@ -1378,10 +1384,9 @@
func (p *Page) saveSource(by []byte, inpath string, safe bool) (err error) {
if !filepath.IsAbs(inpath) {
- inpath = helpers.AbsPathify(inpath)
+ inpath = p.s.PathSpec.AbsPathify(inpath)
}
p.s.Log.INFO.Println("creating", inpath)
-
if safe {
err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source)
} else {
@@ -1691,25 +1696,27 @@
if p.language != nil {
return
}
- pageLang := p.lang
+
ml := p.Site.multilingual
if ml == nil {
panic("Multilanguage not set")
}
- if pageLang == "" {
+ if p.lang == "" {
+ p.lang = ml.DefaultLang.Lang
p.language = ml.DefaultLang
return
}
- language := ml.Language(pageLang)
+ language := ml.Language(p.lang)
if language == nil {
// It can be a file named stefano.chiodino.md.
- p.s.Log.WARN.Printf("Page language (if it is that) not found in multilang setup: %s.", pageLang)
+ p.s.Log.WARN.Printf("Page language (if it is that) not found in multilang setup: %s.", p.lang)
language = ml.DefaultLang
}
p.language = language
+
})
}
@@ -1743,6 +1750,7 @@
if outfile == "" {
outfile = helpers.FilePathSeparator
}
+
if !p.shouldAddLanguagePrefix() {
return outfile
}
@@ -1795,7 +1803,4 @@
case KindTaxonomyTerm:
p.URLPath.URL = "/" + path.Join(p.sections...) + "/"
}
-
- p.s = s
-
}
--- a/hugolib/pageCache_test.go
+++ b/hugolib/pageCache_test.go
@@ -14,13 +14,15 @@
package hugolib
import (
- "github.com/stretchr/testify/assert"
"sync"
"sync/atomic"
"testing"
+
+ "github.com/stretchr/testify/assert"
)
func TestPageCache(t *testing.T) {
+ t.Parallel()
c1 := newPageCache()
changeFirst := func(p Pages) {
@@ -37,8 +39,10 @@
var testPageSets []Pages
+ s := newTestSite(t)
+
for i := 0; i < 50; i++ {
- testPageSets = append(testPageSets, createSortTestPages(i+1))
+ testPageSets = append(testPageSets, createSortTestPages(s, i+1))
}
for j := 0; j < 100; j++ {
--- a/hugolib/pageGroup_test.go
+++ b/hugolib/pageGroup_test.go
@@ -38,18 +38,19 @@
}
func preparePageGroupTestPages(t *testing.T) Pages {
+ s := newTestSite(t)
var pages Pages
- for _, s := range pageGroupTestSources {
- p, err := pageTestSite.NewPage(filepath.FromSlash(s.path))
+ for _, src := range pageGroupTestSources {
+ p, err := s.NewPage(filepath.FromSlash(src.path))
if err != nil {
- t.Fatalf("failed to prepare test page %s", s.path)
+ t.Fatalf("failed to prepare test page %s", src.path)
}
- p.Weight = s.weight
- p.Date = cast.ToTime(s.date)
- p.PublishDate = cast.ToTime(s.date)
- p.ExpiryDate = cast.ToTime(s.date)
- p.Params["custom_param"] = s.param
- p.Params["custom_date"] = cast.ToTime(s.date)
+ p.Weight = src.weight
+ p.Date = cast.ToTime(src.date)
+ p.PublishDate = cast.ToTime(src.date)
+ p.ExpiryDate = cast.ToTime(src.date)
+ p.Params["custom_param"] = src.param
+ p.Params["custom_date"] = cast.ToTime(src.date)
pages = append(pages, p)
}
return pages
@@ -56,6 +57,7 @@
}
func TestGroupByWithFieldNameArg(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: 1, Pages: Pages{pages[3], pages[4]}},
@@ -73,6 +75,7 @@
}
func TestGroupByWithMethodNameArg(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "section1", Pages: Pages{pages[0], pages[1], pages[2]}},
@@ -89,6 +92,7 @@
}
func TestGroupByWithSectionArg(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "section1", Pages: Pages{pages[0], pages[1], pages[2]}},
@@ -105,6 +109,7 @@
}
func TestGroupByInReverseOrder(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: 3, Pages: Pages{pages[0], pages[1]}},
@@ -122,6 +127,7 @@
}
func TestGroupByCalledWithEmptyPages(t *testing.T) {
+ t.Parallel()
var pages Pages
groups, err := pages.GroupBy("Weight")
if err != nil {
@@ -133,6 +139,7 @@
}
func TestGroupByCalledWithUnavailableKey(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
_, err := pages.GroupBy("UnavailableKey")
if err == nil {
@@ -157,6 +164,7 @@
}
func TestGroupByCalledWithInvalidMethod(t *testing.T) {
+ t.Parallel()
var err error
pages := preparePageGroupTestPages(t)
@@ -182,6 +190,7 @@
}
func TestReverse(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
groups1, err := pages.GroupBy("Weight", "desc")
@@ -201,6 +210,7 @@
}
func TestGroupByParam(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "bar", Pages: Pages{pages[1], pages[3]}},
@@ -218,6 +228,7 @@
}
func TestGroupByParamInReverseOrder(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "foo", Pages: Pages{pages[0], pages[2]}},
@@ -237,7 +248,8 @@
func TestGroupByParamCalledWithCapitalLetterString(t *testing.T) {
testStr := "TestString"
f := "/section1/test_capital.md"
- p, err := pageTestSite.NewPage(filepath.FromSlash(f))
+ s := newTestSite(t)
+ p, err := s.NewPage(filepath.FromSlash(f))
if err != nil {
t.Fatalf("failed to prepare test page %s", f)
}
@@ -254,6 +266,7 @@
}
func TestGroupByParamCalledWithSomeUnavailableParams(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
delete(pages[1].Params, "custom_param")
delete(pages[3].Params, "custom_param")
@@ -273,6 +286,7 @@
}
func TestGroupByParamCalledWithEmptyPages(t *testing.T) {
+ t.Parallel()
var pages Pages
groups, err := pages.GroupByParam("custom_param")
if err != nil {
@@ -284,6 +298,7 @@
}
func TestGroupByParamCalledWithUnavailableParam(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
_, err := pages.GroupByParam("unavailable_param")
if err == nil {
@@ -292,6 +307,7 @@
}
func TestGroupByDate(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}},
@@ -309,6 +325,7 @@
}
func TestGroupByDateInReverseOrder(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-01", Pages: Pages{pages[1]}},
@@ -326,6 +343,7 @@
}
func TestGroupByPublishDate(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}},
@@ -343,6 +361,7 @@
}
func TestGroupByPublishDateInReverseOrder(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-01", Pages: Pages{pages[1]}},
@@ -360,6 +379,7 @@
}
func TestGroupByPublishDateWithEmptyPages(t *testing.T) {
+ t.Parallel()
var pages Pages
groups, err := pages.GroupByPublishDate("2006-01")
if err != nil {
@@ -371,6 +391,7 @@
}
func TestGroupByExpiryDate(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}},
@@ -388,6 +409,7 @@
}
func TestGroupByParamDate(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}},
@@ -405,6 +427,7 @@
}
func TestGroupByParamDateInReverseOrder(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-01", Pages: Pages{pages[1]}},
@@ -422,6 +445,7 @@
}
func TestGroupByParamDateWithEmptyPages(t *testing.T) {
+ t.Parallel()
var pages Pages
groups, err := pages.GroupByParamDate("custom_date", "2006-01")
if err != nil {
--- a/hugolib/pageSort.go
+++ b/hugolib/pageSort.go
@@ -68,7 +68,7 @@
}
var languagePageSort = func(p1, p2 *Page) bool {
- if p1.language.Weight == p2.language.Weight {
+ if p1.Language().Weight == p2.Language().Weight {
if p1.Date.Unix() == p2.Date.Unix() {
if p1.LinkTitle() == p2.LinkTitle() {
return (p1.FullFilePath() < p2.FullFilePath())
@@ -78,15 +78,15 @@
return p1.Date.Unix() > p2.Date.Unix()
}
- if p2.language.Weight == 0 {
+ if p2.Language().Weight == 0 {
return true
}
- if p1.language.Weight == 0 {
+ if p1.Language().Weight == 0 {
return false
}
- return p1.language.Weight < p2.language.Weight
+ return p1.Language().Weight < p2.Language().Weight
}
func (ps *pageSorter) Len() int { return len(ps.pages) }
--- a/hugolib/pageSort_test.go
+++ b/hugolib/pageSort_test.go
@@ -21,20 +21,20 @@
"time"
"github.com/spf13/cast"
- "github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/source"
"github.com/stretchr/testify/assert"
)
func TestDefaultSort(t *testing.T) {
-
+ t.Parallel()
d1 := time.Now()
d2 := d1.Add(-1 * time.Hour)
d3 := d1.Add(-2 * time.Hour)
d4 := d1.Add(-3 * time.Hour)
- p := createSortTestPages(4)
+ s := newTestSite(t)
+ p := createSortTestPages(s, 4)
+
// first by weight
setSortVals([4]time.Time{d1, d2, d3, d4}, [4]string{"b", "a", "c", "d"}, [4]int{4, 3, 2, 1}, p)
p.Sort()
@@ -61,13 +61,14 @@
}
func TestSortByN(t *testing.T) {
-
+ t.Parallel()
+ s := newTestSite(t)
d1 := time.Now()
d2 := d1.Add(-2 * time.Hour)
d3 := d1.Add(-10 * time.Hour)
d4 := d1.Add(-20 * time.Hour)
- p := createSortTestPages(4)
+ p := createSortTestPages(s, 4)
for i, this := range []struct {
sortFunc func(p Pages) Pages
@@ -93,7 +94,9 @@
}
func TestLimit(t *testing.T) {
- p := createSortTestPages(10)
+ t.Parallel()
+ s := newTestSite(t)
+ p := createSortTestPages(s, 10)
firstFive := p.Limit(5)
assert.Equal(t, 5, len(firstFive))
for i := 0; i < 5; i++ {
@@ -104,7 +107,9 @@
}
func TestPageSortReverse(t *testing.T) {
- p1 := createSortTestPages(10)
+ t.Parallel()
+ s := newTestSite(t)
+ p1 := createSortTestPages(s, 10)
assert.Equal(t, 0, p1[0].fuzzyWordCount)
assert.Equal(t, 9, p1[9].fuzzyWordCount)
p2 := p1.Reverse()
@@ -115,9 +120,11 @@
}
func TestPageSortByParam(t *testing.T) {
+ t.Parallel()
var k interface{} = "arbitrary"
+ s := newTestSite(t)
- unsorted := createSortTestPages(10)
+ unsorted := createSortTestPages(s, 10)
delete(unsorted[9].Params, cast.ToString(k))
firstSetValue, _ := unsorted[0].Param(k)
@@ -143,9 +150,9 @@
}
func BenchmarkSortByWeightAndReverse(b *testing.B) {
+ s := newTestSite(b)
+ p := createSortTestPages(s, 300)
- p := createSortTestPages(300)
-
b.ResetTimer()
for i := 0; i < b.N; i++ {
p = p.ByWeight().Reverse()
@@ -169,32 +176,25 @@
pages[1].Lastmod = lastLastMod
}
-func createSortTestPages(num int) Pages {
+func createSortTestPages(s *Site, num int) Pages {
pages := make(Pages, num)
- info := newSiteInfo(siteBuilderCfg{baseURL: "http://base", language: helpers.NewDefaultLanguage()})
-
for i := 0; i < num; i++ {
- pages[i] = &Page{
- pageInit: &pageInit{},
- URLPath: URLPath{
- Section: "z",
- URL: fmt.Sprintf("http://base/x/y/p%d.html", i),
- },
- Site: &info,
- Source: Source{File: *source.NewFile(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i)))},
- Params: map[string]interface{}{
- "arbitrary": "xyz" + fmt.Sprintf("%v", 100-i),
- },
+ p := s.newPage(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i)))
+ p.Params = map[string]interface{}{
+ "arbitrary": "xyz" + fmt.Sprintf("%v", 100-i),
}
+
w := 5
if i%2 == 0 {
w = 10
}
- pages[i].fuzzyWordCount = i
- pages[i].Weight = w
- pages[i].Description = "initial"
+ p.fuzzyWordCount = i
+ p.Weight = w
+ p.Description = "initial"
+
+ pages[i] = p
}
return pages
--- a/hugolib/page_permalink_test.go
+++ b/hugolib/page_permalink_test.go
@@ -14,18 +14,18 @@
package hugolib
import (
+ "fmt"
"html/template"
"path/filepath"
"testing"
- "github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/source"
- "github.com/spf13/viper"
+ "github.com/stretchr/testify/require"
+
+ "github.com/spf13/hugo/deps"
)
-// TODO(bep) globals test siteinfo
-func _TestPermalink(t *testing.T) {
- testCommonResetState()
+func TestPermalink(t *testing.T) {
+ t.Parallel()
tests := []struct {
file string
@@ -50,44 +50,46 @@
{"x/y/z/boofar.md", "", "boofar", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"},
{"x/y/z/boofar.md", "http://barnew/", "", "", true, false, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"},
{"x/y/z/boofar.md", "http://barnew/", "boofar", "", true, false, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"},
- {"x/y/z/boofar.md", "http://barnew/boo/", "boofar", "", true, false, "http://barnew/boo/x/y/z/boofar.html", "/boo/x/y/z/boofar.html"},
- {"x/y/z/boofar.md", "http://barnew/boo/", "boofar", "", false, true, "http://barnew/boo/x/y/z/boofar/", "/x/y/z/boofar/"},
- {"x/y/z/boofar.md", "http://barnew/boo/", "boofar", "", false, false, "http://barnew/boo/x/y/z/boofar/", "/boo/x/y/z/boofar/"},
- {"x/y/z/boofar.md", "http://barnew/boo/", "boofar", "", true, true, "http://barnew/boo/x/y/z/boofar.html", "/x/y/z/boofar.html"},
- {"x/y/z/boofar.md", "http://barnew/boo", "boofar", "", true, true, "http://barnew/boo/x/y/z/boofar.html", "/x/y/z/boofar.html"},
+ {"x/y/z/boofar.md", "http://barnew/boo/", "booslug", "", true, false, "http://barnew/boo/x/y/z/booslug.html", "/boo/x/y/z/booslug.html"},
+ {"x/y/z/boofar.md", "http://barnew/boo/", "booslug", "", false, true, "http://barnew/boo/x/y/z/booslug/", "/x/y/z/booslug/"},
+ {"x/y/z/boofar.md", "http://barnew/boo/", "booslug", "", false, false, "http://barnew/boo/x/y/z/booslug/", "/boo/x/y/z/booslug/"},
+ {"x/y/z/boofar.md", "http://barnew/boo/", "booslug", "", true, true, "http://barnew/boo/x/y/z/booslug.html", "/x/y/z/booslug.html"},
+ {"x/y/z/boofar.md", "http://barnew/boo", "booslug", "", true, true, "http://barnew/boo/x/y/z/booslug.html", "/x/y/z/booslug.html"},
// test URL overrides
{"x/y/z/boofar.md", "", "", "/z/y/q/", false, false, "/z/y/q/", "/z/y/q/"},
}
- viper.Set("defaultExtension", "html")
for i, test := range tests {
- viper.Set("uglyURLs", test.uglyURLs)
- viper.Set("canonifyURLs", test.canonifyURLs)
- info := newSiteInfo(siteBuilderCfg{baseURL: string(test.base), language: helpers.NewDefaultLanguage()})
- p := &Page{
- pageInit: &pageInit{},
- Kind: KindPage,
- URLPath: URLPath{
- Section: "z",
- URL: test.url,
- },
- Site: &info,
- Source: Source{File: *source.NewFile(filepath.FromSlash(test.file))},
- }
+ cfg, fs := newTestCfg()
- if test.slug != "" {
- p.update(map[string]interface{}{
- "slug": test.slug,
- })
- }
+ cfg.Set("defaultExtension", "html")
+ cfg.Set("uglyURLs", test.uglyURLs)
+ cfg.Set("canonifyURLs", test.canonifyURLs)
+ cfg.Set("baseURL", test.base)
+
+ pageContent := fmt.Sprintf(`---
+title: Page
+slug: %q
+url: %q
+---
+Content
+`, test.slug, test.url)
+
+ writeSource(t, fs, filepath.Join("content", filepath.FromSlash(test.file)), pageContent)
+
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
+ require.Len(t, s.RegularPages, 1)
+
+ p := s.RegularPages[0]
+
u := p.Permalink()
expected := test.expectedAbs
if u != expected {
- t.Errorf("Test %d: Expected abs url: %s, got: %s", i, expected, u)
+ t.Fatalf("[%d] Expected abs url: %s, got: %s", i, expected, u)
}
u = p.RelPermalink()
@@ -94,7 +96,7 @@
expected = test.expectedRel
if u != expected {
- t.Errorf("Test %d: Expected rel url: %s, got: %s", i, expected, u)
+ t.Errorf("[%d] Expected rel url: %s, got: %s", i, expected, u)
}
}
}
--- a/hugolib/page_taxonomy_test.go
+++ b/hugolib/page_taxonomy_test.go
@@ -57,6 +57,7 @@
TOML Front Matter with tags and categories`
func TestParseTaxonomies(t *testing.T) {
+ t.Parallel()
for _, test := range []string{pageTomlWithTaxonomies,
pageJSONWithTaxonomies,
pageYamlWithTaxonomiesA,
@@ -64,7 +65,8 @@
pageYamlWithTaxonomiesC,
} {
- p, _ := pageTestSite.NewPage("page/with/taxonomy")
+ s := newTestSite(t)
+ p, _ := s.NewPage("page/with/taxonomy")
_, err := p.ReadFrom(strings.NewReader(test))
if err != nil {
t.Fatalf("Failed parsing %q: %s", test, err)
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -28,8 +28,6 @@
"github.com/spf13/cast"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/hugofs"
- "github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -467,13 +465,6 @@
Hi.
`
-func init() {
- testCommonResetState()
- pageTestSite, _ = NewSiteDefaultLang()
-}
-
-var pageTestSite *Site
-
func checkError(t *testing.T, err error, expected string) {
if err == nil {
t.Fatalf("err is nil. Expected: %s", expected)
@@ -484,8 +475,9 @@
}
func TestDegenerateEmptyPageZeroLengthName(t *testing.T) {
-
- _, err := pageTestSite.NewPage("")
+ t.Parallel()
+ s := newTestSite(t)
+ _, err := s.NewPage("")
if err == nil {
t.Fatalf("A zero length page name must return an error")
}
@@ -494,7 +486,9 @@
}
func TestDegenerateEmptyPage(t *testing.T) {
- _, err := pageTestSite.NewPageFrom(strings.NewReader(emptyPage), "test")
+ t.Parallel()
+ s := newTestSite(t)
+ _, err := s.NewPageFrom(strings.NewReader(emptyPage), "test")
if err != nil {
t.Fatalf("Empty files should not trigger an error. Should be able to touch a file while watching without erroring out.")
}
@@ -611,19 +605,17 @@
continue
}
- testCommonResetState()
+ cfg, fs := newTestCfg()
- fs := hugofs.NewMem()
-
if settings != nil {
for k, v := range settings {
- viper.Set(k, v)
+ cfg.Set(k, v)
}
}
contentDir := "content"
- if s := viper.GetString("contentDir"); s != "" {
+ if s := cfg.GetString("contentDir"); s != "" {
contentDir = s
}
@@ -637,7 +629,7 @@
writeSource(t, fs, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1])
}
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, len(pageSources))
@@ -648,7 +640,7 @@
}
func TestCreateNewPage(t *testing.T) {
-
+ t.Parallel()
assertFunc := func(t *testing.T, ext string, pages Pages) {
p := pages[0]
@@ -671,7 +663,7 @@
}
func TestSplitSummaryAndContent(t *testing.T) {
-
+ t.Parallel()
for i, this := range []struct {
markup string
content string
@@ -727,7 +719,7 @@
}
func TestPageWithDelimiter(t *testing.T) {
-
+ t.Parallel()
assertFunc := func(t *testing.T, ext string, pages Pages) {
p := pages[0]
checkPageTitle(t, p, "Simple")
@@ -743,14 +735,12 @@
// Issue #1076
func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) {
+ t.Parallel()
+ cfg, fs := newTestCfg()
- testCommonResetState()
-
- fs := hugofs.NewMem()
-
writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, 1)
@@ -767,10 +757,9 @@
// Issue #2601
func TestPageRawContent(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ cfg, fs := newTestCfg()
- fs := hugofs.NewMem()
-
writeSource(t, fs, filepath.Join("content", "raw.md"), `---
title: Raw
---
@@ -778,7 +767,7 @@
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .RawContent }}`)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, 1)
p := s.RegularPages[0]
@@ -788,7 +777,7 @@
}
func TestPageWithShortCodeInSummary(t *testing.T) {
-
+ t.Parallel()
assertFunc := func(t *testing.T, ext string, pages Pages) {
p := pages[0]
checkPageTitle(t, p, "Simple")
@@ -802,7 +791,7 @@
}
func TestPageWithEmbeddedScriptTag(t *testing.T) {
-
+ t.Parallel()
assertFunc := func(t *testing.T, ext string, pages Pages) {
p := pages[0]
if ext == "ad" || ext == "rst" {
@@ -816,12 +805,12 @@
}
func TestPageWithAdditionalExtension(t *testing.T) {
+ t.Parallel()
+ cfg, fs := newTestCfg()
- fs := hugofs.NewMem()
-
writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithAdditionalExtension)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, 1)
@@ -832,11 +821,11 @@
func TestTableOfContents(t *testing.T) {
- fs := hugofs.NewMem()
+ cfg, fs := newTestCfg()
writeSource(t, fs, filepath.Join("content", "tocpage.md"), pageWithToC)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, 1)
@@ -847,7 +836,7 @@
}
func TestPageWithMoreTag(t *testing.T) {
-
+ t.Parallel()
assertFunc := func(t *testing.T, ext string, pages Pages) {
p := pages[0]
checkPageTitle(t, p, "Simple")
@@ -862,11 +851,12 @@
}
func TestPageWithDate(t *testing.T) {
- fs := hugofs.NewMem()
+ t.Parallel()
+ cfg, fs := newTestCfg()
writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageRFC3339Date)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, 1)
@@ -877,8 +867,7 @@
}
func TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
assertFunc := func(t *testing.T, ext string, pages Pages) {
p := pages[0]
if p.WordCount() != 8 {
@@ -890,6 +879,7 @@
}
func TestWordCountWithAllCJKRunesHasCJKLanguage(t *testing.T) {
+ t.Parallel()
settings := map[string]interface{}{"hasCJKLanguage": true}
assertFunc := func(t *testing.T, ext string, pages Pages) {
@@ -902,6 +892,7 @@
}
func TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) {
+ t.Parallel()
settings := map[string]interface{}{"hasCJKLanguage": true}
assertFunc := func(t *testing.T, ext string, pages Pages) {
@@ -920,8 +911,10 @@
}
func TestWordCountWithIsCJKLanguageFalse(t *testing.T) {
- testCommonResetState()
- viper.Set("hasCJKLanguage", true)
+ t.Parallel()
+ settings := map[string]interface{}{
+ "hasCJKLanguage": true,
+ }
assertFunc := func(t *testing.T, ext string, pages Pages) {
p := pages[0]
@@ -935,12 +928,12 @@
}
}
- testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithIsCJKLanguageFalse)
+ testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithIsCJKLanguageFalse)
}
func TestWordCount(t *testing.T) {
-
+ t.Parallel()
assertFunc := func(t *testing.T, ext string, pages Pages) {
p := pages[0]
if p.WordCount() != 483 {
@@ -962,6 +955,7 @@
}
func TestCreatePage(t *testing.T) {
+ t.Parallel()
var tests = []struct {
r string
}{
@@ -972,7 +966,8 @@
}
for _, test := range tests {
- p, _ := pageTestSite.NewPage("page")
+ s := newTestSite(t)
+ p, _ := s.NewPage("page")
if _, err := p.ReadFrom(strings.NewReader(test.r)); err != nil {
t.Errorf("Unable to parse page: %s", err)
}
@@ -980,6 +975,7 @@
}
func TestDegenerateInvalidFrontMatterShortDelim(t *testing.T) {
+ t.Parallel()
var tests = []struct {
r string
err string
@@ -987,8 +983,8 @@
{invalidFrontmatterShortDelimEnding, "unable to read frontmatter at filepos 45: EOF"},
}
for _, test := range tests {
-
- p, _ := pageTestSite.NewPage("invalid/front/matter/short/delim")
+ s := newTestSite(t)
+ p, _ := s.NewPage("invalid/front/matter/short/delim")
_, err := p.ReadFrom(strings.NewReader(test.r))
checkError(t, err, test.err)
}
@@ -995,6 +991,7 @@
}
func TestShouldRenderContent(t *testing.T) {
+ t.Parallel()
var tests = []struct {
text string
render bool
@@ -1010,8 +1007,8 @@
}
for _, test := range tests {
-
- p, _ := pageTestSite.NewPage("render/front/matter")
+ s := newTestSite(t)
+ p, _ := s.NewPage("render/front/matter")
_, err := p.ReadFrom(strings.NewReader(test.text))
p = pageMust(p, err)
if p.IsRenderable() != test.render {
@@ -1022,13 +1019,15 @@
// Issue #768
func TestCalendarParamsVariants(t *testing.T) {
- pageJSON, _ := pageTestSite.NewPage("test/fileJSON.md")
+ t.Parallel()
+ s := newTestSite(t)
+ pageJSON, _ := s.NewPage("test/fileJSON.md")
_, _ = pageJSON.ReadFrom(strings.NewReader(pageWithCalendarJSONFrontmatter))
- pageYAML, _ := pageTestSite.NewPage("test/fileYAML.md")
+ pageYAML, _ := s.NewPage("test/fileYAML.md")
_, _ = pageYAML.ReadFrom(strings.NewReader(pageWithCalendarYAMLFrontmatter))
- pageTOML, _ := pageTestSite.NewPage("test/fileTOML.md")
+ pageTOML, _ := s.NewPage("test/fileTOML.md")
_, _ = pageTOML.ReadFrom(strings.NewReader(pageWithCalendarTOMLFrontmatter))
assert.True(t, compareObjects(pageJSON.Params, pageYAML.Params))
@@ -1037,7 +1036,9 @@
}
func TestDifferentFrontMatterVarTypes(t *testing.T) {
- page, _ := pageTestSite.NewPage("test/file1.md")
+ t.Parallel()
+ s := newTestSite(t)
+ page, _ := s.NewPage("test/file1.md")
_, _ = page.ReadFrom(strings.NewReader(pageWithVariousFrontmatterTypes))
dateval, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
@@ -1066,7 +1067,9 @@
}
func TestDegenerateInvalidFrontMatterLeadingWhitespace(t *testing.T) {
- p, _ := pageTestSite.NewPage("invalid/front/matter/leading/ws")
+ t.Parallel()
+ s := newTestSite(t)
+ p, _ := s.NewPage("invalid/front/matter/leading/ws")
_, err := p.ReadFrom(strings.NewReader(invalidFrontmatterLadingWs))
if err != nil {
t.Fatalf("Unable to parse front matter given leading whitespace: %s", err)
@@ -1074,7 +1077,9 @@
}
func TestSectionEvaluation(t *testing.T) {
- page, _ := pageTestSite.NewPage(filepath.FromSlash("blue/file1.md"))
+ t.Parallel()
+ s := newTestSite(t)
+ page, _ := s.NewPage(filepath.FromSlash("blue/file1.md"))
page.ReadFrom(strings.NewReader(simplePage))
if page.Section() != "blue" {
t.Errorf("Section should be %s, got: %s", "blue", page.Section())
@@ -1086,6 +1091,7 @@
}
func TestLayoutOverride(t *testing.T) {
+ t.Parallel()
var (
pathContentTwoDir = filepath.Join("content", "dub", "sub", "file1.md")
pathContentOneDir = filepath.Join("content", "gub", "file1.md")
@@ -1119,7 +1125,8 @@
{simplePageTypeLayout, pathNoDirectory, L("barfoo/buzfoo.html", "_default/buzfoo.html")},
}
for _, test := range tests {
- p, _ := pageTestSite.NewPage(test.path)
+ s := newTestSite(t)
+ p, _ := s.NewPage(test.path)
_, err := p.ReadFrom(strings.NewReader(test.content))
if err != nil {
t.Fatalf("Unable to parse content:\n%s\n", test.content)
@@ -1135,6 +1142,7 @@
}
func TestSliceToLower(t *testing.T) {
+ t.Parallel()
tests := []struct {
value []string
expected []string
@@ -1155,10 +1163,9 @@
}
func TestPagePaths(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
- viper.Set("defaultExtension", "html")
- siteParmalinksSetting := PermalinkOverrides{
+ siteParmalinksSetting := map[string]string{
"post": ":year/:month/:day/:title/",
}
@@ -1168,34 +1175,41 @@
hasPermalink bool
expected string
}{
- {simplePage, "content/post/x.md", false, "content/post/x.html"},
- {simplePageWithURL, "content/post/x.md", false, "simple/url/index.html"},
- {simplePageWithSlug, "content/post/x.md", false, "content/post/simple-slug.html"},
- {simplePageWithDate, "content/post/x.md", true, "2013/10/15/simple/index.html"},
- {UTF8Page, "content/post/x.md", false, "content/post/x.html"},
- {UTF8PageWithURL, "content/post/x.md", false, "ラーメン/url/index.html"},
- {UTF8PageWithSlug, "content/post/x.md", false, "content/post/ラーメン-slug.html"},
- {UTF8PageWithDate, "content/post/x.md", true, "2013/10/15/ラーメン/index.html"},
+ {simplePage, "post/x.md", false, "post/x.html"},
+ {simplePageWithURL, "post/x.md", false, "simple/url/index.html"},
+ {simplePageWithSlug, "post/x.md", false, "post/simple-slug.html"},
+ {simplePageWithDate, "post/x.md", true, "2013/10/15/simple/index.html"},
+ {UTF8Page, "post/x.md", false, "post/x.html"},
+ {UTF8PageWithURL, "post/x.md", false, "ラーメン/url/index.html"},
+ {UTF8PageWithSlug, "post/x.md", false, "post/ラーメン-slug.html"},
+ {UTF8PageWithDate, "post/x.md", true, "2013/10/15/ラーメン/index.html"},
}
- for _, test := range tests {
- p, _ := pageTestSite.NewPageFrom(strings.NewReader(test.content), filepath.FromSlash(test.path))
- info := newSiteInfo(siteBuilderCfg{language: helpers.NewDefaultLanguage()})
- p.Site = &info
+ for i, test := range tests {
+ cfg, fs := newTestCfg()
+ cfg.Set("defaultExtension", "html")
+
if test.hasPermalink {
- p.Site.Permalinks = siteParmalinksSetting
+ cfg.Set("permalinks", siteParmalinksSetting)
}
+ writeSource(t, fs, filepath.Join("content", filepath.FromSlash(test.path)), test.content)
+
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
+ require.Len(t, s.RegularPages, 1)
+
+ p := s.RegularPages[0]
+
expectedTargetPath := filepath.FromSlash(test.expected)
expectedFullFilePath := filepath.FromSlash(test.path)
if p.TargetPath() != expectedTargetPath {
- t.Errorf("%s => TargetPath expected: '%s', got: '%s'", test.content, expectedTargetPath, p.TargetPath())
+ t.Fatalf("[%d] %s => TargetPath expected: '%s', got: '%s'", i, test.content, expectedTargetPath, p.TargetPath())
}
if p.FullFilePath() != expectedFullFilePath {
- t.Errorf("%s => FullFilePath expected: '%s', got: '%s'", test.content, expectedFullFilePath, p.FullFilePath())
+ t.Fatalf("[%d] %s => FullFilePath expected: '%s', got: '%s'", i, test.content, expectedFullFilePath, p.FullFilePath())
}
}
}
@@ -1209,7 +1223,9 @@
`
func TestDraftAndPublishedFrontMatterError(t *testing.T) {
- _, err := pageTestSite.NewPageFrom(strings.NewReader(pageWithDraftAndPublished), "content/post/broken.md")
+ t.Parallel()
+ s := newTestSite(t)
+ _, err := s.NewPageFrom(strings.NewReader(pageWithDraftAndPublished), "content/post/broken.md")
if err != ErrHasDraftAndPublished {
t.Errorf("expected ErrHasDraftAndPublished, was %#v", err)
}
@@ -1229,7 +1245,9 @@
`
func TestPublishedFrontMatter(t *testing.T) {
- p, err := pageTestSite.NewPageFrom(strings.NewReader(pagesWithPublishedFalse), "content/post/broken.md")
+ t.Parallel()
+ s := newTestSite(t)
+ p, err := s.NewPageFrom(strings.NewReader(pagesWithPublishedFalse), "content/post/broken.md")
if err != nil {
t.Fatalf("err during parse: %s", err)
}
@@ -1236,7 +1254,7 @@
if !p.Draft {
t.Errorf("expected true, got %t", p.Draft)
}
- p, err = pageTestSite.NewPageFrom(strings.NewReader(pageWithPublishedTrue), "content/post/broken.md")
+ p, err = s.NewPageFrom(strings.NewReader(pageWithPublishedTrue), "content/post/broken.md")
if err != nil {
t.Fatalf("err during parse: %s", err)
}
@@ -1261,10 +1279,12 @@
}
func TestDraft(t *testing.T) {
+ t.Parallel()
+ s := newTestSite(t)
for _, draft := range []bool{true, false} {
for i, templ := range pagesDraftTemplate {
pageContent := fmt.Sprintf(templ, draft)
- p, err := pageTestSite.NewPageFrom(strings.NewReader(pageContent), "content/post/broken.md")
+ p, err := s.NewPageFrom(strings.NewReader(pageContent), "content/post/broken.md")
if err != nil {
t.Fatalf("err during parse: %s", err)
}
@@ -1314,6 +1334,8 @@
}
func TestPageParams(t *testing.T) {
+ t.Parallel()
+ s := newTestSite(t)
want := map[string]interface{}{
"tags": []string{"hugo", "web"},
// Issue #2752
@@ -1324,7 +1346,7 @@
}
for i, c := range pagesParamsTemplate {
- p, err := pageTestSite.NewPageFrom(strings.NewReader(c), "content/post/params.md")
+ p, err := s.NewPageFrom(strings.NewReader(c), "content/post/params.md")
require.NoError(t, err, "err during parse", "#%d", i)
assert.Equal(t, want, p.Params, "#%d", i)
}
@@ -1331,6 +1353,8 @@
}
func TestPageSimpleMethods(t *testing.T) {
+ t.Parallel()
+ s := newTestSite(t)
for i, this := range []struct {
assertFunc func(p *Page) bool
}{
@@ -1340,7 +1364,7 @@
{func(p *Page) bool { return strings.Join(p.PlainWords(), " ") == "Do Be Do Be Do" }},
} {
- p, _ := pageTestSite.NewPage("Test")
+ p, _ := s.NewPage("Test")
p.Content = "<h1>Do Be Do Be Do</h1>"
if !this.assertFunc(p) {
t.Errorf("[%d] Page method error", i)
@@ -1349,6 +1373,7 @@
}
func TestIndexPageSimpleMethods(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
assertFunc func(n *Page) bool
}{
@@ -1371,7 +1396,7 @@
}
func TestKind(t *testing.T) {
-
+ t.Parallel()
// Add tests for these constants to make sure they don't change
require.Equal(t, "page", KindPage)
require.Equal(t, "home", KindHome)
@@ -1382,13 +1407,14 @@
}
func TestChompBOM(t *testing.T) {
+ t.Parallel()
const utf8BOM = "\xef\xbb\xbf"
- fs := hugofs.NewMem()
+ cfg, fs := newTestCfg()
writeSource(t, fs, filepath.Join("content", "simple.md"), utf8BOM+simplePage)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, 1)
@@ -1423,6 +1449,7 @@
}
func TestShouldBuild(t *testing.T) {
+ t.Parallel()
var past = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
var future = time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC)
var zero = time.Time{}
@@ -1469,12 +1496,13 @@
}
func BenchmarkParsePage(b *testing.B) {
+ s := newTestSite(b)
f, _ := os.Open("testdata/redis.cn.md")
var buf bytes.Buffer
buf.ReadFrom(f)
b.ResetTimer()
for i := 0; i < b.N; i++ {
- page, _ := pageTestSite.NewPage("bench")
+ page, _ := s.NewPage("bench")
page.ReadFrom(bytes.NewReader(buf.Bytes()))
}
}
--- a/hugolib/page_time_integration_test.go
+++ b/hugolib/page_time_integration_test.go
@@ -89,7 +89,9 @@
)
func TestDegenerateDateFrontMatter(t *testing.T) {
- p, _ := pageTestSite.NewPageFrom(strings.NewReader(pageWithInvalidDate), "page/with/invalid/date")
+ t.Parallel()
+ s := newTestSite(t)
+ p, _ := s.NewPageFrom(strings.NewReader(pageWithInvalidDate), "page/with/invalid/date")
if p.Date != *new(time.Time) {
t.Fatalf("Date should be set to time.Time zero value. Got: %s", p.Date)
}
@@ -96,6 +98,8 @@
}
func TestParsingDateInFrontMatter(t *testing.T) {
+ t.Parallel()
+ s := newTestSite(t)
tests := []struct {
buf string
dt string
@@ -131,7 +135,7 @@
if e != nil {
t.Fatalf("Unable to parse date time (RFC3339) for running the test: %s", e)
}
- p, err := pageTestSite.NewPageFrom(strings.NewReader(test.buf), "page/with/date")
+ p, err := s.NewPageFrom(strings.NewReader(test.buf), "page/with/date")
if err != nil {
t.Fatalf("Expected to be able to parse page.")
}
--- a/hugolib/pagesPrevNext_test.go
+++ b/hugolib/pagesPrevNext_test.go
@@ -35,6 +35,7 @@
}
func TestPrev(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
assert.Equal(t, pages.Prev(pages[0]), pages[4])
assert.Equal(t, pages.Prev(pages[1]), pages[0])
@@ -42,6 +43,7 @@
}
func TestNext(t *testing.T) {
+ t.Parallel()
pages := preparePageGroupTestPages(t)
assert.Equal(t, pages.Next(pages[0]), pages[1])
assert.Equal(t, pages.Next(pages[1]), pages[2])
@@ -49,16 +51,17 @@
}
func prepareWeightedPagesPrevNext(t *testing.T) WeightedPages {
+ s := newTestSite(t)
w := WeightedPages{}
- for _, s := range pagePNTestSources {
- p, err := pageTestSite.NewPage(s.path)
+ for _, src := range pagePNTestSources {
+ p, err := s.NewPage(src.path)
if err != nil {
- t.Fatalf("failed to prepare test page %s", s.path)
+ t.Fatalf("failed to prepare test page %s", src.path)
}
- p.Weight = s.weight
- p.Date = cast.ToTime(s.date)
- p.PublishDate = cast.ToTime(s.date)
+ p.Weight = src.weight
+ p.Date = cast.ToTime(src.date)
+ p.PublishDate = cast.ToTime(src.date)
w = append(w, WeightedPage{p.Weight, p})
}
@@ -67,6 +70,7 @@
}
func TestWeightedPagesPrev(t *testing.T) {
+ t.Parallel()
w := prepareWeightedPagesPrevNext(t)
assert.Equal(t, w.Prev(w[0].Page), w[4].Page)
assert.Equal(t, w.Prev(w[1].Page), w[0].Page)
@@ -74,6 +78,7 @@
}
func TestWeightedPagesNext(t *testing.T) {
+ t.Parallel()
w := prepareWeightedPagesPrevNext(t)
assert.Equal(t, w.Next(w[0].Page), w[1].Page)
assert.Equal(t, w.Next(w[1].Page), w[2].Page)
--- a/hugolib/pagination.go
+++ b/hugolib/pagination.go
@@ -21,6 +21,8 @@
"path"
"reflect"
+ "github.com/spf13/hugo/config"
+
"github.com/spf13/cast"
"github.com/spf13/hugo/helpers"
)
@@ -266,7 +268,7 @@
if !p.IsNode() {
return nil, fmt.Errorf("Paginators not supported for pages of type %q (%q)", p.Kind, p.Title)
}
- pagerSize, err := resolvePagerSize(options...)
+ pagerSize, err := resolvePagerSize(p.s.Cfg, options...)
if err != nil {
return nil, err
@@ -310,7 +312,7 @@
return nil, fmt.Errorf("Paginators not supported for pages of type %q (%q)", p.Kind, p.Title)
}
- pagerSize, err := resolvePagerSize(options...)
+ pagerSize, err := resolvePagerSize(p.s.Cfg, options...)
if err != nil {
return nil, err
@@ -353,9 +355,9 @@
return p.paginator, nil
}
-func resolvePagerSize(options ...interface{}) (int, error) {
+func resolvePagerSize(cfg config.Provider, options ...interface{}) (int, error) {
if len(options) == 0 {
- return helpers.Config().GetInt("paginate"), nil
+ return cfg.GetInt("paginate"), nil
}
if len(options) > 1 {
--- a/hugolib/pagination_test.go
+++ b/hugolib/pagination_test.go
@@ -20,46 +20,44 @@
"testing"
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/hugofs"
- "github.com/spf13/hugo/source"
- "github.com/spf13/viper"
- "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSplitPages(t *testing.T) {
+ t.Parallel()
+ s := newTestSite(t)
- pages := createTestPages(21)
+ pages := createTestPages(s, 21)
chunks := splitPages(pages, 5)
- assert.Equal(t, 5, len(chunks))
+ require.Equal(t, 5, len(chunks))
for i := 0; i < 4; i++ {
- assert.Equal(t, 5, chunks[i].Len())
+ require.Equal(t, 5, chunks[i].Len())
}
lastChunk := chunks[4]
- assert.Equal(t, 1, lastChunk.Len())
+ require.Equal(t, 1, lastChunk.Len())
}
func TestSplitPageGroups(t *testing.T) {
-
- pages := createTestPages(21)
+ t.Parallel()
+ s := newTestSite(t)
+ pages := createTestPages(s, 21)
groups, _ := pages.GroupBy("Weight", "desc")
chunks := splitPageGroups(groups, 5)
- assert.Equal(t, 5, len(chunks))
+ require.Equal(t, 5, len(chunks))
firstChunk := chunks[0]
// alternate weight 5 and 10
if groups, ok := firstChunk.(PagesGroup); ok {
- assert.Equal(t, 5, groups.Len())
+ require.Equal(t, 5, groups.Len())
for _, pg := range groups {
// first group 10 in weight
- assert.Equal(t, 10, pg.Key)
+ require.Equal(t, 10, pg.Key)
for _, p := range pg.Pages {
- assert.True(t, p.fuzzyWordCount%2 == 0) // magic test
+ require.True(t, p.fuzzyWordCount%2 == 0) // magic test
}
}
} else {
@@ -69,12 +67,12 @@
lastChunk := chunks[4]
if groups, ok := lastChunk.(PagesGroup); ok {
- assert.Equal(t, 1, groups.Len())
+ require.Equal(t, 1, groups.Len())
for _, pg := range groups {
// last should have 5 in weight
- assert.Equal(t, 5, pg.Key)
+ require.Equal(t, 5, pg.Key)
for _, p := range pg.Pages {
- assert.True(t, p.fuzzyWordCount%2 != 0) // magic test
+ require.True(t, p.fuzzyWordCount%2 != 0) // magic test
}
}
} else {
@@ -84,7 +82,9 @@
}
func TestPager(t *testing.T) {
- pages := createTestPages(21)
+ t.Parallel()
+ s := newTestSite(t)
+ pages := createTestPages(s, 21)
groups, _ := pages.GroupBy("Weight", "desc")
urlFactory := func(page int) string {
@@ -92,25 +92,25 @@
}
_, err := newPaginatorFromPages(pages, -1, urlFactory)
- assert.NotNil(t, err)
+ require.NotNil(t, err)
_, err = newPaginatorFromPageGroups(groups, -1, urlFactory)
- assert.NotNil(t, err)
+ require.NotNil(t, err)
pag, err := newPaginatorFromPages(pages, 5, urlFactory)
- assert.Nil(t, err)
+ require.Nil(t, err)
doTestPages(t, pag)
first := pag.Pagers()[0].First()
- assert.Equal(t, "Pager 1", first.String())
- assert.NotEmpty(t, first.Pages())
- assert.Empty(t, first.PageGroups())
+ require.Equal(t, "Pager 1", first.String())
+ require.NotEmpty(t, first.Pages())
+ require.Empty(t, first.PageGroups())
pag, err = newPaginatorFromPageGroups(groups, 5, urlFactory)
- assert.Nil(t, err)
+ require.Nil(t, err)
doTestPages(t, pag)
first = pag.Pagers()[0].First()
- assert.NotEmpty(t, first.PageGroups())
- assert.Empty(t, first.Pages())
+ require.NotEmpty(t, first.PageGroups())
+ require.Empty(t, first.Pages())
}
@@ -118,38 +118,40 @@
paginatorPages := paginator.Pagers()
- assert.Equal(t, 5, len(paginatorPages))
- assert.Equal(t, 21, paginator.TotalNumberOfElements())
- assert.Equal(t, 5, paginator.PageSize())
- assert.Equal(t, 5, paginator.TotalPages())
+ require.Equal(t, 5, len(paginatorPages))
+ require.Equal(t, 21, paginator.TotalNumberOfElements())
+ require.Equal(t, 5, paginator.PageSize())
+ require.Equal(t, 5, paginator.TotalPages())
first := paginatorPages[0]
- assert.Equal(t, template.HTML("page/1/"), first.URL())
- assert.Equal(t, first, first.First())
- assert.True(t, first.HasNext())
- assert.Equal(t, paginatorPages[1], first.Next())
- assert.False(t, first.HasPrev())
- assert.Nil(t, first.Prev())
- assert.Equal(t, 5, first.NumberOfElements())
- assert.Equal(t, 1, first.PageNumber())
+ require.Equal(t, template.HTML("page/1/"), first.URL())
+ require.Equal(t, first, first.First())
+ require.True(t, first.HasNext())
+ require.Equal(t, paginatorPages[1], first.Next())
+ require.False(t, first.HasPrev())
+ require.Nil(t, first.Prev())
+ require.Equal(t, 5, first.NumberOfElements())
+ require.Equal(t, 1, first.PageNumber())
third := paginatorPages[2]
- assert.True(t, third.HasNext())
- assert.True(t, third.HasPrev())
- assert.Equal(t, paginatorPages[1], third.Prev())
+ require.True(t, third.HasNext())
+ require.True(t, third.HasPrev())
+ require.Equal(t, paginatorPages[1], third.Prev())
last := paginatorPages[4]
- assert.Equal(t, template.HTML("page/5/"), last.URL())
- assert.Equal(t, last, last.Last())
- assert.False(t, last.HasNext())
- assert.Nil(t, last.Next())
- assert.True(t, last.HasPrev())
- assert.Equal(t, 1, last.NumberOfElements())
- assert.Equal(t, 5, last.PageNumber())
+ require.Equal(t, template.HTML("page/5/"), last.URL())
+ require.Equal(t, last, last.Last())
+ require.False(t, last.HasNext())
+ require.Nil(t, last.Next())
+ require.True(t, last.HasPrev())
+ require.Equal(t, 1, last.NumberOfElements())
+ require.Equal(t, 5, last.PageNumber())
}
func TestPagerNoPages(t *testing.T) {
- pages := createTestPages(0)
+ t.Parallel()
+ s := newTestSite(t)
+ pages := createTestPages(s, 0)
groups, _ := pages.GroupBy("Weight", "desc")
urlFactory := func(page int) string {
@@ -160,15 +162,15 @@
doTestPagerNoPages(t, paginator)
first := paginator.Pagers()[0].First()
- assert.Empty(t, first.PageGroups())
- assert.Empty(t, first.Pages())
+ require.Empty(t, first.PageGroups())
+ require.Empty(t, first.Pages())
paginator, _ = newPaginatorFromPageGroups(groups, 5, urlFactory)
doTestPagerNoPages(t, paginator)
first = paginator.Pagers()[0].First()
- assert.Empty(t, first.PageGroups())
- assert.Empty(t, first.Pages())
+ require.Empty(t, first.PageGroups())
+ require.Empty(t, first.Pages())
}
@@ -175,39 +177,40 @@
func doTestPagerNoPages(t *testing.T, paginator *paginator) {
paginatorPages := paginator.Pagers()
- assert.Equal(t, 1, len(paginatorPages))
- assert.Equal(t, 0, paginator.TotalNumberOfElements())
- assert.Equal(t, 5, paginator.PageSize())
- assert.Equal(t, 0, paginator.TotalPages())
+ require.Equal(t, 1, len(paginatorPages))
+ require.Equal(t, 0, paginator.TotalNumberOfElements())
+ require.Equal(t, 5, paginator.PageSize())
+ require.Equal(t, 0, paginator.TotalPages())
// pageOne should be nothing but the first
pageOne := paginatorPages[0]
- assert.NotNil(t, pageOne.First())
- assert.False(t, pageOne.HasNext())
- assert.False(t, pageOne.HasPrev())
- assert.Nil(t, pageOne.Next())
- assert.Equal(t, 1, len(pageOne.Pagers()))
- assert.Equal(t, 0, pageOne.Pages().Len())
- assert.Equal(t, 0, pageOne.NumberOfElements())
- assert.Equal(t, 0, pageOne.TotalNumberOfElements())
- assert.Equal(t, 0, pageOne.TotalPages())
- assert.Equal(t, 1, pageOne.PageNumber())
- assert.Equal(t, 5, pageOne.PageSize())
+ require.NotNil(t, pageOne.First())
+ require.False(t, pageOne.HasNext())
+ require.False(t, pageOne.HasPrev())
+ require.Nil(t, pageOne.Next())
+ require.Equal(t, 1, len(pageOne.Pagers()))
+ require.Equal(t, 0, pageOne.Pages().Len())
+ require.Equal(t, 0, pageOne.NumberOfElements())
+ require.Equal(t, 0, pageOne.TotalNumberOfElements())
+ require.Equal(t, 0, pageOne.TotalPages())
+ require.Equal(t, 1, pageOne.PageNumber())
+ require.Equal(t, 5, pageOne.PageSize())
}
func TestPaginationURLFactory(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ cfg, fs := newTestCfg()
- viper.Set("paginatePath", "zoo")
+ cfg.Set("paginatePath", "zoo")
- pathSpec := newTestPathSpec()
+ pathSpec := newTestPathSpec(fs, cfg)
unicode := newPaginationURLFactory(pathSpec, "новости проекта")
fooBar := newPaginationURLFactory(pathSpec, "foo", "bar")
- assert.Equal(t, "/foo/bar/", fooBar(1))
- assert.Equal(t, "/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0/zoo/4/", unicode(4))
+ require.Equal(t, "/foo/bar/", fooBar(1))
+ require.Equal(t, "/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0/zoo/4/", unicode(4))
unicoded := unicode(4)
unicodedExpected := "/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0/zoo/4/"
@@ -216,13 +219,12 @@
t.Fatal("Expected\n", unicodedExpected, "\nGot\n", unicoded)
}
- assert.Equal(t, "/foo/bar/zoo/12345/", fooBar(12345))
+ require.Equal(t, "/foo/bar/zoo/12345/", fooBar(12345))
}
func TestPaginator(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
for _, useViper := range []bool{false, true} {
doTestPaginator(t, useViper)
}
@@ -229,17 +231,20 @@
}
func doTestPaginator(t *testing.T, useViper bool) {
- testCommonResetState()
+ cfg, fs := newTestCfg()
+
pagerSize := 5
if useViper {
- viper.Set("paginate", pagerSize)
+ cfg.Set("paginate", pagerSize)
} else {
- viper.Set("paginate", -1)
+ cfg.Set("paginate", -1)
}
- pages := createTestPages(12)
- s, err := NewSiteDefaultLang()
+
+ s, err := NewSiteForCfg(deps.DepsCfg{Cfg: cfg, Fs: fs})
require.NoError(t, err)
+
+ pages := createTestPages(s, 12)
n1 := s.newHomePage()
n2 := s.newHomePage()
n1.Data["Pages"] = pages
@@ -252,38 +257,34 @@
paginator1, err = n1.Paginator(pagerSize)
}
- assert.Nil(t, err)
- assert.NotNil(t, paginator1)
- assert.Equal(t, 3, paginator1.TotalPages())
- assert.Equal(t, 12, paginator1.TotalNumberOfElements())
+ require.Nil(t, err)
+ require.NotNil(t, paginator1)
+ require.Equal(t, 3, paginator1.TotalPages())
+ require.Equal(t, 12, paginator1.TotalNumberOfElements())
n2.paginator = paginator1.Next()
paginator2, err := n2.Paginator()
- assert.Nil(t, err)
- assert.Equal(t, paginator2, paginator1.Next())
+ require.Nil(t, err)
+ require.Equal(t, paginator2, paginator1.Next())
- n1.Data["Pages"] = createTestPages(1)
+ n1.Data["Pages"] = createTestPages(s, 1)
samePaginator, _ := n1.Paginator()
- assert.Equal(t, paginator1, samePaginator)
+ require.Equal(t, paginator1, samePaginator)
- p, _ := pageTestSite.NewPage("test")
+ p, _ := s.NewPage("test")
_, err = p.Paginator()
- assert.NotNil(t, err)
+ require.NotNil(t, err)
}
func TestPaginatorWithNegativePaginate(t *testing.T) {
- testCommonResetState()
-
- viper.Set("paginate", -1)
- s, err := NewSiteDefaultLang()
- require.NoError(t, err)
- _, err = s.newHomePage().Paginator()
+ t.Parallel()
+ s := newTestSite(t, "paginate", -1)
+ _, err := s.newHomePage().Paginator()
require.Error(t, err)
}
func TestPaginate(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
for _, useViper := range []bool{false, true} {
doTestPaginate(t, useViper)
}
@@ -290,13 +291,12 @@
}
func TestPaginatorURL(t *testing.T) {
+ t.Parallel()
+ cfg, fs := newTestCfg()
- testCommonResetState()
- viper.Set("paginate", 2)
- viper.Set("paginatePath", "testing")
+ cfg.Set("paginate", 2)
+ cfg.Set("paginatePath", "testing")
- fs := hugofs.NewMem()
-
for i := 0; i < 10; i++ {
// Issue #2177, do not double encode URLs
writeSource(t, fs, filepath.Join("content", "阅读", fmt.Sprintf("page%d.md", (i+1))),
@@ -318,23 +318,29 @@
{{ end }}
</body></html>`)
- buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
- assertFileContent(t, fs, filepath.Join("public", "阅读", "testing", "2", "index.html"), false, "2: /%E9%98%85%E8%AF%BB/testing/2/")
+ th := testHelper{s.Cfg}
+ th.assertFileContent(t, fs, filepath.Join("public", "阅读", "testing", "2", "index.html"), false, "2: /%E9%98%85%E8%AF%BB/testing/2/")
+
}
func doTestPaginate(t *testing.T, useViper bool) {
pagerSize := 5
+
+ var (
+ s *Site
+ err error
+ )
+
if useViper {
- viper.Set("paginate", pagerSize)
+ s = newTestSite(t, "paginate", pagerSize)
} else {
- viper.Set("paginate", -1)
+ s = newTestSite(t, "paginate", -1)
}
- pages := createTestPages(6)
- s, err := NewSiteDefaultLang()
- require.NoError(t, err)
+ pages := createTestPages(s, 6)
n1 := s.newHomePage()
n2 := s.newHomePage()
@@ -346,10 +352,10 @@
paginator1, err = n1.Paginate(pages, pagerSize)
}
- assert.Nil(t, err)
- assert.NotNil(t, paginator1)
- assert.Equal(t, 2, paginator1.TotalPages())
- assert.Equal(t, 6, paginator1.TotalNumberOfElements())
+ require.Nil(t, err)
+ require.NotNil(t, paginator1)
+ require.Equal(t, 2, paginator1.TotalPages())
+ require.Equal(t, 6, paginator1.TotalNumberOfElements())
n2.paginator = paginator1.Next()
if useViper {
@@ -357,101 +363,102 @@
} else {
paginator2, err = n2.Paginate(pages, pagerSize)
}
- assert.Nil(t, err)
- assert.Equal(t, paginator2, paginator1.Next())
+ require.Nil(t, err)
+ require.Equal(t, paginator2, paginator1.Next())
- p, _ := pageTestSite.NewPage("test")
+ p, _ := s.NewPage("test")
_, err = p.Paginate(pages)
- assert.NotNil(t, err)
+ require.NotNil(t, err)
}
func TestInvalidOptions(t *testing.T) {
- s, err := NewSiteDefaultLang()
- require.NoError(t, err)
+ t.Parallel()
+ s := newTestSite(t)
n1 := s.newHomePage()
- _, err = n1.Paginate(createTestPages(1), 1, 2)
- assert.NotNil(t, err)
+ _, err := n1.Paginate(createTestPages(s, 1), 1, 2)
+ require.NotNil(t, err)
_, err = n1.Paginator(1, 2)
- assert.NotNil(t, err)
+ require.NotNil(t, err)
_, err = n1.Paginator(-1)
- assert.NotNil(t, err)
+ require.NotNil(t, err)
}
func TestPaginateWithNegativePaginate(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ cfg, fs := newTestCfg()
+ cfg.Set("paginate", -1)
- viper.Set("paginate", -1)
- s, err := NewSiteDefaultLang()
+ s, err := NewSiteForCfg(deps.DepsCfg{Cfg: cfg, Fs: fs})
require.NoError(t, err)
- _, err = s.newHomePage().Paginate(createTestPages(2))
- assert.NotNil(t, err)
+
+ _, err = s.newHomePage().Paginate(createTestPages(s, 2))
+ require.NotNil(t, err)
}
func TestPaginatePages(t *testing.T) {
- groups, _ := createTestPages(31).GroupBy("Weight", "desc")
- pathSpec := newTestPathSpec()
+ t.Parallel()
+ s := newTestSite(t)
- for i, seq := range []interface{}{createTestPages(11), groups, WeightedPages{}, PageGroup{}, &Pages{}} {
- v, err := paginatePages(pathSpec, seq, 11, "t")
- assert.NotNil(t, v, "Val %d", i)
- assert.Nil(t, err, "Err %d", i)
+ groups, _ := createTestPages(s, 31).GroupBy("Weight", "desc")
+
+ for i, seq := range []interface{}{createTestPages(s, 11), groups, WeightedPages{}, PageGroup{}, &Pages{}} {
+ v, err := paginatePages(s.PathSpec, seq, 11, "t")
+ require.NotNil(t, v, "Val %d", i)
+ require.Nil(t, err, "Err %d", i)
}
- _, err := paginatePages(pathSpec, Site{}, 11, "t")
- assert.NotNil(t, err)
+ _, err := paginatePages(s.PathSpec, Site{}, 11, "t")
+ require.NotNil(t, err)
}
// Issue #993
func TestPaginatorFollowedByPaginateShouldFail(t *testing.T) {
- testCommonResetState()
-
- viper.Set("paginate", 10)
- s, err := NewSiteDefaultLang()
- require.NoError(t, err)
+ t.Parallel()
+ s := newTestSite(t, "paginate", 10)
n1 := s.newHomePage()
n2 := s.newHomePage()
- _, err = n1.Paginator()
- assert.Nil(t, err)
- _, err = n1.Paginate(createTestPages(2))
- assert.NotNil(t, err)
+ _, err := n1.Paginator()
+ require.Nil(t, err)
+ _, err = n1.Paginate(createTestPages(s, 2))
+ require.NotNil(t, err)
- _, err = n2.Paginate(createTestPages(2))
- assert.Nil(t, err)
+ _, err = n2.Paginate(createTestPages(s, 2))
+ require.Nil(t, err)
}
func TestPaginateFollowedByDifferentPaginateShouldFail(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ s := newTestSite(t, "paginate", 10)
- viper.Set("paginate", 10)
- s, err := NewSiteDefaultLang()
- require.NoError(t, err)
n1 := s.newHomePage()
n2 := s.newHomePage()
- p1 := createTestPages(2)
- p2 := createTestPages(10)
+ p1 := createTestPages(s, 2)
+ p2 := createTestPages(s, 10)
- _, err = n1.Paginate(p1)
- assert.Nil(t, err)
+ _, err := n1.Paginate(p1)
+ require.Nil(t, err)
_, err = n1.Paginate(p1)
- assert.Nil(t, err)
+ require.Nil(t, err)
_, err = n1.Paginate(p2)
- assert.NotNil(t, err)
+ require.NotNil(t, err)
_, err = n2.Paginate(p2)
- assert.Nil(t, err)
+ require.Nil(t, err)
}
func TestProbablyEqualPageLists(t *testing.T) {
- fivePages := createTestPages(5)
- zeroPages := createTestPages(0)
- zeroPagesByWeight, _ := createTestPages(0).GroupBy("Weight", "asc")
- fivePagesByWeight, _ := createTestPages(5).GroupBy("Weight", "asc")
- ninePagesByWeight, _ := createTestPages(9).GroupBy("Weight", "asc")
+ t.Parallel()
+ s := newTestSite(t)
+ fivePages := createTestPages(s, 5)
+ zeroPages := createTestPages(s, 0)
+ zeroPagesByWeight, _ := createTestPages(s, 0).GroupBy("Weight", "asc")
+ fivePagesByWeight, _ := createTestPages(s, 5).GroupBy("Weight", "asc")
+ ninePagesByWeight, _ := createTestPages(s, 9).GroupBy("Weight", "asc")
for i, this := range []struct {
v1 interface{}
@@ -462,7 +469,7 @@
{"a", "b", true},
{"a", fivePages, false},
{fivePages, "a", false},
- {fivePages, createTestPages(2), false},
+ {fivePages, createTestPages(s, 2), false},
{fivePages, fivePages, true},
{zeroPages, zeroPages, true},
{fivePagesByWeight, fivePagesByWeight, true},
@@ -481,13 +488,16 @@
}
func TestPage(t *testing.T) {
+ t.Parallel()
urlFactory := func(page int) string {
return fmt.Sprintf("page/%d/", page)
}
- fivePages := createTestPages(7)
- fivePagesFuzzyWordCount, _ := createTestPages(7).GroupBy("FuzzyWordCount", "asc")
+ s := newTestSite(t)
+ fivePages := createTestPages(s, 7)
+ fivePagesFuzzyWordCount, _ := createTestPages(s, 7).GroupBy("FuzzyWordCount", "asc")
+
p1, _ := newPaginatorFromPages(fivePages, 2, urlFactory)
p2, _ := newPaginatorFromPageGroups(fivePagesFuzzyWordCount, 2, urlFactory)
@@ -500,33 +510,26 @@
page21, _ := f2.page(1)
page2Nil, _ := f2.page(3)
- assert.Equal(t, 3, page11.fuzzyWordCount)
- assert.Nil(t, page1Nil)
+ require.Equal(t, 3, page11.fuzzyWordCount)
+ require.Nil(t, page1Nil)
- assert.Equal(t, 3, page21.fuzzyWordCount)
- assert.Nil(t, page2Nil)
+ require.Equal(t, 3, page21.fuzzyWordCount)
+ require.Nil(t, page2Nil)
}
-func createTestPages(num int) Pages {
+func createTestPages(s *Site, num int) Pages {
pages := make(Pages, num)
- info := newSiteInfo(siteBuilderCfg{baseURL: "http://base/", language: helpers.NewDefaultLanguage()})
for i := 0; i < num; i++ {
- pages[i] = &Page{
- pageInit: &pageInit{},
- URLPath: URLPath{
- Section: "z",
- URL: fmt.Sprintf("http://base/x/y/p%d.html", i),
- },
- Site: &info,
- Source: Source{File: *source.NewFile(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i)))},
- }
+ p := s.newPage(filepath.FromSlash(fmt.Sprintf("/x/y/z/p%d.md", i)))
w := 5
if i%2 == 0 {
w = 10
}
- pages[i].fuzzyWordCount = i + 2
- pages[i].Weight = w
+ p.fuzzyWordCount = i + 2
+ p.Weight = w
+ pages[i] = p
+
}
return pages
--- a/hugolib/path_separators_test.go
+++ b/hugolib/path_separators_test.go
@@ -26,7 +26,9 @@
`
func TestDegenerateMissingFolderInPageFilename(t *testing.T) {
- p, err := pageTestSite.NewPageFrom(strings.NewReader(simplePageYAML), filepath.Join("foobar"))
+ t.Parallel()
+ s := newTestSite(t)
+ p, err := s.NewPageFrom(strings.NewReader(simplePageYAML), filepath.Join("foobar"))
if err != nil {
t.Fatalf("Error in NewPageFrom")
}
@@ -36,6 +38,8 @@
}
func TestNewPageWithFilePath(t *testing.T) {
+ t.Parallel()
+ s := newTestSite(t)
toCheck := []struct {
input string
section string
@@ -48,7 +52,7 @@
}
for i, el := range toCheck {
- p, err := pageTestSite.NewPageFrom(strings.NewReader(simplePageYAML), el.input)
+ p, err := s.NewPageFrom(strings.NewReader(simplePageYAML), el.input)
if err != nil {
t.Errorf("[%d] Reading from simplePageYAML resulted in an error: %s", i, err)
}
--- a/hugolib/path_separators_windows_test.go
+++ b/hugolib/path_separators_windows_test.go
@@ -14,8 +14,9 @@
package hugolib
import (
- "github.com/spf13/hugo/tpl"
"testing"
+
+ "github.com/spf13/hugo/tpl"
)
const (
@@ -24,6 +25,7 @@
)
func TestTemplatePathSeparator(t *testing.T) {
+ t.Parallel()
tmpl := new(tpl.GoHTMLTemplate)
if name := tmpl.GenerateTemplateNameFrom(win_base, win_path); name != "sub1/index.html" {
t.Fatalf("Template name incorrect. got %s but expected %s", name, "sub1/index.html")
--- a/hugolib/permalinks_test.go
+++ b/hugolib/permalinks_test.go
@@ -16,8 +16,6 @@
import (
"strings"
"testing"
-
- "github.com/spf13/hugo/helpers"
)
// testdataPermalinks is used by a couple of tests; the expandsTo content is
@@ -54,6 +52,7 @@
}
func TestPermalinkValidation(t *testing.T) {
+ t.Parallel()
for _, item := range testdataPermalinks {
pp := pathPattern(item.spec)
have := pp.validate()
@@ -71,9 +70,10 @@
}
func TestPermalinkExpansion(t *testing.T) {
- page, err := pageTestSite.NewPageFrom(strings.NewReader(simplePageJSON), "blue/test-page.md")
- info := newSiteInfo(siteBuilderCfg{language: helpers.NewDefaultLanguage()})
- page.Site = &info
+ t.Parallel()
+ s := newTestSite(t)
+ page, err := s.NewPageFrom(strings.NewReader(simplePageJSON), "blue/test-page.md")
+
if err != nil {
t.Fatalf("failed before we began, could not parse SIMPLE_PAGE_JSON: %s", err)
}
--- a/hugolib/robotstxt_test.go
+++ b/hugolib/robotstxt_test.go
@@ -17,10 +17,7 @@
"path/filepath"
"testing"
- "github.com/spf13/hugo/hugofs"
-
"github.com/spf13/hugo/deps"
- "github.com/spf13/viper"
)
const robotTxtTemplate = `User-agent: Googlebot
@@ -30,18 +27,20 @@
`
func TestRobotsTXTOutput(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
- viper.Set("baseURL", "http://auth/bub/")
- viper.Set("enableRobotsTXT", true)
+ cfg.Set("baseURL", "http://auth/bub/")
+ cfg.Set("enableRobotsTXT", true)
- fs := hugofs.NewMem()
-
writeSource(t, fs, filepath.Join("layouts", "robots.txt"), robotTxtTemplate)
writeSourcesToSource(t, "content", fs, weightedSources...)
- buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
- assertFileContent(t, fs, "public/robots.txt", true, "User-agent: Googlebot")
+ th.assertFileContent(t, fs, "public/robots.txt", true, "User-agent: Googlebot")
}
--- a/hugolib/rss_test.go
+++ b/hugolib/rss_test.go
@@ -18,31 +18,32 @@
"testing"
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/hugofs"
- "github.com/spf13/viper"
)
func TestRSSOutput(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ var (
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
+ )
rssURI := "customrss.xml"
- viper.Set("baseURL", "http://auth/bub/")
- viper.Set("rssURI", rssURI)
- viper.Set("title", "RSSTest")
- fs := hugofs.NewMem()
+ cfg.Set("baseURL", "http://auth/bub/")
+ cfg.Set("rssURI", rssURI)
+ cfg.Set("title", "RSSTest")
for _, src := range weightedSources {
writeSource(t, fs, filepath.Join("content", "sect", src.Name), string(src.Content))
}
- buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
// Home RSS
- assertFileContent(t, fs, filepath.Join("public", rssURI), true, "<?xml", "rss version", "RSSTest")
+ th.assertFileContent(t, fs, filepath.Join("public", rssURI), true, "<?xml", "rss version", "RSSTest")
// Section RSS
- assertFileContent(t, fs, filepath.Join("public", "sect", rssURI), true, "<?xml", "rss version", "Sects on RSSTest")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect", rssURI), true, "<?xml", "rss version", "Sects on RSSTest")
// Taxonomy RSS
- assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", rssURI), true, "<?xml", "rss version", "Hugo on RSSTest")
+ th.assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", rssURI), true, "<?xml", "rss version", "Hugo on RSSTest")
}
--- a/hugolib/scratch_test.go
+++ b/hugolib/scratch_test.go
@@ -14,13 +14,15 @@
package hugolib
import (
- "github.com/stretchr/testify/assert"
"reflect"
"sync"
"testing"
+
+ "github.com/stretchr/testify/assert"
)
func TestScratchAdd(t *testing.T) {
+ t.Parallel()
scratch := newScratch()
scratch.Add("int1", 10)
scratch.Add("int1", 20)
@@ -50,6 +52,7 @@
}
func TestScratchAddSlice(t *testing.T) {
+ t.Parallel()
scratch := newScratch()
_, err := scratch.Add("intSlice", []int{1, 2})
@@ -78,6 +81,7 @@
}
func TestScratchSet(t *testing.T) {
+ t.Parallel()
scratch := newScratch()
scratch.Set("key", "val")
assert.Equal(t, "val", scratch.Get("key"))
@@ -119,6 +123,7 @@
}
func TestScratchGet(t *testing.T) {
+ t.Parallel()
scratch := newScratch()
nothing := scratch.Get("nothing")
if nothing != nil {
@@ -127,6 +132,7 @@
}
func TestScratchSetInMap(t *testing.T) {
+ t.Parallel()
scratch := newScratch()
scratch.SetInMap("key", "lux", "Lux")
scratch.SetInMap("key", "abc", "Abc")
@@ -137,6 +143,7 @@
}
func TestScratchGetSortedMapValues(t *testing.T) {
+ t.Parallel()
scratch := newScratch()
nothing := scratch.GetSortedMapValues("nothing")
if nothing != nil {
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -149,31 +149,6 @@
return fmt.Sprintf("%s(%q, %t){%s}", sc.name, params, sc.doMarkup, sc.inner)
}
-// HandleShortcodes does all in one go: extract, render and replace
-// only used for testing
-func HandleShortcodes(stringToParse string, page *Page) (string, error) {
- tmpContent, tmpShortcodes, err := extractAndRenderShortcodes(stringToParse, page)
-
- if err != nil {
- return "", err
- }
-
- if len(tmpShortcodes) > 0 {
- shortcodes, err := executeShortcodeFuncMap(tmpShortcodes)
- if err != nil {
- return "", err
- }
- tmpContentWithTokensReplaced, err := replaceShortcodeTokens([]byte(tmpContent), shortcodePlaceholderPrefix, shortcodes)
-
- if err != nil {
- return "", fmt.Errorf("Failed to replace shortcode tokens in %s:\n%s", page.BaseFileName(), err.Error())
- }
- return string(tmpContentWithTokensReplaced), nil
- }
-
- return tmpContent, nil
-}
-
var isInnerShortcodeCache = struct {
sync.RWMutex
m map[string]bool
@@ -239,12 +214,12 @@
}
if sc.doMarkup {
- newInner := helpers.RenderBytes(&helpers.RenderingContext{
+ newInner := p.s.ContentSpec.RenderBytes(&helpers.RenderingContext{
Content: []byte(inner), PageFmt: p.determineMarkupType(),
- ConfigProvider: p.Language(),
- DocumentID: p.UniqueID(),
- DocumentName: p.Path(),
- Config: p.getRenderingConfig()})
+ Cfg: p.Language(),
+ DocumentID: p.UniqueID(),
+ DocumentName: p.Path(),
+ Config: p.getRenderingConfig()})
// If the type is “unknown” or “markdown”, we assume the markdown
// generation has been performed. Given the input: `a line`, markdown
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -24,20 +24,22 @@
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/tplapi"
- "github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
// TODO(bep) remove
func pageFromString(in, filename string, withTemplate ...func(templ tplapi.Template) error) (*Page, error) {
- s := pageTestSite
+ s := newTestSite(nil)
if len(withTemplate) > 0 {
// Have to create a new site
var err error
- s, err = NewSiteDefaultLang(withTemplate...)
+ cfg, fs := newTestCfg()
+
+ d := deps.DepsCfg{Language: helpers.NewLanguage("en", cfg), Fs: fs, WithTemplate: withTemplate[0]}
+
+ s, err = NewSiteForCfg(d)
if err != nil {
return nil, err
}
@@ -50,9 +52,8 @@
}
func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tplapi.Template) error, expectError bool) {
- testCommonResetState()
- fs := hugofs.NewMem()
+ cfg, fs := newTestCfg()
// Need some front matter, see https://github.com/spf13/hugo/issues/2337
contentFile := `---
@@ -62,7 +63,7 @@
writeSource(t, fs, "content/simple.md", contentFile)
- h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs, WithTemplate: withTemplate})
+ h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate})
require.NoError(t, err)
require.Len(t, h.Sites, 1)
@@ -90,37 +91,8 @@
}
}
-func TestShortcodeGoFuzzReports(t *testing.T) {
-
- p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error {
- return templ.AddInternalShortcode("sc.html", `foo`)
- })
-
- for i, this := range []struct {
- data string
- expectErr bool
- }{
- {"{{</*/", true},
- } {
- output, err := HandleShortcodes(this.data, p)
-
- if this.expectErr && err == nil {
- t.Errorf("[%d] should have errored", i)
- }
-
- if !this.expectErr && err != nil {
- t.Errorf("[%d] should not have errored: %s", i, err)
- }
-
- if !this.expectErr && err == nil && len(output) == 0 {
- t.Errorf("[%d] empty result", i)
- }
- }
-
-}
-
func TestNonSC(t *testing.T) {
-
+ t.Parallel()
// notice the syntax diff from 0.12, now comment delims must be added
CheckShortCodeMatch(t, "{{%/* movie 47238zzb */%}}", "{{% movie 47238zzb %}}", nil)
}
@@ -127,6 +99,7 @@
// Issue #929
func TestHyphenatedSC(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
return nil
@@ -137,6 +110,7 @@
// Issue #1753
func TestNoTrailingNewline(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("a.html", `{{ .Get 0 }}`)
return nil
@@ -146,6 +120,7 @@
}
func TestPositionalParamSC(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
return nil
@@ -159,6 +134,7 @@
}
func TestPositionalParamIndexOutOfBounds(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 1 }}`)
return nil
@@ -169,6 +145,7 @@
// some repro issues for panics in Go Fuzz testing
func TestNamedParamSC(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
return nil
@@ -183,6 +160,7 @@
// Issue #2294
func TestNestedNamedMissingParam(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("acc.html", `<div class="acc">{{ .Inner }}</div>`)
tem.AddInternalShortcode("div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
@@ -195,6 +173,7 @@
}
func TestIsNamedParamsSC(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("byposition.html", `<div id="{{ .Get 0 }}">`)
tem.AddInternalShortcode("byname.html", `<div id="{{ .Get "id" }}">`)
@@ -210,6 +189,7 @@
}
func TestInnerSC(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
@@ -220,6 +200,7 @@
}
func TestInnerSCWithMarkdown(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
@@ -233,6 +214,7 @@
}
func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
@@ -256,6 +238,7 @@
}
func TestEmbeddedSC(t *testing.T) {
+ t.Parallel()
CheckShortCodeMatch(t, "{{% test %}}", "This is a simple Test", nil)
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" %}}`, "\n<figure class=\"bananas orange\">\n \n <img src=\"/found/here\" />\n \n \n</figure>\n", nil)
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" caption="This is a caption" %}}`, "\n<figure class=\"bananas orange\">\n \n <img src=\"/found/here\" alt=\"This is a caption\" />\n \n \n <figcaption>\n <p>\n This is a caption\n \n \n \n </p> \n </figcaption>\n \n</figure>\n", nil)
@@ -262,6 +245,7 @@
}
func TestNestedSC(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
tem.AddInternalShortcode("scn2.html", `<div>SC2</div>`)
@@ -273,6 +257,7 @@
}
func TestNestedComplexSC(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("row.html", `-row-{{ .Inner}}-rowStop-`)
tem.AddInternalShortcode("column.html", `-col-{{.Inner }}-colStop-`)
@@ -288,6 +273,7 @@
}
func TestParentShortcode(t *testing.T) {
+ t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
tem.AddInternalShortcode("r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
@@ -300,43 +286,14 @@
}
func TestFigureImgWidth(t *testing.T) {
+ t.Parallel()
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" width="100px" %}}`, "\n<figure class=\"bananas orange\">\n \n <img src=\"/found/here\" alt=\"apple\" width=\"100px\" />\n \n \n</figure>\n", nil)
}
-func TestHighlight(t *testing.T) {
- testCommonResetState()
-
- if !helpers.HasPygments() {
- t.Skip("Skip test as Pygments is not installed")
- }
- viper.Set("pygmentsStyle", "bw")
- viper.Set("pygmentsUseClasses", false)
-
- code := `
-{{< highlight java >}}
-void do();
-{{< /highlight >}}`
-
- p, _ := pageFromString(simplePage, "simple.md")
- output, err := HandleShortcodes(code, p)
-
- if err != nil {
- t.Fatal("Handle shortcode error", err)
- }
- matched, err := regexp.MatchString("(?s)^\n<div class=\"highlight\" style=\"background: #ffffff\"><pre style=\"line-height: 125%\">.*?void</span> do().*?</pre></div>\n$", output)
-
- if err != nil {
- t.Fatal("Regexp error", err)
- }
-
- if !matched {
- t.Errorf("Hightlight mismatch, got (escaped to see invisible chars)\n%+q", output)
- }
-}
-
const testScPlaceholderRegexp = "HAHAHUGOSHORTCODE-\\d+HBHB"
func TestExtractShortcodes(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
name string
input string
@@ -455,18 +412,9 @@
}
func TestShortcodesInSite(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
baseURL := "http://foo/bar"
- viper.Set("defaultExtension", "html")
- viper.Set("defaultContentLanguage", "en")
- viper.Set("baseURL", baseURL)
- viper.Set("uglyURLs", false)
- viper.Set("verbose", true)
- viper.Set("pygmentsUseClasses", true)
- viper.Set("pygmentsCodefences", true)
-
tests := []struct {
contentPath string
content string
@@ -579,11 +527,21 @@
}
- fs := hugofs.NewMem()
+ cfg, fs := newTestCfg()
+ cfg.Set("defaultExtension", "html")
+ cfg.Set("defaultContentLanguage", "en")
+ cfg.Set("baseURL", baseURL)
+ cfg.Set("uglyURLs", false)
+ cfg.Set("verbose", true)
+
+ cfg.Set("pygmentsUseClasses", true)
+ cfg.Set("pygmentsCodefences", true)
+
writeSourcesToSource(t, "content", fs, sources...)
- buildSingleSite(t, deps.DepsCfg{WithTemplate: addTemplates, Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{WithTemplate: addTemplates, Fs: fs, Cfg: cfg}, BuildCfg{})
+ th := testHelper{s.Cfg}
for _, test := range tests {
if strings.HasSuffix(test.contentPath, ".ad") && !helpers.HasAsciidoc() {
@@ -597,7 +555,7 @@
continue
}
- assertFileContent(t, fs, test.outFile, true, test.expected)
+ th.assertFileContent(t, fs, test.outFile, true, test.expected)
}
}
@@ -665,6 +623,7 @@
}
func TestReplaceShortcodeTokens(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
input string
prefix string
--- a/hugolib/shortcodeparser_test.go
+++ b/hugolib/shortcodeparser_test.go
@@ -152,6 +152,7 @@
}
func TestShortcodeLexer(t *testing.T) {
+ t.Parallel()
for i, test := range shortCodeLexerTests {
items := collect(&test)
if !equal(items, test.items) {
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -37,11 +37,9 @@
bp "github.com/spf13/hugo/bufferpool"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/parser"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/target"
- "github.com/spf13/hugo/tpl"
"github.com/spf13/hugo/tplapi"
"github.com/spf13/hugo/transform"
"github.com/spf13/nitro"
@@ -113,7 +111,7 @@
// reset returns a new Site prepared for rebuild.
func (s *Site) reset() *Site {
- return &Site{Deps: s.Deps, owner: s.owner, PageCollections: newPageCollections()}
+ return &Site{Deps: s.Deps, Language: s.Language, owner: s.owner, PageCollections: newPageCollections()}
}
// newSite creates a new site with the given configuration.
@@ -121,7 +119,7 @@
c := newPageCollections()
if cfg.Language == nil {
- cfg.Language = helpers.NewDefaultLanguage()
+ cfg.Language = helpers.NewDefaultLanguage(cfg.Cfg)
}
s := &Site{PageCollections: c, Language: cfg.Language}
@@ -148,12 +146,13 @@
return s, nil
}
-// TODO(bep) globals clean below...
// NewSiteDefaultLang creates a new site in the default language.
// The site will have a template system loaded and ready to use.
// Note: This is mainly used in single site tests.
func NewSiteDefaultLang(withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
- return newSiteForLang(helpers.NewDefaultLanguage(), withTemplate...)
+ v := viper.New()
+ loadDefaultSettingsFor(v)
+ return newSiteForLang(helpers.NewDefaultLanguage(v), withTemplate...)
}
// NewEnglishSite creates a new site in English language.
@@ -160,27 +159,11 @@
// The site will have a template system loaded and ready to use.
// Note: This is mainly used in single site tests.
func NewEnglishSite(withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
- return newSiteForLang(helpers.NewLanguage("en"), withTemplate...)
+ v := viper.New()
+ loadDefaultSettingsFor(v)
+ return newSiteForLang(helpers.NewLanguage("en", v), withTemplate...)
}
-// NewEnglishSite creates a new site in the English language with in-memory Fs.
-// The site will have a template system loaded and ready to use.
-// Note: This is mainly used in single site tests.
-func NewEnglishSiteMem(withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
- withTemplates := func(templ tplapi.Template) error {
- for _, wt := range withTemplate {
- if err := wt(templ); err != nil {
- return err
- }
- }
- return nil
- }
-
- cfg := deps.DepsCfg{WithTemplate: withTemplates, Language: helpers.NewLanguage("en"), Fs: hugofs.NewMem()}
-
- return newSiteForCfg(cfg)
-}
-
// newSiteForLang creates a new site in the given language.
func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
withTemplates := func(templ tplapi.Template) error {
@@ -191,13 +174,17 @@
}
return nil
}
+
cfg := deps.DepsCfg{WithTemplate: withTemplates, Language: lang}
- return newSiteForCfg(cfg)
+ return NewSiteForCfg(cfg)
}
-func newSiteForCfg(cfg deps.DepsCfg) (*Site, error) {
+// NewSiteForCfg creates a new site for the given configuration.
+// The site will have a template system loaded and ready to use.
+// Note: This is mainly used in single site tests.
+func NewSiteForCfg(cfg deps.DepsCfg) (*Site, error) {
s, err := newSite(cfg)
if err != nil {
@@ -269,15 +256,13 @@
// Used in tests.
type siteBuilderCfg struct {
- language *helpers.Language
- // TOD(bep) globals fs
+ language *helpers.Language
s *Site
- fs *hugofs.Fs
pageCollections *PageCollections
baseURL string
}
-// TODO(bep) globals get rid of this
+// TODO(bep) get rid of this
func newSiteInfo(cfg siteBuilderCfg) SiteInfo {
return SiteInfo{
s: cfg.s,
@@ -284,6 +269,7 @@
BaseURL: template.URL(cfg.baseURL),
multilingual: newMultiLingualForLanguage(cfg.language),
PageCollections: cfg.pageCollections,
+ Params: make(map[string]interface{}),
}
}
@@ -586,12 +572,12 @@
}
}
- if len(tmplChanged) > 0 {
+ if len(tmplChanged) > 0 || len(i18nChanged) > 0 {
sites := s.owner.Sites
first := sites[0]
// TOD(bep) globals clean
- if err := first.Deps.LoadTemplates(); err != nil {
+ if err := first.Deps.LoadResources(); err != nil {
s.Log.ERROR.Println(err)
}
@@ -613,12 +599,6 @@
s.readDataFromSourceFS()
}
- if len(i18nChanged) > 0 {
- if err := s.readI18nSources(); err != nil {
- s.Log.ERROR.Println(err)
- }
- }
-
// If a content file changes, we need to reload only it and re-render the entire site.
// First step is to read the changed files and (re)place them in site.AllPages
@@ -648,7 +628,7 @@
wg2.Add(4)
for i := 0; i < 2; i++ {
go fileConverter(s, fileConvChan, convertResults, wg2)
- go pageConverter(s, pageChan, convertResults, wg2)
+ go pageConverter(pageChan, convertResults, wg2)
}
for _, ev := range sourceChanged {
@@ -732,7 +712,7 @@
}
func (s *Site) loadData(sources []source.Input) (err error) {
- s.Log.DEBUG.Printf("Load Data from %q", sources)
+ s.Log.DEBUG.Printf("Load Data from %d source(s)", len(sources))
s.Data = make(map[string]interface{})
var current map[string]interface{}
for _, currentSource := range sources {
@@ -790,35 +770,19 @@
case "toml":
return parser.HandleTOMLMetaData(f.Bytes())
default:
- s.Log.WARN.Printf("Data not supported for extension '%s'", f.Extension())
- return nil, nil
+ return nil, fmt.Errorf("Data not supported for extension '%s'", f.Extension())
}
}
-func (s *Site) readI18nSources() error {
-
- i18nSources := []source.Input{source.NewFilesystem(s.Fs, s.absI18nDir())}
-
- themeI18nDir, err := s.PathSpec.GetThemeI18nDirPath()
- if err == nil {
- i18nSources = []source.Input{source.NewFilesystem(s.Fs, themeI18nDir), i18nSources[0]}
- }
-
- if err = s.loadI18n(i18nSources); err != nil {
- return err
- }
-
- return nil
-}
-
func (s *Site) readDataFromSourceFS() error {
+ sp := source.NewSourceSpec(s.Cfg, s.Fs)
dataSources := make([]source.Input, 0, 2)
- dataSources = append(dataSources, source.NewFilesystem(s.Fs, s.absDataDir()))
+ dataSources = append(dataSources, sp.NewFilesystem(s.absDataDir()))
// have to be last - duplicate keys in earlier entries will win
themeDataDir, err := s.PathSpec.GetThemeDataDirPath()
if err == nil {
- dataSources = append(dataSources, source.NewFilesystem(s.Fs, themeDataDir))
+ dataSources = append(dataSources, sp.NewFilesystem(themeDataDir))
}
err = s.loadData(dataSources)
@@ -837,10 +801,6 @@
return
}
- if err = s.readI18nSources(); err != nil {
- return
- }
-
s.timerStep("load i18n")
return s.createPages()
@@ -858,20 +818,7 @@
}
}
-func (s *Site) setCurrentLanguageConfig() error {
- // There are sadly some global template funcs etc. that need the language information.
- viper.Set("multilingual", s.multilingualEnabled())
- viper.Set("currentContentLanguage", s.Language)
- // Cache the current config.
- helpers.InitConfigProviderForCurrentContentLanguage()
- return tpl.SetTranslateLang(s.Language)
-}
-
func (s *Site) render() (err error) {
- if err = s.setCurrentLanguageConfig(); err != nil {
- return
- }
-
if err = s.preparePages(); err != nil {
return
}
@@ -927,9 +874,10 @@
return err
}
- staticDir := helpers.AbsPathify(viper.GetString("staticDir") + "/")
+ staticDir := s.PathSpec.AbsPathify(s.Cfg.GetString("staticDir") + "/")
- s.Source = source.NewFilesystem(s.Fs, s.absContentDir(), staticDir)
+ sp := source.NewSourceSpec(s.Cfg, s.Fs)
+ s.Source = sp.NewFilesystem(s.absContentDir(), staticDir)
return
}
@@ -945,7 +893,7 @@
// SitemapAbsURL is a convenience method giving the absolute URL to the sitemap.
func (s *SiteInfo) SitemapAbsURL() string {
- sitemapDefault := parseSitemap(viper.GetStringMap("sitemap"))
+ sitemapDefault := parseSitemap(s.s.Cfg.GetStringMap("sitemap"))
p := s.HomeAbsURL()
if !strings.HasSuffix(p, "/") {
p += "/"
@@ -967,12 +915,12 @@
params := lang.Params()
permalinks := make(PermalinkOverrides)
- for k, v := range viper.GetStringMapString("permalinks") {
+ for k, v := range s.Cfg.GetStringMapString("permalinks") {
permalinks[k] = pathPattern(v)
}
- defaultContentInSubDir := viper.GetBool("defaultContentLanguageInSubdir")
- defaultContentLanguage := viper.GetString("defaultContentLanguage")
+ defaultContentInSubDir := s.Cfg.GetBool("defaultContentLanguageInSubdir")
+ defaultContentLanguage := s.Cfg.GetString("defaultContentLanguage")
languagePrefix := ""
if s.multilingualEnabled() && (defaultContentInSubDir || lang.Lang != defaultContentLanguage) {
@@ -985,7 +933,7 @@
}
s.Info = SiteInfo{
- BaseURL: template.URL(helpers.SanitizeURLKeepTrailingSlash(viper.GetString("baseURL"))),
+ BaseURL: template.URL(helpers.SanitizeURLKeepTrailingSlash(s.Cfg.GetString("baseURL"))),
Title: lang.GetString("title"),
Author: lang.GetStringMap("author"),
Social: lang.GetStringMapString("social"),
@@ -999,8 +947,8 @@
defaultContentLanguageInSubdir: defaultContentInSubDir,
sectionPagesMenu: lang.GetString("sectionPagesMenu"),
GoogleAnalytics: lang.GetString("googleAnalytics"),
- BuildDrafts: viper.GetBool("buildDrafts"),
- canonifyURLs: viper.GetBool("canonifyURLs"),
+ BuildDrafts: s.Cfg.GetBool("buildDrafts"),
+ canonifyURLs: s.Cfg.GetBool("canonifyURLs"),
preserveTaxonomyNames: lang.GetBool("preserveTaxonomyNames"),
PageCollections: s.PageCollections,
Files: &s.Files,
@@ -1016,22 +964,22 @@
}
func (s *Site) hasTheme() bool {
- return viper.GetString("theme") != ""
+ return s.Cfg.GetString("theme") != ""
}
func (s *Site) dataDir() string {
- return viper.GetString("dataDir")
+ return s.Cfg.GetString("dataDir")
}
func (s *Site) absDataDir() string {
- return helpers.AbsPathify(s.dataDir())
+ return s.PathSpec.AbsPathify(s.dataDir())
}
func (s *Site) i18nDir() string {
- return viper.GetString("i18nDir")
+ return s.Cfg.GetString("i18nDir")
}
func (s *Site) absI18nDir() string {
- return helpers.AbsPathify(s.i18nDir())
+ return s.PathSpec.AbsPathify(s.i18nDir())
}
func (s *Site) isI18nEvent(e fsnotify.Event) bool {
@@ -1049,7 +997,7 @@
if !s.hasTheme() {
return ""
}
- return s.getRealDir(helpers.AbsPathify(filepath.Join(s.themeDir(), s.i18nDir())), path)
+ return s.getRealDir(s.PathSpec.AbsPathify(filepath.Join(s.themeDir(), s.i18nDir())), path)
}
func (s *Site) isDataDirEvent(e fsnotify.Event) bool {
@@ -1067,23 +1015,23 @@
if !s.hasTheme() {
return ""
}
- return s.getRealDir(helpers.AbsPathify(filepath.Join(s.themeDir(), s.dataDir())), path)
+ return s.getRealDir(s.PathSpec.AbsPathify(filepath.Join(s.themeDir(), s.dataDir())), path)
}
func (s *Site) themeDir() string {
- return viper.GetString("themesDir") + "/" + viper.GetString("theme")
+ return s.Cfg.GetString("themesDir") + "/" + s.Cfg.GetString("theme")
}
func (s *Site) absThemeDir() string {
- return helpers.AbsPathify(s.themeDir())
+ return s.PathSpec.AbsPathify(s.themeDir())
}
func (s *Site) layoutDir() string {
- return viper.GetString("layoutDir")
+ return s.Cfg.GetString("layoutDir")
}
func (s *Site) absLayoutDir() string {
- return helpers.AbsPathify(s.layoutDir())
+ return s.PathSpec.AbsPathify(s.layoutDir())
}
func (s *Site) isLayoutDirEvent(e fsnotify.Event) bool {
@@ -1101,11 +1049,11 @@
if !s.hasTheme() {
return ""
}
- return s.getRealDir(helpers.AbsPathify(filepath.Join(s.themeDir(), s.layoutDir())), path)
+ return s.getRealDir(s.PathSpec.AbsPathify(filepath.Join(s.themeDir(), s.layoutDir())), path)
}
func (s *Site) absContentDir() string {
- return helpers.AbsPathify(viper.GetString("contentDir"))
+ return s.PathSpec.AbsPathify(s.Cfg.GetString("contentDir"))
}
func (s *Site) isContentDirEvent(e fsnotify.Event) bool {
@@ -1141,7 +1089,7 @@
}
func (s *Site) absPublishDir() string {
- return helpers.AbsPathify(viper.GetString("publishDir"))
+ return s.PathSpec.AbsPathify(s.Cfg.GetString("publishDir"))
}
func (s *Site) checkDirectories() (err error) {
@@ -1160,8 +1108,10 @@
if err != nil {
return nil, err
}
- file, err = source.NewFileFromAbs(s.getContentDir(absFilePath), absFilePath, reader)
+ sp := source.NewSourceSpec(s.Cfg, s.Fs)
+ file, err = sp.NewFileFromAbs(s.getContentDir(absFilePath), absFilePath, reader)
+
if err != nil {
return nil, err
}
@@ -1219,7 +1169,7 @@
wg.Add(2 * procs * 4)
for i := 0; i < procs*4; i++ {
go fileConverter(s, fileConvChan, results, wg)
- go pageConverter(s, pageChan, results, wg)
+ go pageConverter(pageChan, results, wg)
}
go converterCollator(s, results, errs)
@@ -1278,7 +1228,7 @@
}
}
-func pageConverter(s *Site, pages <-chan *Page, results HandleResults, wg *sync.WaitGroup) {
+func pageConverter(pages <-chan *Page, results HandleResults, wg *sync.WaitGroup) {
defer wg.Done()
for page := range pages {
var h *MetaHandle
@@ -1288,7 +1238,10 @@
h = NewMetaHandler(page.File.Extension())
}
if h != nil {
- h.Convert(page, s, results)
+ // Note that we convert pages from the site's rawAllPages collection
+ // Which may contain pages from multiple sites, so we use the Page's site
+ // for the conversion.
+ h.Convert(page, page.s, results)
}
}
}
@@ -1478,7 +1431,6 @@
//creating flat hash
pages := s.Pages
for _, p := range pages {
-
if sectionPagesMenu != "" {
if _, ok := sectionPagesMenus[p.Section()]; !ok {
if p.Section() != "" {
@@ -1750,7 +1702,7 @@
func (s *SiteInfo) permalinkStr(plink string) string {
return helpers.MakePermalink(
- viper.GetString("baseURL"),
+ s.s.Cfg.GetString("baseURL"),
s.s.PathSpec.URLizeAndPrep(plink)).String()
}
@@ -1770,10 +1722,10 @@
defer bp.PutBuffer(outBuffer)
var path []byte
- if viper.GetBool("relativeURLs") {
+ if s.Cfg.GetBool("relativeURLs") {
path = []byte(helpers.GetDottedRelativePath(dest))
} else {
- s := viper.GetString("baseURL")
+ s := s.Cfg.GetString("baseURL")
if !strings.HasSuffix(s, "/") {
s += "/"
}
@@ -1811,17 +1763,17 @@
transformLinks := transform.NewEmptyTransforms()
- if viper.GetBool("relativeURLs") || viper.GetBool("canonifyURLs") {
+ if s.Cfg.GetBool("relativeURLs") || s.Cfg.GetBool("canonifyURLs") {
transformLinks = append(transformLinks, transform.AbsURL)
}
- if s.running() && viper.GetBool("watch") && !viper.GetBool("disableLiveReload") {
- transformLinks = append(transformLinks, transform.LiveReloadInject)
+ if s.running() && s.Cfg.GetBool("watch") && !s.Cfg.GetBool("disableLiveReload") {
+ transformLinks = append(transformLinks, transform.LiveReloadInject(s.Cfg.GetInt("port")))
}
// For performance reasons we only inject the Hugo generator tag on the home page.
if n, ok := d.(*Page); ok && n.IsHome() {
- if !viper.GetBool("disableHugoGeneratorInject") {
+ if !s.Cfg.GetBool("disableHugoGeneratorInject") {
transformLinks = append(transformLinks, transform.HugoGeneratorInject)
}
}
@@ -1828,14 +1780,14 @@
var path []byte
- if viper.GetBool("relativeURLs") {
+ if s.Cfg.GetBool("relativeURLs") {
translated, err := pageTarget.(target.OptionalTranslator).TranslateRelative(dest)
if err != nil {
return err
}
path = []byte(helpers.GetDottedRelativePath(translated))
- } else if viper.GetBool("canonifyURLs") {
- s := viper.GetString("baseURL")
+ } else if s.Cfg.GetBool("canonifyURLs") {
+ s := s.Cfg.GetString("baseURL")
if !strings.HasSuffix(s, "/") {
s += "/"
}
@@ -1850,7 +1802,7 @@
s.Log.WARN.Printf("%s is rendered empty\n", dest)
if dest == "/" {
debugAddend := ""
- if !viper.GetBool("verbose") {
+ if !s.Cfg.GetBool("verbose") {
debugAddend = "* For more debugging information, run \"hugo -v\""
}
distinctFeedbackLogger.Printf(`=============================================================
@@ -1860,7 +1812,7 @@
%s
=============================================================`,
filepath.Base(viper.ConfigFileUsed()),
- viper.GetString("theme"),
+ s.Cfg.GetString("theme"),
debugAddend)
}
@@ -1956,7 +1908,7 @@
s.targets.page = &target.PagePub{
Fs: s.Fs,
PublishDir: s.absPublishDir(),
- UglyURLs: viper.GetBool("uglyURLs"),
+ UglyURLs: s.Cfg.GetBool("uglyURLs"),
LangDir: langDir,
}
}
@@ -2007,9 +1959,9 @@
}
func (s *Site) publishDestAlias(aliasPublisher target.AliasPublisher, path, permalink string, p *Page) (err error) {
- if viper.GetBool("relativeURLs") {
+ if s.Cfg.GetBool("relativeURLs") {
// convert `permalink` into URI relative to location of `path`
- baseURL := helpers.SanitizeURLKeepTrailingSlash(viper.GetString("baseURL"))
+ baseURL := helpers.SanitizeURLKeepTrailingSlash(s.Cfg.GetString("baseURL"))
if strings.HasPrefix(permalink, baseURL) {
permalink = "/" + strings.TrimPrefix(permalink, baseURL)
}
@@ -2035,7 +1987,7 @@
msg = fmt.Sprintf("%d drafts rendered", s.draftCount)
}
- if viper.GetBool("buildDrafts") {
+ if s.Cfg.GetBool("buildDrafts") {
return fmt.Sprintf("%d of ", s.draftCount) + msg
}
@@ -2054,7 +2006,7 @@
msg = fmt.Sprintf("%d futures rendered", s.futureCount)
}
- if viper.GetBool("buildFuture") {
+ if s.Cfg.GetBool("buildFuture") {
return fmt.Sprintf("%d of ", s.futureCount) + msg
}
@@ -2073,7 +2025,7 @@
msg = fmt.Sprintf("%d expired rendered", s.expiredCount)
}
- if viper.GetBool("buildExpired") {
+ if s.Cfg.GetBool("buildExpired") {
return fmt.Sprintf("%d of ", s.expiredCount) + msg
}
@@ -2091,11 +2043,11 @@
func (s *Site) newNodePage(typ string) *Page {
return &Page{
+ language: s.Language,
pageInit: &pageInit{},
Kind: typ,
Data: make(map[string]interface{}),
Site: &s.Info,
- language: s.Language,
s: s}
}
@@ -2148,7 +2100,7 @@
}
sectionName = helpers.FirstUpper(sectionName)
- if viper.GetBool("pluralizeListTitles") {
+ if s.Cfg.GetBool("pluralizeListTitles") {
p.Title = inflect.Pluralize(sectionName)
} else {
p.Title = sectionName
--- a/hugolib/siteJSONEncode_test.go
+++ b/hugolib/siteJSONEncode_test.go
@@ -20,7 +20,6 @@
"path/filepath"
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/hugofs"
)
// Issue #1123
@@ -27,9 +26,9 @@
// Testing prevention of cyclic refs in JSON encoding
// May be smart to run with: -timeout 4000ms
func TestEncodePage(t *testing.T) {
+ t.Parallel()
+ cfg, fs := newTestCfg()
- fs := hugofs.NewMem()
-
// borrowed from menu_test.go
for _, src := range menuPageSources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
@@ -36,7 +35,7 @@
}
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
_, err := json.Marshal(s)
check(t, err)
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -21,8 +21,6 @@
"time"
bp "github.com/spf13/hugo/bufferpool"
- "github.com/spf13/hugo/helpers"
- "github.com/spf13/viper"
)
// renderPages renders pages each corresponding to a markdown file.
@@ -65,6 +63,7 @@
defer wg.Done()
for p := range pages {
targetPath := p.TargetPath()
+
layouts := p.layouts()
s.Log.DEBUG.Printf("Render %s to %q with layouts %q", p.Kind, targetPath, layouts)
@@ -89,12 +88,12 @@
func (s *Site) renderPaginator(p *Page) error {
if p.paginator != nil {
s.Log.DEBUG.Printf("Render paginator for page %q", p.Path())
- paginatePath := helpers.Config().GetString("paginatePath")
+ paginatePath := s.Cfg.GetString("paginatePath")
// write alias for page 1
// TODO(bep) ml all of these n.addLang ... fix.
- aliasPath := p.addLangPathPrefix(helpers.PaginateAliasPath(path.Join(p.sections...), 1))
+ aliasPath := p.addLangPathPrefix(s.PathSpec.PaginateAliasPath(path.Join(p.sections...), 1))
link := p.Permalink()
s.writeDestAlias(aliasPath, link, nil)
@@ -131,7 +130,7 @@
func (s *Site) renderRSS(p *Page) error {
- if viper.GetBool("disableRSS") {
+ if s.Cfg.GetBool("disableRSS") {
return nil
}
@@ -168,7 +167,7 @@
}
func (s *Site) render404() error {
- if viper.GetBool("disable404") {
+ if s.Cfg.GetBool("disable404") {
return nil
}
@@ -185,11 +184,11 @@
}
func (s *Site) renderSitemap() error {
- if viper.GetBool("disableSitemap") {
+ if s.Cfg.GetBool("disableSitemap") {
return nil
}
- sitemapDefault := parseSitemap(viper.GetStringMap("sitemap"))
+ sitemapDefault := parseSitemap(s.Cfg.GetStringMap("sitemap"))
n := s.newNodePage(kindSitemap)
@@ -228,7 +227,7 @@
}
func (s *Site) renderRobotsTXT() error {
- if !viper.GetBool("enableRobotsTXT") {
+ if !s.Cfg.GetBool("enableRobotsTXT") {
return nil
}
@@ -265,9 +264,9 @@
}
if s.owner.multilingual.enabled() {
- mainLang := s.owner.multilingual.DefaultLang.Lang
+ mainLang := s.owner.multilingual.DefaultLang
if s.Info.defaultContentLanguageInSubdir {
- mainLangURL := s.PathSpec.AbsURL(mainLang, false)
+ mainLangURL := s.PathSpec.AbsURL(mainLang.Lang, false)
s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
if err := s.publishDestAlias(s.languageAliasTarget(), "/", mainLangURL, nil); err != nil {
return err
@@ -275,7 +274,7 @@
} else {
mainLangURL := s.PathSpec.AbsURL("", false)
s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
- if err := s.publishDestAlias(s.languageAliasTarget(), mainLang, mainLangURL, nil); err != nil {
+ if err := s.publishDestAlias(s.languageAliasTarget(), mainLang.Lang, mainLangURL, nil); err != nil {
return err
}
}
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -27,7 +27,6 @@
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
- "github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -54,12 +53,12 @@
}
func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
+ t.Parallel()
+ cfg, fs := newTestCfg()
- fs := hugofs.NewMem()
-
writeSource(t, fs, filepath.Join("content", "a", "file.md"), pageSimpleTitle)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
require.Len(t, s.RegularPages, 1)
@@ -72,18 +71,18 @@
}
func TestRenderWithInvalidTemplate(t *testing.T) {
+ t.Parallel()
+ cfg, fs := newTestCfg()
- fs := hugofs.NewMem()
-
writeSource(t, fs, filepath.Join("content", "foo.md"), "foo")
withTemplate := createWithTemplateFromNameValues("missing", templateMissingFunc)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs, WithTemplate: withTemplate}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{})
errCount := s.Log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError)
- // TODO(bep) globals clean up the template error handling
+ // TODO(bep) clean up the template error handling
// The template errors are stored in a slice etc. so we get 4 log entries
// When we should get only 1
if errCount == 0 {
@@ -92,8 +91,7 @@
}
func TestDraftAndFutureRender(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
sources := []source.ByteSource{
{Name: filepath.FromSlash("sect/doc1.md"), Content: []byte("---\ntitle: doc1\ndraft: true\npublishdate: \"2414-05-29\"\n---\n# doc1\n*some content*")},
{Name: filepath.FromSlash("sect/doc2.md"), Content: []byte("---\ntitle: doc2\ndraft: true\npublishdate: \"2012-05-29\"\n---\n# doc2\n*some content*")},
@@ -101,19 +99,23 @@
{Name: filepath.FromSlash("sect/doc4.md"), Content: []byte("---\ntitle: doc4\ndraft: false\npublishdate: \"2012-05-29\"\n---\n# doc4\n*some content*")},
}
- siteSetup := func(t *testing.T) *Site {
- fs := hugofs.NewMem()
+ siteSetup := func(t *testing.T, configKeyValues ...interface{}) *Site {
+ cfg, fs := newTestCfg()
+ cfg.Set("baseURL", "http://auth/bub")
+
+ for i := 0; i < len(configKeyValues); i += 2 {
+ cfg.Set(configKeyValues[i].(string), configKeyValues[i+1])
+ }
+
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
- return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
}
- viper.Set("baseURL", "http://auth/bub")
-
// Testing Defaults.. Only draft:true and publishDate in the past should be rendered
s := siteSetup(t)
if len(s.RegularPages) != 1 {
@@ -121,36 +123,33 @@
}
// only publishDate in the past should be rendered
- viper.Set("buildDrafts", true)
- s = siteSetup(t)
+ s = siteSetup(t, "buildDrafts", true)
if len(s.RegularPages) != 2 {
t.Fatal("Future Dated Posts published unexpectedly")
}
// drafts should not be rendered, but all dates should
- viper.Set("buildDrafts", false)
- viper.Set("buildFuture", true)
- s = siteSetup(t)
+ s = siteSetup(t,
+ "buildDrafts", false,
+ "buildFuture", true)
+
if len(s.RegularPages) != 2 {
t.Fatal("Draft posts published unexpectedly")
}
// all 4 should be included
- viper.Set("buildDrafts", true)
- viper.Set("buildFuture", true)
- s = siteSetup(t)
+ s = siteSetup(t,
+ "buildDrafts", true,
+ "buildFuture", true)
+
if len(s.RegularPages) != 4 {
t.Fatal("Drafts or Future posts not included as expected")
}
- //setting defaults back
- viper.Set("buildDrafts", false)
- viper.Set("buildFuture", false)
}
func TestFutureExpirationRender(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
sources := []source.ByteSource{
{Name: filepath.FromSlash("sect/doc3.md"), Content: []byte("---\ntitle: doc1\nexpirydate: \"2400-05-29\"\n---\n# doc1\n*some content*")},
{Name: filepath.FromSlash("sect/doc4.md"), Content: []byte("---\ntitle: doc2\nexpirydate: \"2000-05-29\"\n---\n# doc2\n*some content*")},
@@ -157,7 +156,8 @@
}
siteSetup := func(t *testing.T) *Site {
- fs := hugofs.NewMem()
+ cfg, fs := newTestCfg()
+ cfg.Set("baseURL", "http://auth/bub")
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
@@ -164,11 +164,9 @@
}
- return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
}
- viper.Set("baseURL", "http://auth/bub")
-
s := siteSetup(t)
if len(s.AllPages) != 1 {
@@ -188,6 +186,7 @@
// Issue #957
func TestCrossrefs(t *testing.T) {
+ t.Parallel()
for _, uglyURLs := range []bool{true, false} {
for _, relative := range []bool{true, false} {
doTestCrossrefs(t, relative, uglyURLs)
@@ -196,13 +195,8 @@
}
func doTestCrossrefs(t *testing.T, relative, uglyURLs bool) {
- testCommonResetState()
baseURL := "http://foo/bar"
- viper.Set("defaultExtension", "html")
- viper.Set("baseURL", baseURL)
- viper.Set("uglyURLs", uglyURLs)
- viper.Set("verbose", true)
var refShortcode string
var expectedBase string
@@ -246,8 +240,13 @@
},
}
- fs := hugofs.NewMem()
+ cfg, fs := newTestCfg()
+ cfg.Set("defaultExtension", "html")
+ cfg.Set("baseURL", baseURL)
+ cfg.Set("uglyURLs", uglyURLs)
+ cfg.Set("verbose", true)
+
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
@@ -256,6 +255,7 @@
t,
deps.DepsCfg{
Fs: fs,
+ Cfg: cfg,
WithTemplate: createWithTemplateFromNameValues("_default/single.html", "{{.Content}}")},
BuildCfg{})
@@ -263,6 +263,8 @@
t.Fatalf("Expected 3 got %d pages", len(s.AllPages))
}
+ th := testHelper{s.Cfg}
+
tests := []struct {
doc string
expected string
@@ -273,7 +275,7 @@
}
for _, test := range tests {
- assertFileContent(t, fs, test.doc, true, test.expected)
+ th.assertFileContent(t, fs, test.doc, true, test.expected)
}
@@ -282,6 +284,7 @@
// Issue #939
// Issue #1923
func TestShouldAlwaysHaveUglyURLs(t *testing.T) {
+ t.Parallel()
for _, uglyURLs := range []bool{true, false} {
doTestShouldAlwaysHaveUglyURLs(t, uglyURLs)
}
@@ -288,19 +291,20 @@
}
func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
- testCommonResetState()
- viper.Set("defaultExtension", "html")
- viper.Set("verbose", true)
- viper.Set("baseURL", "http://auth/bub")
- viper.Set("disableSitemap", false)
- viper.Set("disableRSS", false)
- viper.Set("rssURI", "index.xml")
- viper.Set("blackfriday",
+ cfg, fs := newTestCfg()
+
+ cfg.Set("defaultExtension", "html")
+ cfg.Set("verbose", true)
+ cfg.Set("baseURL", "http://auth/bub")
+ cfg.Set("disableSitemap", false)
+ cfg.Set("disableRSS", false)
+ cfg.Set("rssURI", "index.xml")
+ cfg.Set("blackfriday",
map[string]interface{}{
"plainIDAnchors": true})
- viper.Set("uglyURLs", uglyURLs)
+ cfg.Set("uglyURLs", uglyURLs)
sources := []source.ByteSource{
{Name: filepath.FromSlash("sect/doc1.md"), Content: []byte("---\nmarkup: markdown\n---\n# title\nsome *content*")},
@@ -307,8 +311,6 @@
{Name: filepath.FromSlash("sect/doc2.md"), Content: []byte("---\nurl: /ugly.html\nmarkup: markdown\n---\n# title\ndoc2 *content*")},
}
- fs := hugofs.NewMem()
-
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
@@ -319,7 +321,7 @@
writeSource(t, fs, filepath.Join("layouts", "rss.xml"), "<root>RSS</root>")
writeSource(t, fs, filepath.Join("layouts", "sitemap.xml"), "<root>SITEMAP</root>")
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
var expectedPagePath string
if uglyURLs {
@@ -356,6 +358,7 @@
}
func TestNewSiteDefaultLang(t *testing.T) {
+ t.Parallel()
s, err := NewSiteDefaultLang()
require.NoError(t, err)
require.Equal(t, hugofs.Os, s.Fs.Source)
@@ -364,7 +367,7 @@
// Issue #1176
func TestSectionNaming(t *testing.T) {
-
+ t.Parallel()
for _, canonify := range []bool{true, false} {
for _, uglify := range []bool{true, false} {
for _, pluralize := range []bool{true, false} {
@@ -375,14 +378,7 @@
}
func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
- testCommonResetState()
- viper.Set("baseURL", "http://auth/sub/")
- viper.Set("defaultExtension", "html")
- viper.Set("uglyURLs", uglify)
- viper.Set("pluralizeListTitles", pluralize)
- viper.Set("canonifyURLs", canonify)
-
var expectedPathSuffix string
if uglify {
@@ -397,8 +393,14 @@
{Name: filepath.FromSlash("ラーメン/doc3.html"), Content: []byte("doc3")},
}
- fs := hugofs.NewMem()
+ cfg, fs := newTestCfg()
+ cfg.Set("baseURL", "http://auth/sub/")
+ cfg.Set("defaultExtension", "html")
+ cfg.Set("uglyURLs", uglify)
+ cfg.Set("pluralizeListTitles", pluralize)
+ cfg.Set("canonifyURLs", canonify)
+
for _, source := range sources {
writeSource(t, fs, filepath.Join("content", source.Name), string(source.Content))
}
@@ -406,8 +408,8 @@
writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}")
writeSource(t, fs, filepath.Join("layouts", "_default/list.html"), "{{.Title}}")
- buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
-
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
+ th := testHelper{s.Cfg}
tests := []struct {
doc string
pluralAware bool
@@ -427,13 +429,12 @@
test.expected = inflect.Pluralize(test.expected)
}
- assertFileContent(t, fs, filepath.Join("public", test.doc), true, test.expected)
+ th.assertFileContent(t, fs, filepath.Join("public", test.doc), true, test.expected)
}
}
func TestSkipRender(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
sources := []source.ByteSource{
{Name: filepath.FromSlash("sect/doc1.html"), Content: []byte("---\nmarkup: markdown\n---\n# title\nsome *content*")},
{Name: filepath.FromSlash("sect/doc2.html"), Content: []byte("<!doctype html><html><body>more content</body></html>")},
@@ -447,13 +448,13 @@
{Name: filepath.FromSlash("doc9.html"), Content: []byte("<html><body>doc9: {{< myshortcode >}}</body></html>")},
}
- viper.Set("defaultExtension", "html")
- viper.Set("verbose", true)
- viper.Set("canonifyURLs", true)
- viper.Set("uglyURLs", true)
- viper.Set("baseURL", "http://auth/bub")
+ cfg, fs := newTestCfg()
- fs := hugofs.NewMem()
+ cfg.Set("defaultExtension", "html")
+ cfg.Set("verbose", true)
+ cfg.Set("canonifyURLs", true)
+ cfg.Set("uglyURLs", true)
+ cfg.Set("baseURL", "http://auth/bub")
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
@@ -465,7 +466,7 @@
writeSource(t, fs, filepath.Join("layouts", "head_abs"), "<head><script src=\"/script.js\"></script></head>")
writeSource(t, fs, filepath.Join("layouts", "shortcodes", "myshortcode.html"), "SHORT")
- buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
tests := []struct {
doc string
@@ -497,11 +498,7 @@
}
func TestAbsURLify(t *testing.T) {
- testCommonResetState()
-
- viper.Set("defaultExtension", "html")
- viper.Set("uglyURLs", true)
-
+ t.Parallel()
sources := []source.ByteSource{
{Name: filepath.FromSlash("sect/doc1.html"), Content: []byte("<!doctype html><html><head></head><body><a href=\"#frag1\">link</a></body></html>")},
{Name: filepath.FromSlash("blue/doc2.html"), Content: []byte("---\nf: t\n---\n<!doctype html><html><body>more content</body></html>")},
@@ -508,11 +505,14 @@
}
for _, baseURL := range []string{"http://auth/bub", "http://base", "//base"} {
for _, canonify := range []bool{true, false} {
- viper.Set("canonifyURLs", canonify)
- viper.Set("baseURL", baseURL)
- fs := hugofs.NewMem()
+ cfg, fs := newTestCfg()
+ cfg.Set("defaultExtension", "html")
+ cfg.Set("uglyURLs", true)
+ cfg.Set("canonifyURLs", canonify)
+ cfg.Set("baseURL", baseURL)
+
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
@@ -520,7 +520,8 @@
writeSource(t, fs, filepath.Join("layouts", "blue/single.html"), templateWithURLAbs)
- buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
+ th := testHelper{s.Cfg}
tests := []struct {
file, expected string
@@ -541,7 +542,7 @@
expected = strings.Replace(expected, baseURL, "", -1)
}
- assertFileContent(t, fs, test.file, true, expected)
+ th.assertFileContent(t, fs, test.file, true, expected)
}
}
@@ -594,18 +595,16 @@
}
func TestOrderedPages(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ cfg, fs := newTestCfg()
+ cfg.Set("baseURL", "http://auth/bub")
- viper.Set("baseURL", "http://auth/bub")
-
- fs := hugofs.NewMem()
-
for _, src := range weightedSources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
if s.Sections["sect"][0].Weight != 2 || s.Sections["sect"][3].Weight != 6 {
t.Errorf("Pages in unexpected order. First should be '%d', got '%d'", 2, s.Sections["sect"][0].Weight)
@@ -656,8 +655,7 @@
}
func TestGroupedPages(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
@@ -664,11 +662,11 @@
}
}()
- viper.Set("baseURL", "http://auth/bub")
+ cfg, fs := newTestCfg()
+ cfg.Set("baseURL", "http://auth/bub")
- fs := hugofs.NewMem()
writeSourcesToSource(t, "content", fs, groupedSources...)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
rbysection, err := s.RegularPages.GroupBy("Section", "desc")
if err != nil {
@@ -832,8 +830,7 @@
Front Matter with weighted tags and categories`)
func TestWeightedTaxonomies(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
sources := []source.ByteSource{
{Name: filepath.FromSlash("sect/doc1.md"), Content: pageWithWeightedTaxonomies2},
{Name: filepath.FromSlash("sect/doc2.md"), Content: pageWithWeightedTaxonomies1},
@@ -844,12 +841,13 @@
taxonomies["tag"] = "tags"
taxonomies["category"] = "categories"
- viper.Set("baseURL", "http://auth/bub")
- viper.Set("taxonomies", taxonomies)
+ cfg, fs := newTestCfg()
- fs := hugofs.NewMem()
+ cfg.Set("baseURL", "http://auth/bub")
+ cfg.Set("taxonomies", taxonomies)
+
writeSourcesToSource(t, "content", fs, sources...)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
if s.Taxonomies["tags"]["a"][0].Page.Title != "foo" {
t.Errorf("Pages in unexpected order, 'foo' expected first, got '%v'", s.Taxonomies["tags"]["a"][0].Page.Title)
@@ -865,7 +863,8 @@
}
func findPage(site *Site, f string) *Page {
- currentPath := source.NewFile(filepath.FromSlash(f))
+ sp := source.NewSourceSpec(site.Cfg, site.Fs)
+ currentPath := sp.NewFile(filepath.FromSlash(f))
//t.Logf("looking for currentPath: %s", currentPath.Path())
for _, page := range site.Pages {
@@ -901,24 +900,24 @@
{Name: filepath.FromSlash("level2/level3/common.png"), Content: []byte("")},
}
- viper.Set("baseURL", "http://auth/")
- viper.Set("defaultExtension", "html")
- viper.Set("uglyURLs", false)
- viper.Set("pluralizeListTitles", false)
- viper.Set("canonifyURLs", false)
- viper.Set("blackfriday",
+ cfg, fs := newTestCfg()
+
+ cfg.Set("baseURL", "http://auth/")
+ cfg.Set("defaultExtension", "html")
+ cfg.Set("uglyURLs", false)
+ cfg.Set("pluralizeListTitles", false)
+ cfg.Set("canonifyURLs", false)
+ cfg.Set("blackfriday",
map[string]interface{}{
"sourceRelativeLinksProjectFolder": "/docs"})
- fs := hugofs.NewMem()
writeSourcesToSource(t, "content", fs, sources...)
- return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
}
func TestRefLinking(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
site := setupLinkingMockSite(t)
currentPage := findPage(site, "level2/level3/index.md")
@@ -941,8 +940,7 @@
}
func TestSourceRelativeLinksing(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
site := setupLinkingMockSite(t)
type resultMap map[string]string
@@ -1077,8 +1075,7 @@
}
func TestSourceRelativeLinkFileing(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
site := setupLinkingMockSite(t)
type resultMap map[string]string
--- a/hugolib/site_url_test.go
+++ b/hugolib/site_url_test.go
@@ -20,9 +20,7 @@
"html/template"
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
- "github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
@@ -35,14 +33,6 @@
slug doc 2 content
`
-const indexTemplate = "{{ range .Data.Pages }}.{{ end }}"
-
-func must(err error) {
- if err != nil {
- panic(err)
- }
-}
-
var urlFakeSource = []source.ByteSource{
{Name: filepath.FromSlash("content/blue/doc1.md"), Content: []byte(slugDoc1)},
{Name: filepath.FromSlash("content/blue/doc2.md"), Content: []byte(slugDoc2)},
@@ -50,8 +40,7 @@
// Issue #1105
func TestShouldNotAddTrailingSlashToBaseURL(t *testing.T) {
- testCommonResetState()
-
+ t.Parallel()
for i, this := range []struct {
in string
expected string
@@ -61,8 +50,10 @@
{"http://base.com/sub", "http://base.com/sub"},
{"http://base.com", "http://base.com"}} {
- viper.Set("baseURL", this.in)
- s, err := NewSiteDefaultLang()
+ cfg, fs := newTestCfg()
+ cfg.Set("baseURL", this.in)
+ d := deps.DepsCfg{Cfg: cfg, Fs: fs}
+ s, err := NewSiteForCfg(d)
require.NoError(t, err)
s.initializeSiteInfo()
@@ -70,18 +61,16 @@
t.Errorf("[%d] got %s expected %s", i, s.Info.BaseURL, this.expected)
}
}
-
}
func TestPageCount(t *testing.T) {
- testCommonResetState()
+ t.Parallel()
+ cfg, fs := newTestCfg()
+ cfg.Set("uglyURLs", false)
+ cfg.Set("paginate", 10)
- viper.Set("uglyURLs", false)
- viper.Set("paginate", 10)
-
- fs := hugofs.NewMem()
writeSourcesToSource(t, "content", fs, urlFakeSource...)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
_, err := s.Fs.Destination.Open("public/blue")
if err != nil {
--- a/hugolib/sitemap_test.go
+++ b/hugolib/sitemap_test.go
@@ -19,9 +19,7 @@
"reflect"
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/tplapi"
- "github.com/spf13/viper"
)
const sitemapTemplate = `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@@ -36,6 +34,7 @@
</urlset>`
func TestSitemapOutput(t *testing.T) {
+ t.Parallel()
for _, internal := range []bool{false, true} {
doTestSitemapOutput(t, internal)
}
@@ -42,14 +41,12 @@
}
func doTestSitemapOutput(t *testing.T, internal bool) {
- testCommonResetState()
- viper.Set("baseURL", "http://auth/bub/")
+ cfg, fs := newTestCfg()
+ cfg.Set("baseURL", "http://auth/bub/")
- fs := hugofs.NewMem()
+ depsCfg := deps.DepsCfg{Fs: fs, Cfg: cfg}
- depsCfg := deps.DepsCfg{Fs: fs}
-
if !internal {
depsCfg.WithTemplate = func(templ tplapi.Template) error {
templ.AddTemplate("sitemap.xml", sitemapTemplate)
@@ -59,8 +56,9 @@
writeSourcesToSource(t, "content", fs, weightedSources...)
s := buildSingleSite(t, depsCfg, BuildCfg{})
+ th := testHelper{s.Cfg}
- assertFileContent(t, s.Fs, "public/sitemap.xml", true,
+ th.assertFileContent(t, s.Fs, "public/sitemap.xml", true,
// Regular page
" <loc>http://auth/bub/sect/doc1/</loc>",
// Home page
@@ -76,6 +74,7 @@
}
func TestParseSitemap(t *testing.T) {
+ t.Parallel()
expected := Sitemap{Priority: 3.0, Filename: "doo.xml", ChangeFreq: "3"}
input := map[string]interface{}{
"changefreq": "3",
--- a/hugolib/taxonomy_test.go
+++ b/hugolib/taxonomy_test.go
@@ -19,26 +19,22 @@
"testing"
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/hugofs"
-
- "github.com/spf13/viper"
)
func TestByCountOrderOfTaxonomies(t *testing.T) {
- defer testCommonResetState()
-
+ t.Parallel()
taxonomies := make(map[string]string)
taxonomies["tag"] = "tags"
taxonomies["category"] = "categories"
- viper.Set("taxonomies", taxonomies)
+ cfg, fs := newTestCfg()
- fs := hugofs.NewMem()
+ cfg.Set("taxonomies", taxonomies)
writeSource(t, fs, filepath.Join("content", "page.md"), pageYamlWithTaxonomiesA)
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
st := make([]string, 0)
for _, t := range s.Taxonomies["tags"].ByCount() {
--- a/hugolib/template_engines_test.go
+++ b/hugolib/template_engines_test.go
@@ -20,13 +20,11 @@
"strings"
- "github.com/spf13/viper"
-
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/hugofs"
)
func TestAllTemplateEngines(t *testing.T) {
+ t.Parallel()
noOp := func(s string) string {
return s
}
@@ -57,11 +55,8 @@
func doTestTemplateEngine(t *testing.T, suffix string, templateFixer func(s string) string) {
- testCommonResetState()
+ cfg, fs := newTestCfg()
- fs := hugofs.NewMem()
- viper.SetFs(fs.Source)
-
writeSource(t, fs, filepath.Join("content", "p.md"), `
---
title: My Title
@@ -88,9 +83,10 @@
writeSource(t, fs, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
- buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
+ th := testHelper{s.Cfg}
- assertFileContent(t, fs, filepath.Join("public", "p", "index.html"), true,
+ th.assertFileContent(t, fs, filepath.Join("public", "p", "index.html"), true,
"Page Title: My Title",
"My Content",
"Hello World",
--- a/hugolib/template_test.go
+++ b/hugolib/template_test.go
@@ -24,9 +24,13 @@
)
func TestBaseGoTemplate(t *testing.T) {
+ t.Parallel()
+ var (
+ fs *hugofs.Fs
+ cfg *viper.Viper
+ th testHelper
+ )
- var fs *hugofs.Fs
-
// Variants:
// 1. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
// 2. <current-path>/baseof.<suffix>
@@ -44,7 +48,7 @@
},
func(t *testing.T) {
- assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
},
},
{
@@ -55,7 +59,7 @@
},
func(t *testing.T) {
- assertFileContent(t, fs, filepath.Join("public", "index.html"), false, "Base: index")
+ th.assertFileContent(t, fs, filepath.Join("public", "index.html"), false, "Base: index")
},
},
{
@@ -66,7 +70,7 @@
},
func(t *testing.T) {
- assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: list")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: list")
},
},
{
@@ -77,13 +81,13 @@
},
func(t *testing.T) {
- assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: list")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: list")
},
},
{
// Variant 1, theme, use project's base
func(t *testing.T) {
- viper.Set("theme", "mytheme")
+ cfg.Set("theme", "mytheme")
writeSource(t, fs, filepath.Join("layouts", "section", "sect-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "section", "sect-baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
writeSource(t, fs, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
@@ -90,25 +94,25 @@
},
func(t *testing.T) {
- assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
},
},
{
// Variant 1, theme, use theme's base
func(t *testing.T) {
- viper.Set("theme", "mytheme")
+ cfg.Set("theme", "mytheme")
writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "section", "sect-baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
writeSource(t, fs, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
},
func(t *testing.T) {
- assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base Theme: sect")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base Theme: sect")
},
},
{
// Variant 4, theme, use project's base
func(t *testing.T) {
- viper.Set("theme", "mytheme")
+ cfg.Set("theme", "mytheme")
writeSource(t, fs, filepath.Join("layouts", "_default", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "_default", "baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
@@ -115,27 +119,26 @@
},
func(t *testing.T) {
- assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: list")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: list")
},
},
{
// Variant 4, theme, use themes's base
func(t *testing.T) {
- viper.Set("theme", "mytheme")
+ cfg.Set("theme", "mytheme")
writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "_default", "baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
},
func(t *testing.T) {
- assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base Theme: list")
+ th.assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base Theme: list")
},
},
} {
- testCommonResetState()
+ cfg, fs = newTestCfg()
+ th = testHelper{cfg}
- fs = hugofs.NewMem()
-
writeSource(t, fs, filepath.Join("content", "sect", "page.md"), `---
title: Template test
---
@@ -143,7 +146,7 @@
`)
this.setup(t)
- buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
this.assert(t)
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -6,22 +6,66 @@
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/tplapi"
"github.com/spf13/viper"
+ "io/ioutil"
+ "os"
+
+ "log"
+
+ "github.com/spf13/hugo/hugofs"
+ jww "github.com/spf13/jwalterweatherman"
"github.com/stretchr/testify/require"
)
-func newTestDepsConfig() deps.DepsCfg {
- return deps.DepsCfg{Fs: hugofs.NewMem()}
+func newTestPathSpec(fs *hugofs.Fs, v *viper.Viper) *helpers.PathSpec {
+ l := helpers.NewDefaultLanguage(v)
+ return helpers.NewPathSpec(fs, l)
}
-func newTestPathSpec() *helpers.PathSpec {
- return helpers.NewPathSpec(hugofs.NewMem(), viper.GetViper())
+func newTestCfg() (*viper.Viper, *hugofs.Fs) {
+
+ v := viper.New()
+ fs := hugofs.NewMem(v)
+
+ v.SetFs(fs.Source)
+
+ loadDefaultSettingsFor(v)
+
+ // Default is false, but true is easier to use as default in tests
+ v.Set("defaultContentLanguageInSubdir", true)
+
+ return v, fs
+
}
+// newTestSite creates a new site in the English language with in-memory Fs.
+// The site will have a template system loaded and ready to use.
+// Note: This is only used in single site tests.
+func newTestSite(t testing.TB, configKeyValues ...interface{}) *Site {
+
+ cfg, fs := newTestCfg()
+
+ for i := 0; i < len(configKeyValues); i += 2 {
+ cfg.Set(configKeyValues[i].(string), configKeyValues[i+1])
+ }
+
+ d := deps.DepsCfg{Language: helpers.NewLanguage("en", cfg), Fs: fs}
+
+ s, err := NewSiteForCfg(d)
+
+ if err != nil {
+ t.Fatalf("Failed to create Site: %s", err)
+ }
+ return s
+}
+
+func newDebugLogger() *jww.Notepad {
+ return jww.NewNotepad(jww.LevelDebug, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+}
+
func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tplapi.Template) error {
return func(templ tplapi.Template) error {
@@ -36,10 +80,20 @@
}
func buildSingleSite(t *testing.T, depsCfg deps.DepsCfg, buildCfg BuildCfg) *Site {
- h, err := NewHugoSitesFromConfiguration(depsCfg)
+ return buildSingleSiteExpected(t, false, depsCfg, buildCfg)
+}
+func buildSingleSiteExpected(t *testing.T, expectBuildError bool, depsCfg deps.DepsCfg, buildCfg BuildCfg) *Site {
+ h, err := NewHugoSites(depsCfg)
+
require.NoError(t, err)
require.Len(t, h.Sites, 1)
+
+ if expectBuildError {
+ require.Error(t, h.Build(buildCfg))
+ return nil
+
+ }
require.NoError(t, h.Build(buildCfg))
--- /dev/null
+++ b/i18n/i18n.go
@@ -1,0 +1,96 @@
+// 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 i18n
+
+import (
+ "github.com/nicksnyder/go-i18n/i18n/bundle"
+ "github.com/spf13/hugo/config"
+ "github.com/spf13/hugo/helpers"
+ jww "github.com/spf13/jwalterweatherman"
+)
+
+var (
+ i18nWarningLogger = helpers.NewDistinctFeedbackLogger()
+)
+
+// Translator handles i18n translations.
+type Translator struct {
+ translateFuncs map[string]bundle.TranslateFunc
+ cfg config.Provider
+ logger *jww.Notepad
+}
+
+// NewTranslator creates a new Translator for the given language bundle and configuration.
+func NewTranslator(b *bundle.Bundle, cfg config.Provider, logger *jww.Notepad) Translator {
+ t := Translator{cfg: cfg, logger: logger, translateFuncs: make(map[string]bundle.TranslateFunc)}
+ t.initFuncs(b)
+ return t
+}
+
+// Func gets the translate func for the given language, or for the default
+// configured language if not found.
+func (t Translator) Func(lang string) bundle.TranslateFunc {
+ if f, ok := t.translateFuncs[lang]; ok {
+ return f
+ }
+ t.logger.WARN.Printf("Translation func for language %v not found, use default.", lang)
+ if f, ok := t.translateFuncs[t.cfg.GetString("defaultContentLanguage")]; ok {
+ return f
+ }
+ t.logger.WARN.Println("i18n not initialized, check that you have language file (in i18n) that matches the site language or the default language.")
+ return func(translationID string, args ...interface{}) string {
+ return ""
+ }
+
+}
+
+func (t Translator) initFuncs(bndl *bundle.Bundle) {
+ defaultContentLanguage := t.cfg.GetString("defaultContentLanguage")
+ var (
+ defaultT bundle.TranslateFunc
+ err error
+ )
+
+ defaultT, err = bndl.Tfunc(defaultContentLanguage)
+
+ if err != nil {
+ jww.WARN.Printf("No translation bundle found for default language %q", defaultContentLanguage)
+ }
+
+ enableMissingTranslationPlaceholders := t.cfg.GetBool("enableMissingTranslationPlaceholders")
+ for _, lang := range bndl.LanguageTags() {
+ currentLang := lang
+
+ t.translateFuncs[currentLang] = func(translationID string, args ...interface{}) string {
+ tFunc, err := bndl.Tfunc(currentLang)
+ if err != nil {
+ jww.WARN.Printf("could not load translations for language %q (%s), will use default content language.\n", lang, err)
+ } else if translated := tFunc(translationID, args...); translated != translationID {
+ return translated
+ }
+ if t.cfg.GetBool("logI18nWarnings") {
+ i18nWarningLogger.Printf("i18n|MISSING_TRANSLATION|%s|%s", currentLang, translationID)
+ }
+ if enableMissingTranslationPlaceholders {
+ return "[i18n] " + translationID
+ }
+ if defaultT != nil {
+ if translated := defaultT(translationID, args...); translated != translationID {
+ return translated
+ }
+ }
+ return ""
+ }
+ }
+}
--- /dev/null
+++ b/i18n/i18n_test.go
@@ -1,0 +1,155 @@
+// 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 i18n
+
+import (
+ "testing"
+
+ "io/ioutil"
+ "os"
+
+ "log"
+
+ "github.com/nicksnyder/go-i18n/i18n/bundle"
+ "github.com/spf13/hugo/config"
+ jww "github.com/spf13/jwalterweatherman"
+ "github.com/spf13/viper"
+ "github.com/stretchr/testify/require"
+)
+
+var logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+
+type i18nTest struct {
+ data map[string][]byte
+ args interface{}
+ lang, id, expected, expectedFlag string
+}
+
+var i18nTests = []i18nTest{
+ // All translations present
+ {
+ data: map[string][]byte{
+ "en.yaml": []byte("- id: \"hello\"\n translation: \"Hello, World!\""),
+ "es.yaml": []byte("- id: \"hello\"\n translation: \"¡Hola, Mundo!\""),
+ },
+ args: nil,
+ lang: "es",
+ id: "hello",
+ expected: "¡Hola, Mundo!",
+ expectedFlag: "¡Hola, Mundo!",
+ },
+ // Translation missing in current language but present in default
+ {
+ data: map[string][]byte{
+ "en.yaml": []byte("- id: \"hello\"\n translation: \"Hello, World!\""),
+ "es.yaml": []byte("- id: \"goodbye\"\n translation: \"¡Adiós, Mundo!\""),
+ },
+ args: nil,
+ lang: "es",
+ id: "hello",
+ expected: "Hello, World!",
+ expectedFlag: "[i18n] hello",
+ },
+ // Translation missing in default language but present in current
+ {
+ data: map[string][]byte{
+ "en.yaml": []byte("- id: \"goodybe\"\n translation: \"Goodbye, World!\""),
+ "es.yaml": []byte("- id: \"hello\"\n translation: \"¡Hola, Mundo!\""),
+ },
+ args: nil,
+ lang: "es",
+ id: "hello",
+ expected: "¡Hola, Mundo!",
+ expectedFlag: "¡Hola, Mundo!",
+ },
+ // Translation missing in both default and current language
+ {
+ data: map[string][]byte{
+ "en.yaml": []byte("- id: \"goodbye\"\n translation: \"Goodbye, World!\""),
+ "es.yaml": []byte("- id: \"goodbye\"\n translation: \"¡Adiós, Mundo!\""),
+ },
+ args: nil,
+ lang: "es",
+ id: "hello",
+ expected: "",
+ expectedFlag: "[i18n] hello",
+ },
+ // Default translation file missing or empty
+ {
+ data: map[string][]byte{
+ "en.yaml": []byte(""),
+ },
+ args: nil,
+ lang: "es",
+ id: "hello",
+ expected: "",
+ expectedFlag: "[i18n] hello",
+ },
+ // Context provided
+ {
+ data: map[string][]byte{
+ "en.yaml": []byte("- id: \"wordCount\"\n translation: \"Hello, {{.WordCount}} people!\""),
+ "es.yaml": []byte("- id: \"wordCount\"\n translation: \"¡Hola, {{.WordCount}} gente!\""),
+ },
+ args: struct {
+ WordCount int
+ }{
+ 50,
+ },
+ lang: "es",
+ id: "wordCount",
+ expected: "¡Hola, 50 gente!",
+ expectedFlag: "¡Hola, 50 gente!",
+ },
+}
+
+func doTestI18nTranslate(t *testing.T, data map[string][]byte, lang, id string, args interface{}, cfg config.Provider) string {
+ i18nBundle := bundle.New()
+
+ for file, content := range data {
+ err := i18nBundle.ParseTranslationFileBytes(file, content)
+ if err != nil {
+ t.Errorf("Error parsing translation file: %s", err)
+ }
+ }
+
+ translator := NewTranslator(i18nBundle, cfg, logger)
+
+ f := translator.Func(lang)
+
+ translated := f(id, args)
+
+ return translated
+}
+
+func TestI18nTranslate(t *testing.T) {
+ var actual, expected string
+ v := viper.New()
+ v.SetDefault("defaultContentLanguage", "en")
+
+ // Test without and with placeholders
+ for _, enablePlaceholders := range []bool{false, true} {
+ v.Set("enableMissingTranslationPlaceholders", enablePlaceholders)
+
+ for _, test := range i18nTests {
+ if enablePlaceholders {
+ expected = test.expectedFlag
+ } else {
+ expected = test.expected
+ }
+ actual = doTestI18nTranslate(t, test.data, test.lang, test.id, test.args, v)
+ require.Equal(t, expected, actual)
+ }
+ }
+}
--- /dev/null
+++ b/i18n/translationProvider.go
@@ -1,0 +1,73 @@
+// 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 i18n
+
+import (
+ "fmt"
+
+ "github.com/nicksnyder/go-i18n/i18n/bundle"
+ "github.com/spf13/hugo/deps"
+ "github.com/spf13/hugo/source"
+)
+
+// TranslationProvider provides translation handling, i.e. loading
+// of bundles etc.
+type TranslationProvider struct {
+ t Translator
+}
+
+// NewTranslationProvider creates a new translation provider.
+func NewTranslationProvider() *TranslationProvider {
+ return &TranslationProvider{}
+}
+
+// Update updates the i18n func in the provided Deps.
+func (tp *TranslationProvider) Update(d *deps.Deps) error {
+ dir := d.PathSpec.AbsPathify(d.Cfg.GetString("i18nDir"))
+ sp := source.NewSourceSpec(d.Cfg, d.Fs)
+ sources := []source.Input{sp.NewFilesystem(dir)}
+
+ themeI18nDir, err := d.PathSpec.GetThemeI18nDirPath()
+
+ if err == nil {
+ sources = []source.Input{sp.NewFilesystem(themeI18nDir), sources[0]}
+ }
+
+ d.Log.DEBUG.Printf("Load I18n from %q", sources)
+
+ i18nBundle := bundle.New()
+
+ for _, currentSource := range sources {
+ for _, r := range currentSource.Files() {
+ err := i18nBundle.ParseTranslationFileBytes(r.LogicalName(), r.Bytes())
+ if err != nil {
+ return fmt.Errorf("Failed to load translations in file %q: %s", r.LogicalName(), err)
+ }
+ }
+ }
+
+ tp.t = NewTranslator(i18nBundle, d.Cfg, d.Log)
+
+ d.Translate = tp.t.Func(d.Language.Lang)
+
+ return nil
+
+}
+
+// Clone sets the language func for the new language.
+func (tp *TranslationProvider) Clone(d *deps.Deps) error {
+ d.Translate = tp.t.Func(d.Language.Lang)
+
+ return nil
+}
--- a/source/content_directory_test.go
+++ b/source/content_directory_test.go
@@ -14,13 +14,13 @@
package source
import (
- "github.com/spf13/viper"
"testing"
+
+ "github.com/spf13/hugo/hugofs"
+ "github.com/spf13/viper"
)
func TestIgnoreDotFilesAndDirectories(t *testing.T) {
- viper.Reset()
- defer viper.Reset()
tests := []struct {
path string
@@ -49,9 +49,12 @@
for _, test := range tests {
- viper.Set("ignoreFiles", test.ignoreFilesRegexpes)
+ v := viper.New()
+ v.Set("ignoreFiles", test.ignoreFilesRegexpes)
- if ignored := isNonProcessablePath(test.path); test.ignore != ignored {
+ s := NewSourceSpec(v, hugofs.NewMem(v))
+
+ if ignored := s.isNonProcessablePath(test.path); test.ignore != ignored {
t.Errorf("File not ignored. Expected: %t, got: %t", test.ignore, ignored)
}
}
--- a/source/file.go
+++ b/source/file.go
@@ -18,10 +18,21 @@
"path/filepath"
"strings"
+ "github.com/spf13/hugo/hugofs"
+
+ "github.com/spf13/hugo/config"
"github.com/spf13/hugo/helpers"
- "github.com/spf13/viper"
)
+type SourceSpec struct {
+ Cfg config.Provider
+ Fs *hugofs.Fs
+}
+
+func NewSourceSpec(cfg config.Provider, fs *hugofs.Fs) SourceSpec {
+ return SourceSpec{Cfg: cfg, Fs: fs}
+}
+
// File represents a source content file.
// All paths are relative from the source directory base
type File struct {
@@ -110,8 +121,8 @@
// NewFileWithContents creates a new File pointer with the given relative path and
// content. The language defaults to "en".
-func NewFileWithContents(relpath string, content io.Reader) *File {
- file := NewFile(relpath)
+func (sp SourceSpec) NewFileWithContents(relpath string, content io.Reader) *File {
+ file := sp.NewFile(relpath)
file.Contents = content
file.lang = "en"
return file
@@ -118,7 +129,7 @@
}
// NewFile creates a new File pointer with the given relative path.
-func NewFile(relpath string) *File {
+func (sp SourceSpec) NewFile(relpath string) *File {
f := &File{
relpath: relpath,
}
@@ -128,8 +139,8 @@
f.baseName = helpers.Filename(f.LogicalName())
lang := strings.TrimPrefix(filepath.Ext(f.baseName), ".")
- if _, ok := viper.GetStringMap("languages")[lang]; lang == "" || !ok {
- f.lang = viper.GetString("defaultContentLanguage")
+ if _, ok := sp.Cfg.GetStringMap("languages")[lang]; lang == "" || !ok {
+ f.lang = sp.Cfg.GetString("defaultContentLanguage")
f.translationBaseName = f.baseName
} else {
f.lang = lang
@@ -144,11 +155,11 @@
// NewFileFromAbs creates a new File pointer with the given full file path path and
// content.
-func NewFileFromAbs(base, fullpath string, content io.Reader) (f *File, err error) {
+func (sp SourceSpec) NewFileFromAbs(base, fullpath string, content io.Reader) (f *File, err error) {
var name string
if name, err = helpers.GetRelativePath(fullpath, base); err != nil {
return nil, err
}
- return NewFileWithContents(name, content), nil
+ return sp.NewFileWithContents(name, content), nil
}
--- a/source/file_test.go
+++ b/source/file_test.go
@@ -18,28 +18,40 @@
"strings"
"testing"
+ "github.com/spf13/hugo/hugofs"
+ "github.com/spf13/viper"
+
"github.com/stretchr/testify/assert"
)
func TestFileUniqueID(t *testing.T) {
+ ss := newTestSourceSpec()
+
f1 := File{uniqueID: "123"}
- f2 := NewFile("a")
+ f2 := ss.NewFile("a")
assert.Equal(t, "123", f1.UniqueID())
assert.Equal(t, "0cc175b9c0f1b6a831c399e269772661", f2.UniqueID())
- f3 := NewFile(filepath.FromSlash("test1/index.md"))
- f4 := NewFile(filepath.FromSlash("test2/index.md"))
+ f3 := ss.NewFile(filepath.FromSlash("test1/index.md"))
+ f4 := ss.NewFile(filepath.FromSlash("test2/index.md"))
assert.NotEqual(t, f3.UniqueID(), f4.UniqueID())
}
func TestFileString(t *testing.T) {
- assert.Equal(t, "abc", NewFileWithContents("a", strings.NewReader("abc")).String())
- assert.Equal(t, "", NewFile("a").String())
+ ss := newTestSourceSpec()
+ assert.Equal(t, "abc", ss.NewFileWithContents("a", strings.NewReader("abc")).String())
+ assert.Equal(t, "", ss.NewFile("a").String())
}
func TestFileBytes(t *testing.T) {
- assert.Equal(t, []byte("abc"), NewFileWithContents("a", strings.NewReader("abc")).Bytes())
- assert.Equal(t, []byte(""), NewFile("a").Bytes())
+ ss := newTestSourceSpec()
+ assert.Equal(t, []byte("abc"), ss.NewFileWithContents("a", strings.NewReader("abc")).Bytes())
+ assert.Equal(t, []byte(""), ss.NewFile("a").Bytes())
+}
+
+func newTestSourceSpec() SourceSpec {
+ v := viper.New()
+ return SourceSpec{Fs: hugofs.NewMem(v), Cfg: v}
}
--- a/source/filesystem.go
+++ b/source/filesystem.go
@@ -21,13 +21,10 @@
"runtime"
"strings"
- "github.com/spf13/hugo/hugofs"
- "golang.org/x/text/unicode/norm"
-
- "github.com/spf13/viper"
-
+ "github.com/spf13/cast"
"github.com/spf13/hugo/helpers"
jww "github.com/spf13/jwalterweatherman"
+ "golang.org/x/text/unicode/norm"
)
type Input interface {
@@ -39,11 +36,11 @@
Base string
AvoidPaths []string
- fs *hugofs.Fs
+ SourceSpec
}
-func NewFilesystem(fs *hugofs.Fs, base string, avoidPaths ...string) *Filesystem {
- return &Filesystem{fs: fs, Base: base, AvoidPaths: avoidPaths}
+func (sp SourceSpec) NewFilesystem(base string, avoidPaths ...string) *Filesystem {
+ return &Filesystem{SourceSpec: sp, Base: base, AvoidPaths: avoidPaths}
}
func (f *Filesystem) FilesByExts(exts ...string) []*File {
@@ -79,7 +76,7 @@
name = norm.NFC.String(name)
}
- file, err = NewFileFromAbs(f.Base, name, reader)
+ file, err = f.SourceSpec.NewFileFromAbs(f.Base, name, reader)
if err == nil {
f.files = append(f.files, file)
@@ -98,7 +95,7 @@
return err
}
if b {
- rd, err := NewLazyFileReader(f.fs.Source, filePath)
+ rd, err := NewLazyFileReader(f.Fs.Source, filePath)
if err != nil {
return err
}
@@ -107,10 +104,10 @@
return err
}
- if f.fs == nil {
+ if f.Fs == nil {
panic("Must have a fs")
}
- err := helpers.SymbolicWalk(f.fs.Source, f.Base, walker)
+ err := helpers.SymbolicWalk(f.Fs.Source, f.Base, walker)
if err != nil {
jww.ERROR.Println(err)
@@ -128,7 +125,7 @@
jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", filePath, err)
return false, nil
}
- linkfi, err := f.fs.Source.Stat(link)
+ linkfi, err := f.Fs.Source.Stat(link)
if err != nil {
jww.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
return false, nil
@@ -140,13 +137,13 @@
}
if fi.IsDir() {
- if f.avoid(filePath) || isNonProcessablePath(filePath) {
+ if f.avoid(filePath) || f.isNonProcessablePath(filePath) {
return false, filepath.SkipDir
}
return false, nil
}
- if isNonProcessablePath(filePath) {
+ if f.isNonProcessablePath(filePath) {
return false, nil
}
return true, nil
@@ -161,7 +158,7 @@
return false
}
-func isNonProcessablePath(filePath string) bool {
+func (s SourceSpec) isNonProcessablePath(filePath string) bool {
base := filepath.Base(filePath)
if strings.HasPrefix(base, ".") ||
strings.HasPrefix(base, "#") ||
@@ -168,7 +165,7 @@
strings.HasSuffix(base, "~") {
return true
}
- ignoreFiles := viper.GetStringSlice("ignoreFiles")
+ ignoreFiles := cast.ToStringSlice(s.Cfg.Get("ignoreFiles"))
if len(ignoreFiles) > 0 {
for _, ignorePattern := range ignoreFiles {
match, err := regexp.MatchString(ignorePattern, filePath)
--- a/source/filesystem_test.go
+++ b/source/filesystem_test.go
@@ -19,12 +19,11 @@
"runtime"
"strings"
"testing"
-
- "github.com/spf13/hugo/hugofs"
)
func TestEmptySourceFilesystem(t *testing.T) {
- src := NewFilesystem(hugofs.NewMem(), "Empty")
+ ss := newTestSourceSpec()
+ src := ss.NewFilesystem("Empty")
if len(src.Files()) != 0 {
t.Errorf("new filesystem should contain 0 files.")
}
@@ -39,12 +38,12 @@
}
func TestAddFile(t *testing.T) {
- fs := hugofs.NewMem()
+ ss := newTestSourceSpec()
tests := platformPaths
for _, test := range tests {
base := platformBase
- srcDefault := NewFilesystem(fs, "")
- srcWithBase := NewFilesystem(fs, base)
+ srcDefault := ss.NewFilesystem("")
+ srcWithBase := ss.NewFilesystem(base)
for _, src := range []*Filesystem{srcDefault, srcWithBase} {
@@ -100,10 +99,10 @@
{NFC: "é", NFD: "\x65\xcc\x81"},
}
- fs := hugofs.NewMem()
+ ss := newTestSourceSpec()
for _, path := range paths {
- src := NewFilesystem(fs, "")
+ src := ss.NewFilesystem("")
_ = src.add(path.NFD, strings.NewReader(""))
f := src.Files()[0]
if f.BaseFileName() != path.NFC {
--- a/source/inmemory.go
+++ b/source/inmemory.go
@@ -13,8 +13,6 @@
package source
-import "bytes"
-
type ByteSource struct {
Name string
Content []byte
@@ -22,16 +20,4 @@
func (b *ByteSource) String() string {
return b.Name + " " + string(b.Content)
-}
-
-type InMemorySource struct {
- ByteSource []ByteSource
-}
-
-func (i *InMemorySource) Files() (files []*File) {
- files = make([]*File, len(i.ByteSource))
- for i, fake := range i.ByteSource {
- files[i] = NewFileWithContents(fake.Name, bytes.NewReader(fake.Content))
- }
- return
}
--- a/target/page_test.go
+++ b/target/page_test.go
@@ -18,10 +18,11 @@
"testing"
"github.com/spf13/hugo/hugofs"
+ "github.com/spf13/viper"
)
func TestPageTranslator(t *testing.T) {
- fs := hugofs.NewMem()
+ fs := hugofs.NewMem(viper.New())
tests := []struct {
content string
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -21,6 +21,8 @@
"path/filepath"
"strings"
+ "sync"
+
"github.com/eknkc/amber"
"github.com/spf13/afero"
bp "github.com/spf13/hugo/bufferpool"
@@ -31,6 +33,9 @@
// TODO(bep) globals get rid of the rest of the jww.ERR etc.
+// Protecting global map access (Amber)
+var amberMu sync.Mutex
+
type templateErr struct {
name string
err error
@@ -132,6 +137,7 @@
t.amberFuncMap = template.FuncMap{}
+ amberMu.Lock()
for k, v := range amber.FuncMap {
t.amberFuncMap[k] = v
}
@@ -143,6 +149,7 @@
panic("should never be invoked")
}
}
+ amberMu.Unlock()
}
@@ -362,7 +369,9 @@
return err
}
+ amberMu.Lock()
templ, err := t.CompileAmberWithTemplate(b, path, t.New(templateName))
+ amberMu.Unlock()
if err != nil {
return err
}
@@ -482,11 +491,11 @@
}
if needsBase {
- layoutDir := helpers.GetLayoutDirPath()
+ layoutDir := t.PathSpec.GetLayoutDirPath()
currBaseFilename := fmt.Sprintf("%s-%s", helpers.Filename(path), baseFileName)
templateDir := filepath.Dir(path)
- themeDir := filepath.Join(helpers.GetThemeDir())
- relativeThemeLayoutsDir := filepath.Join(helpers.GetRelativeThemeDir(), "layouts")
+ themeDir := filepath.Join(t.PathSpec.GetThemeDir())
+ relativeThemeLayoutsDir := filepath.Join(t.PathSpec.GetRelativeThemeDir(), "layouts")
var baseTemplatedDir string
--- a/tpl/template_ast_transformers_test.go
+++ b/tpl/template_ast_transformers_test.go
@@ -113,6 +113,7 @@
)
func TestParamsKeysToLower(t *testing.T) {
+ t.Parallel()
require.Error(t, applyTemplateTransformers(nil))
@@ -190,6 +191,7 @@
}
func TestParamsKeysToLowerVars(t *testing.T) {
+ t.Parallel()
var (
ctx = map[string]interface{}{
"Params": map[string]interface{}{
@@ -227,6 +229,7 @@
}
func TestParamsKeysToLowerInBlockTemplate(t *testing.T) {
+ t.Parallel()
var (
ctx = map[string]interface{}{
--- a/tpl/template_func_truncate_test.go
+++ b/tpl/template_func_truncate_test.go
@@ -21,6 +21,7 @@
)
func TestTruncate(t *testing.T) {
+ t.Parallel()
var err error
cases := []struct {
v1 interface{}
--- a/tpl/template_funcs.go
+++ b/tpl/template_funcs.go
@@ -46,7 +46,6 @@
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
// Importing image codecs for image.DecodeConfig
_ "image/gif"
@@ -58,7 +57,6 @@
type templateFuncster struct {
funcMap template.FuncMap
cachedPartials partialCache
-
*deps.Deps
}
@@ -398,6 +396,7 @@
}
// ResetCaches resets all caches that might be used during build.
+// TODO(bep) globals move image config cache to funcster
func ResetCaches() {
resetImageConfigCache()
}
@@ -1357,7 +1356,7 @@
}
// highlight returns an HTML string with syntax highlighting applied.
-func highlight(in interface{}, lang, opts string) (template.HTML, error) {
+func (t *templateFuncster) highlight(in interface{}, lang, opts string) (template.HTML, error) {
str, err := cast.ToStringE(in)
if err != nil {
@@ -1364,7 +1363,7 @@
return "", err
}
- return template.HTML(helpers.Highlight(html.UnescapeString(str), lang, opts)), nil
+ return template.HTML(helpers.Highlight(t.Cfg, html.UnescapeString(str), lang, opts)), nil
}
var markdownTrimPrefix = []byte("<p>")
@@ -1371,17 +1370,15 @@
var markdownTrimSuffix = []byte("</p>\n")
// markdownify renders a given string from Markdown to HTML.
-func markdownify(in interface{}) (template.HTML, error) {
+func (t *templateFuncster) markdownify(in interface{}) (template.HTML, error) {
text, err := cast.ToStringE(in)
if err != nil {
return "", err
}
- language := viper.Get("currentContentLanguage").(*helpers.Language)
-
- m := helpers.RenderBytes(&helpers.RenderingContext{
- ConfigProvider: language,
- Content: []byte(text), PageFmt: "markdown"})
+ m := t.ContentSpec.RenderBytes(&helpers.RenderingContext{
+ Cfg: t.Cfg,
+ Content: []byte(text), PageFmt: "markdown"})
m = bytes.TrimPrefix(m, markdownTrimPrefix)
m = bytes.TrimSuffix(m, markdownTrimSuffix)
return template.HTML(m), nil
@@ -2143,7 +2140,7 @@
"getenv": getenv,
"gt": gt,
"hasPrefix": hasPrefix,
- "highlight": highlight,
+ "highlight": t.highlight,
"htmlEscape": htmlEscape,
"htmlUnescape": htmlUnescape,
"humanize": humanize,
@@ -2159,7 +2156,7 @@
"le": le,
"lower": lower,
"lt": lt,
- "markdownify": markdownify,
+ "markdownify": t.markdownify,
"md5": md5,
"mod": mod,
"modBool": modBool,
@@ -2211,8 +2208,8 @@
"upper": upper,
"urlize": t.PathSpec.URLize,
"where": where,
- "i18n": i18nTranslate,
- "T": i18nTranslate,
+ "i18n": t.Translate,
+ "T": t.Translate,
}
t.funcMap = funcMap
--- a/tpl/template_funcs_test.go
+++ b/tpl/template_funcs_test.go
@@ -42,7 +42,9 @@
"github.com/spf13/afero"
"github.com/spf13/cast"
+ "github.com/spf13/hugo/config"
"github.com/spf13/hugo/hugofs"
+ "github.com/spf13/hugo/i18n"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
@@ -53,12 +55,16 @@
logger = jww.NewNotepad(jww.LevelFatal, jww.LevelFatal, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
)
-func newDefaultDepsCfg() deps.DepsCfg {
+func newDepsConfig(cfg config.Provider) deps.DepsCfg {
+ l := helpers.NewLanguage("en", cfg)
+ l.Set("i18nDir", "i18n")
return deps.DepsCfg{
- Language: helpers.NewLanguage("en"),
- Fs: hugofs.NewMem(),
- Logger: logger,
- TemplateProvider: DefaultTemplateProvider,
+ Language: l,
+ Cfg: cfg,
+ Fs: hugofs.NewMem(l),
+ Logger: logger,
+ TemplateProvider: DefaultTemplateProvider,
+ TranslationProvider: i18n.NewTranslationProvider(),
}
}
@@ -88,23 +94,18 @@
return tp == tstLt || tp == tstLe
}
-func tstInitTemplates() {
- viper.Set("CurrentContentLanguage", helpers.NewLanguage("en"))
- helpers.ResetConfigProvider()
-}
-
func TestFuncsInTemplate(t *testing.T) {
+ t.Parallel()
- testReset()
-
workingDir := "/home/hugo"
- viper.Set("workingDir", workingDir)
- viper.Set("currentContentLanguage", helpers.NewDefaultLanguage())
- viper.Set("multilingual", true)
+ v := viper.New()
- fs := hugofs.NewMem()
+ v.Set("workingDir", workingDir)
+ v.Set("multilingual", true)
+ fs := hugofs.NewMem(v)
+
afero.WriteFile(fs.Source, filepath.Join(workingDir, "README.txt"), []byte("Hugo Rocks!"), 0755)
// Add the examples from the docs: As a smoke test and to make sure the examples work.
@@ -268,11 +269,10 @@
data.Section = "blog"
data.Params = map[string]interface{}{"langCode": "en"}
- viper.Set("baseURL", "http://mysite.com/hugo/")
+ v.Set("baseURL", "http://mysite.com/hugo/")
+ v.Set("CurrentContentLanguage", helpers.NewLanguage("en", v))
- tstInitTemplates()
-
- config := newDefaultDepsCfg()
+ config := newDepsConfig(v)
config.WithTemplate = func(templ tplapi.Template) error {
if _, err := templ.New("test").Parse(in); err != nil {
t.Fatal("Got error on parse", err)
@@ -282,7 +282,7 @@
config.Fs = fs
d := deps.New(config)
- if err := d.LoadTemplates(); err != nil {
+ if err := d.LoadResources(); err != nil {
t.Fatal(err)
}
@@ -300,6 +300,7 @@
}
func TestCompare(t *testing.T) {
+ t.Parallel()
for _, this := range []struct {
tstCompareType
funcUnderTest func(a, b interface{}) bool
@@ -370,6 +371,7 @@
}
func TestMod(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
a interface{}
b interface{}
@@ -405,6 +407,7 @@
}
func TestModBool(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
a interface{}
b interface{}
@@ -445,6 +448,7 @@
}
func TestFirst(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
count interface{}
sequence interface{}
@@ -480,6 +484,7 @@
}
func TestLast(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
count interface{}
sequence interface{}
@@ -515,6 +520,7 @@
}
func TestAfter(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
count interface{}
sequence interface{}
@@ -550,6 +556,7 @@
}
func TestShuffleInputAndOutputFormat(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
sequence interface{}
success bool
@@ -588,6 +595,7 @@
}
func TestShuffleRandomising(t *testing.T) {
+ t.Parallel()
// Note that this test can fail with false negative result if the shuffle
// of the sequence happens to be the same as the original sequence. However
// the propability of the event is 10^-158 which is negligible.
@@ -615,6 +623,7 @@
}
func TestDictionary(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
v1 []interface{}
expecterr bool
@@ -647,14 +656,16 @@
}
func TestImageConfig(t *testing.T) {
- testReset()
+ t.Parallel()
workingDir := "/home/hugo"
- viper.Set("workingDir", workingDir)
+ v := viper.New()
- f := newTestFuncster()
+ v.Set("workingDir", workingDir)
+ f := newTestFuncsterWithViper(v)
+
for i, this := range []struct {
resetCache bool
path string
@@ -754,6 +765,7 @@
}
func TestIn(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
v1 interface{}
v2 interface{}
@@ -783,6 +795,7 @@
}
func TestSlicestr(t *testing.T) {
+ t.Parallel()
var err error
for i, this := range []struct {
v1 interface{}
@@ -848,6 +861,7 @@
}
func TestHasPrefix(t *testing.T) {
+ t.Parallel()
cases := []struct {
s interface{}
prefix interface{}
@@ -875,6 +889,7 @@
}
func TestSubstr(t *testing.T) {
+ t.Parallel()
var err error
var n int
for i, this := range []struct {
@@ -952,6 +967,7 @@
}
func TestSplit(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
v1 interface{}
v2 string
@@ -982,6 +998,7 @@
}
func TestIntersect(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
sequence1 interface{}
sequence2 interface{}
@@ -1025,6 +1042,7 @@
}
func TestIsSet(t *testing.T) {
+ t.Parallel()
aSlice := []interface{}{1, 2, 3, 5}
aMap := map[string]interface{}{"a": 1, "b": 2}
@@ -1074,6 +1092,7 @@
}
func TestTimeUnix(t *testing.T) {
+ t.Parallel()
var sec int64 = 1234567890
tv := reflect.ValueOf(time.Unix(sec, 0))
i := 1
@@ -1096,6 +1115,7 @@
}
func TestEvaluateSubElem(t *testing.T) {
+ t.Parallel()
tstx := TstX{A: "foo", B: "bar"}
var inner struct {
S fmt.Stringer
@@ -1146,6 +1166,7 @@
}
func TestCheckCondition(t *testing.T) {
+ t.Parallel()
type expect struct {
result bool
isError bool
@@ -1266,6 +1287,7 @@
}
func TestWhere(t *testing.T) {
+ t.Parallel()
type Mid struct {
Tst TstX
@@ -1671,6 +1693,7 @@
}
func TestDelimit(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
sequence interface{}
delimiter interface{}
@@ -1720,6 +1743,7 @@
}
func TestSort(t *testing.T) {
+ t.Parallel()
type ts struct {
MyInt int
MyFloat float64
@@ -1932,6 +1956,7 @@
}
func TestReturnWhenSet(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
data interface{}
key interface{}
@@ -1957,8 +1982,11 @@
}
func TestMarkdownify(t *testing.T) {
- viper.Set("currentContentLanguage", helpers.NewDefaultLanguage())
+ t.Parallel()
+ v := viper.New()
+ f := newTestFuncsterWithViper(v)
+
for i, this := range []struct {
in interface{}
expect interface{}
@@ -1966,7 +1994,7 @@
{"Hello **World!**", template.HTML("Hello <strong>World!</strong>")},
{[]byte("Hello Bytes **World!**"), template.HTML("Hello Bytes <strong>World!</strong>")},
} {
- result, err := markdownify(this.in)
+ result, err := f.markdownify(this.in)
if err != nil {
t.Fatalf("[%d] unexpected error in markdownify: %s", i, err)
}
@@ -1975,12 +2003,13 @@
}
}
- if _, err := markdownify(t); err == nil {
+ if _, err := f.markdownify(t); err == nil {
t.Fatalf("markdownify should have errored")
}
}
func TestApply(t *testing.T) {
+ t.Parallel()
f := newTestFuncster()
@@ -2024,6 +2053,7 @@
}
func TestChomp(t *testing.T) {
+ t.Parallel()
base := "\n This is\na story "
for i, item := range []string{
"\n", "\n\n",
@@ -2046,6 +2076,7 @@
}
func TestLower(t *testing.T) {
+ t.Parallel()
cases := []struct {
s interface{}
want string
@@ -2069,6 +2100,7 @@
}
func TestTitle(t *testing.T) {
+ t.Parallel()
cases := []struct {
s interface{}
want string
@@ -2092,6 +2124,7 @@
}
func TestUpper(t *testing.T) {
+ t.Parallel()
cases := []struct {
s interface{}
want string
@@ -2115,9 +2148,13 @@
}
func TestHighlight(t *testing.T) {
+ t.Parallel()
code := "func boo() {}"
- highlighted, err := highlight(code, "go", "")
+ f := newTestFuncster()
+
+ highlighted, err := f.highlight(code, "go", "")
+
if err != nil {
t.Fatal("Highlight returned error:", err)
}
@@ -2127,7 +2164,7 @@
t.Errorf("Highlight mismatch, got %v", highlighted)
}
- _, err = highlight(t, "go", "")
+ _, err = f.highlight(t, "go", "")
if err == nil {
t.Error("Expected highlight error")
@@ -2135,6 +2172,7 @@
}
func TestInflect(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
inflectFunc func(i interface{}) (string, error)
in interface{}
@@ -2169,6 +2207,7 @@
}
func TestCounterFuncs(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
countFunc func(i interface{}) (int, error)
in string
@@ -2195,6 +2234,7 @@
}
func TestReplace(t *testing.T) {
+ t.Parallel()
v, _ := replace("aab", "a", "b")
assert.Equal(t, "bbb", v)
v, _ = replace("11a11", 1, 2)
@@ -2210,6 +2250,7 @@
}
func TestReplaceRE(t *testing.T) {
+ t.Parallel()
for i, val := range []struct {
pattern interface{}
repl interface{}
@@ -2234,6 +2275,7 @@
}
func TestFindRE(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
expr string
content interface{}
@@ -2264,6 +2306,7 @@
}
func TestTrim(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
v1 interface{}
@@ -2294,6 +2337,7 @@
}
func TestDateFormat(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
layout string
value interface{}
@@ -2328,6 +2372,7 @@
}
func TestDefaultFunc(t *testing.T) {
+ t.Parallel()
then := time.Now()
now := time.Now()
@@ -2385,6 +2430,7 @@
}
func TestDefault(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
input interface{}
tpl string
@@ -2414,6 +2460,7 @@
}
func TestSafeHTML(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
str string
tmplStr string
@@ -2454,6 +2501,7 @@
}
func TestSafeHTMLAttr(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
str string
tmplStr string
@@ -2494,6 +2542,7 @@
}
func TestSafeCSS(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
str string
tmplStr string
@@ -2535,6 +2584,7 @@
// TODO(bep) what is this? Also look above.
func TestSafeJS(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
str string
tmplStr string
@@ -2576,6 +2626,7 @@
// TODO(bep) what is this?
func TestSafeURL(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
str string
tmplStr string
@@ -2616,6 +2667,7 @@
}
func TestBase64Decode(t *testing.T) {
+ t.Parallel()
testStr := "abc123!?$*&()'-=@~"
enc := base64.StdEncoding.EncodeToString([]byte(testStr))
result, err := base64Decode(enc)
@@ -2635,6 +2687,7 @@
}
func TestBase64Encode(t *testing.T) {
+ t.Parallel()
testStr := "YWJjMTIzIT8kKiYoKSctPUB+"
dec, err := base64.StdEncoding.DecodeString(testStr)
@@ -2659,6 +2712,7 @@
}
func TestMD5(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
input string
expectedHash string
@@ -2683,6 +2737,7 @@
}
func TestSHA1(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
input string
expectedHash string
@@ -2707,6 +2762,7 @@
}
func TestSHA256(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
input string
expectedHash string
@@ -2731,14 +2787,16 @@
}
func TestReadFile(t *testing.T) {
- testReset()
+ t.Parallel()
workingDir := "/home/hugo"
- viper.Set("workingDir", workingDir)
+ v := viper.New()
- f := newTestFuncster()
+ v.Set("workingDir", workingDir)
+ f := newTestFuncsterWithViper(v)
+
afero.WriteFile(f.Fs.Source, filepath.Join(workingDir, "/f/f1.txt"), []byte("f1-content"), 0755)
afero.WriteFile(f.Fs.Source, filepath.Join("/home", "f2.txt"), []byte("f2-content"), 0755)
@@ -2770,6 +2828,7 @@
}
func TestPartialCached(t *testing.T) {
+ t.Parallel()
testCases := []struct {
name string
partial string
@@ -2793,7 +2852,6 @@
data.Section = "blog"
data.Params = map[string]interface{}{"langCode": "en"}
- tstInitTemplates()
for i, tc := range testCases {
var tmp string
if tc.variant != "" {
@@ -2802,9 +2860,9 @@
tmp = tc.tmpl
}
- cfg := newDefaultDepsCfg()
+ config := newDepsConfig(viper.New())
- cfg.WithTemplate = func(templ tplapi.Template) error {
+ config.WithTemplate = func(templ tplapi.Template) error {
err := templ.AddTemplate("testroot", tmp)
if err != nil {
return err
@@ -2817,8 +2875,8 @@
return nil
}
- de := deps.New(cfg)
- require.NoError(t, de.LoadTemplates())
+ de := deps.New(config)
+ require.NoError(t, de.LoadResources())
buf := new(bytes.Buffer)
templ := de.Tmpl.Lookup("testroot")
@@ -2842,8 +2900,8 @@
}
func BenchmarkPartial(b *testing.B) {
- cfg := newDefaultDepsCfg()
- cfg.WithTemplate = func(templ tplapi.Template) error {
+ config := newDepsConfig(viper.New())
+ config.WithTemplate = func(templ tplapi.Template) error {
err := templ.AddTemplate("testroot", `{{ partial "bench1" . }}`)
if err != nil {
return err
@@ -2856,8 +2914,8 @@
return nil
}
- de := deps.New(cfg)
- require.NoError(b, de.LoadTemplates())
+ de := deps.New(config)
+ require.NoError(b, de.LoadResources())
buf := new(bytes.Buffer)
tmpl := de.Tmpl.Lookup("testroot")
@@ -2873,8 +2931,8 @@
}
func BenchmarkPartialCached(b *testing.B) {
- cfg := newDefaultDepsCfg()
- cfg.WithTemplate = func(templ tplapi.Template) error {
+ config := newDepsConfig(viper.New())
+ config.WithTemplate = func(templ tplapi.Template) error {
err := templ.AddTemplate("testroot", `{{ partialCached "bench1" . }}`)
if err != nil {
return err
@@ -2887,8 +2945,8 @@
return nil
}
- de := deps.New(cfg)
- require.NoError(b, de.LoadTemplates())
+ de := deps.New(config)
+ require.NoError(b, de.LoadResources())
buf := new(bytes.Buffer)
tmpl := de.Tmpl.Lookup("testroot")
@@ -2904,9 +2962,14 @@
}
func newTestFuncster() *templateFuncster {
- cfg := newDefaultDepsCfg()
- d := deps.New(cfg)
- if err := d.LoadTemplates(); err != nil {
+ return newTestFuncsterWithViper(viper.New())
+}
+
+func newTestFuncsterWithViper(v *viper.Viper) *templateFuncster {
+ config := newDepsConfig(v)
+ d := deps.New(config)
+
+ if err := d.LoadResources(); err != nil {
panic(err)
}
@@ -2914,8 +2977,8 @@
}
func newTestTemplate(t *testing.T, name, template string) *template.Template {
- cfg := newDefaultDepsCfg()
- cfg.WithTemplate = func(templ tplapi.Template) error {
+ config := newDepsConfig(viper.New())
+ config.WithTemplate = func(templ tplapi.Template) error {
err := templ.AddTemplate(name, template)
if err != nil {
return err
@@ -2923,8 +2986,8 @@
return nil
}
- de := deps.New(cfg)
- require.NoError(t, de.LoadTemplates())
+ de := deps.New(config)
+ require.NoError(t, de.LoadResources())
return de.Tmpl.Lookup(name)
}
--- a/tpl/template_i18n.go
+++ /dev/null
@@ -1,100 +1,0 @@
-// Copyright 2015 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 tpl
-
-import (
- "github.com/nicksnyder/go-i18n/i18n/bundle"
- "github.com/spf13/hugo/helpers"
- jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
-)
-
-var (
- // Logi18nWarnings set to true to print warnings about missing language strings
- Logi18nWarnings bool
- i18nWarningLogger = helpers.NewDistinctFeedbackLogger()
- currentLanguage *helpers.Language
-)
-
-type translate struct {
- translateFuncs map[string]bundle.TranslateFunc
-
- current bundle.TranslateFunc
-}
-
-// TODO(bep) global translator
-var translator *translate
-
-// SetTranslateLang sets the translations language to use during template processing.
-// This construction is unfortunate, but the template system is currently global.
-func SetTranslateLang(language *helpers.Language) error {
- currentLanguage = language
- if f, ok := translator.translateFuncs[language.Lang]; ok {
- translator.current = f
- } else {
- jww.WARN.Printf("Translation func for language %v not found, use default.", language.Lang)
- translator.current = translator.translateFuncs[viper.GetString("defaultContentLanguage")]
- }
- return nil
-}
-
-// SetI18nTfuncs sets the language bundle to be used for i18n.
-func SetI18nTfuncs(bndl *bundle.Bundle) {
- translator = &translate{translateFuncs: make(map[string]bundle.TranslateFunc)}
- defaultContentLanguage := viper.GetString("defaultContentLanguage")
- var (
- defaultT bundle.TranslateFunc
- err error
- )
-
- defaultT, err = bndl.Tfunc(defaultContentLanguage)
-
- if err != nil {
- jww.WARN.Printf("No translation bundle found for default language %q", defaultContentLanguage)
- }
-
- enableMissingTranslationPlaceholders := viper.GetBool("enableMissingTranslationPlaceholders")
- for _, lang := range bndl.LanguageTags() {
- currentLang := lang
-
- translator.translateFuncs[currentLang] = func(translationID string, args ...interface{}) string {
- tFunc, err := bndl.Tfunc(currentLang)
- if err != nil {
- jww.WARN.Printf("could not load translations for language %q (%s), will use default content language.\n", lang, err)
- } else if translated := tFunc(translationID, args...); translated != translationID {
- return translated
- }
- if Logi18nWarnings {
- i18nWarningLogger.Printf("i18n|MISSING_TRANSLATION|%s|%s", currentLang, translationID)
- }
- if enableMissingTranslationPlaceholders {
- return "[i18n] " + translationID
- }
- if defaultT != nil {
- if translated := defaultT(translationID, args...); translated != translationID {
- return translated
- }
- }
- return ""
- }
- }
-}
-
-func i18nTranslate(id string, args ...interface{}) (string, error) {
- if translator == nil || translator.current == nil {
- helpers.DistinctErrorLog.Printf("i18n not initialized, check that you have language file (in i18n) that matches the site language or the default language.")
- return "", nil
- }
- return translator.current(id, args...), nil
-}
--- a/tpl/template_i18n_test.go
+++ /dev/null
@@ -1,149 +1,0 @@
-// Copyright 2015 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 tpl
-
-import (
- "testing"
-
- "github.com/nicksnyder/go-i18n/i18n/bundle"
- "github.com/spf13/hugo/helpers"
- "github.com/spf13/viper"
- "github.com/stretchr/testify/assert"
-)
-
-type i18nTest struct {
- data map[string][]byte
- args interface{}
- lang, id, expected, expectedFlag string
-}
-
-var i18nTests = []i18nTest{
- // All translations present
- {
- data: map[string][]byte{
- "en.yaml": []byte("- id: \"hello\"\n translation: \"Hello, World!\""),
- "es.yaml": []byte("- id: \"hello\"\n translation: \"¡Hola, Mundo!\""),
- },
- args: nil,
- lang: "es",
- id: "hello",
- expected: "¡Hola, Mundo!",
- expectedFlag: "¡Hola, Mundo!",
- },
- // Translation missing in current language but present in default
- {
- data: map[string][]byte{
- "en.yaml": []byte("- id: \"hello\"\n translation: \"Hello, World!\""),
- "es.yaml": []byte("- id: \"goodbye\"\n translation: \"¡Adiós, Mundo!\""),
- },
- args: nil,
- lang: "es",
- id: "hello",
- expected: "Hello, World!",
- expectedFlag: "[i18n] hello",
- },
- // Translation missing in default language but present in current
- {
- data: map[string][]byte{
- "en.yaml": []byte("- id: \"goodybe\"\n translation: \"Goodbye, World!\""),
- "es.yaml": []byte("- id: \"hello\"\n translation: \"¡Hola, Mundo!\""),
- },
- args: nil,
- lang: "es",
- id: "hello",
- expected: "¡Hola, Mundo!",
- expectedFlag: "¡Hola, Mundo!",
- },
- // Translation missing in both default and current language
- {
- data: map[string][]byte{
- "en.yaml": []byte("- id: \"goodbye\"\n translation: \"Goodbye, World!\""),
- "es.yaml": []byte("- id: \"goodbye\"\n translation: \"¡Adiós, Mundo!\""),
- },
- args: nil,
- lang: "es",
- id: "hello",
- expected: "",
- expectedFlag: "[i18n] hello",
- },
- // Default translation file missing or empty
- {
- data: map[string][]byte{
- "en.yaml": []byte(""),
- },
- args: nil,
- lang: "es",
- id: "hello",
- expected: "",
- expectedFlag: "[i18n] hello",
- },
- // Context provided
- {
- data: map[string][]byte{
- "en.yaml": []byte("- id: \"wordCount\"\n translation: \"Hello, {{.WordCount}} people!\""),
- "es.yaml": []byte("- id: \"wordCount\"\n translation: \"¡Hola, {{.WordCount}} gente!\""),
- },
- args: struct {
- WordCount int
- }{
- 50,
- },
- lang: "es",
- id: "wordCount",
- expected: "¡Hola, 50 gente!",
- expectedFlag: "¡Hola, 50 gente!",
- },
-}
-
-func doTestI18nTranslate(t *testing.T, data map[string][]byte, lang, id string, args interface{}) string {
- i18nBundle := bundle.New()
-
- for file, content := range data {
- err := i18nBundle.ParseTranslationFileBytes(file, content)
- if err != nil {
- t.Errorf("Error parsing translation file: %s", err)
- }
- }
-
- SetI18nTfuncs(i18nBundle)
- SetTranslateLang(helpers.NewLanguage(lang))
-
- translated, err := i18nTranslate(id, args)
- if err != nil {
- t.Errorf("Error translating '%s': %s", id, err)
- }
- return translated
-}
-
-func TestI18nTranslate(t *testing.T) {
- var actual, expected string
-
- viper.SetDefault("defaultContentLanguage", "en")
- viper.Set("currentContentLanguage", helpers.NewLanguage("en"))
-
- // Test without and with placeholders
- for _, enablePlaceholders := range []bool{false, true} {
- viper.Set("enableMissingTranslationPlaceholders", enablePlaceholders)
-
- for _, test := range i18nTests {
- if enablePlaceholders {
- expected = test.expectedFlag
- } else {
- expected = test.expected
- }
- actual = doTestI18nTranslate(t, test.data, test.lang, test.id, test.args)
- assert.Equal(t, expected, actual)
- }
- }
-}
--- a/tpl/template_resources.go
+++ b/tpl/template_resources.go
@@ -27,9 +27,9 @@
"time"
"github.com/spf13/afero"
+ "github.com/spf13/hugo/config"
"github.com/spf13/hugo/helpers"
jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
)
var (
@@ -63,17 +63,17 @@
}
// getCacheFileID returns the cache ID for a string
-func getCacheFileID(id string) string {
- return viper.GetString("cacheDir") + url.QueryEscape(id)
+func getCacheFileID(cfg config.Provider, id string) string {
+ return cfg.GetString("cacheDir") + url.QueryEscape(id)
}
// resGetCache returns the content for an ID from the file cache or an error
// if the file is not found returns nil,nil
-func resGetCache(id string, fs afero.Fs, ignoreCache bool) ([]byte, error) {
+func resGetCache(id string, fs afero.Fs, cfg config.Provider, ignoreCache bool) ([]byte, error) {
if ignoreCache {
return nil, nil
}
- fID := getCacheFileID(id)
+ fID := getCacheFileID(cfg, id)
isExists, err := helpers.Exists(fID, fs)
if err != nil {
return nil, err
@@ -87,11 +87,11 @@
}
// resWriteCache writes bytes to an ID into the file cache
-func resWriteCache(id string, c []byte, fs afero.Fs, ignoreCache bool) error {
+func resWriteCache(id string, c []byte, fs afero.Fs, cfg config.Provider, ignoreCache bool) error {
if ignoreCache {
return nil
}
- fID := getCacheFileID(id)
+ fID := getCacheFileID(cfg, id)
f, err := fs.Create(fID)
if err != nil {
return errors.New("Error: " + err.Error() + ". Failed to create file: " + fID)
@@ -107,13 +107,13 @@
return nil
}
-func resDeleteCache(id string, fs afero.Fs) error {
- return fs.Remove(getCacheFileID(id))
+func resDeleteCache(id string, fs afero.Fs, cfg config.Provider) error {
+ return fs.Remove(getCacheFileID(cfg, id))
}
// resGetRemote loads the content of a remote file. This method is thread safe.
-func resGetRemote(url string, fs afero.Fs, hc *http.Client) ([]byte, error) {
- c, err := resGetCache(url, fs, viper.GetBool("ignoreCache"))
+func resGetRemote(url string, fs afero.Fs, cfg config.Provider, hc *http.Client) ([]byte, error) {
+ c, err := resGetCache(url, fs, cfg, cfg.GetBool("ignoreCache"))
if c != nil && err == nil {
return c, nil
}
@@ -126,7 +126,7 @@
defer func() { remoteURLLock.URLUnlock(url) }()
// avoid multiple locks due to calling resGetCache twice
- c, err = resGetCache(url, fs, viper.GetBool("ignoreCache"))
+ c, err = resGetCache(url, fs, cfg, cfg.GetBool("ignoreCache"))
if c != nil && err == nil {
return c, nil
}
@@ -144,17 +144,17 @@
if err != nil {
return nil, err
}
- err = resWriteCache(url, c, fs, viper.GetBool("ignoreCache"))
+ err = resWriteCache(url, c, fs, cfg, cfg.GetBool("ignoreCache"))
if err != nil {
return nil, err
}
- jww.INFO.Printf("... and cached to: %s", getCacheFileID(url))
+ jww.INFO.Printf("... and cached to: %s", getCacheFileID(cfg, url))
return c, nil
}
// resGetLocal loads the content of a local file
-func resGetLocal(url string, fs afero.Fs) ([]byte, error) {
- filename := filepath.Join(viper.GetString("workingDir"), url)
+func resGetLocal(url string, fs afero.Fs, cfg config.Provider) ([]byte, error) {
+ filename := filepath.Join(cfg.GetString("workingDir"), url)
if e, err := helpers.Exists(filename, fs); !e {
return nil, err
}
@@ -169,9 +169,9 @@
return nil, nil
}
if strings.Contains(url, "://") {
- return resGetRemote(url, t.Fs.Source, http.DefaultClient)
+ return resGetRemote(url, t.Fs.Source, t.Cfg, http.DefaultClient)
}
- return resGetLocal(url, t.Fs.Source)
+ return resGetLocal(url, t.Fs.Source, t.Cfg)
}
// getJSON expects one or n-parts of a URL to a resource which can either be a local or a remote one.
@@ -193,7 +193,7 @@
jww.ERROR.Printf("Cannot read json from resource %s with error message %s", url, err)
jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep)
time.Sleep(resSleep)
- resDeleteCache(url, t.Fs.Source)
+ resDeleteCache(url, t.Fs.Source, t.Cfg)
continue
}
break
@@ -226,7 +226,7 @@
var clearCacheSleep = func(i int, u string) {
jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep)
time.Sleep(resSleep)
- resDeleteCache(url, t.Fs.Source)
+ resDeleteCache(url, t.Fs.Source, t.Cfg)
}
for i := 0; i <= resRetries; i++ {
--- a/tpl/template_resources_test.go
+++ b/tpl/template_resources_test.go
@@ -19,10 +19,8 @@
"net/http"
"net/http/httptest"
"net/url"
- "os"
"strings"
"testing"
- "time"
"github.com/spf13/afero"
"github.com/spf13/hugo/helpers"
@@ -32,6 +30,7 @@
)
func TestScpCache(t *testing.T) {
+ t.Parallel()
tests := []struct {
path string
@@ -50,7 +49,8 @@
fs := new(afero.MemMapFs)
for _, test := range tests {
- c, err := resGetCache(test.path, fs, test.ignore)
+ cfg := viper.New()
+ c, err := resGetCache(test.path, fs, cfg, test.ignore)
if err != nil {
t.Errorf("Error getting cache: %s", err)
}
@@ -58,12 +58,12 @@
t.Errorf("There is content where there should not be anything: %s", string(c))
}
- err = resWriteCache(test.path, test.content, fs, test.ignore)
+ err = resWriteCache(test.path, test.content, fs, cfg, test.ignore)
if err != nil {
t.Errorf("Error writing cache: %s", err)
}
- c, err = resGetCache(test.path, fs, test.ignore)
+ c, err = resGetCache(test.path, fs, cfg, test.ignore)
if err != nil {
t.Errorf("Error getting cache after writing: %s", err)
}
@@ -80,8 +80,9 @@
}
func TestScpGetLocal(t *testing.T) {
- testReset()
- fs := hugofs.NewMem()
+ t.Parallel()
+ v := viper.New()
+ fs := hugofs.NewMem(v)
ps := helpers.FilePathSeparator
tests := []struct {
@@ -102,7 +103,7 @@
t.Error(err)
}
- c, err := resGetLocal(test.path, fs.Source)
+ c, err := resGetLocal(test.path, fs.Source, v)
if err != nil {
t.Errorf("Error getting resource content: %s", err)
}
@@ -126,6 +127,7 @@
}
func TestScpGetRemote(t *testing.T) {
+ t.Parallel()
fs := new(afero.MemMapFs)
tests := []struct {
@@ -146,7 +148,9 @@
})
defer func() { srv.Close() }()
- c, err := resGetRemote(test.path, fs, cl)
+ cfg := viper.New()
+
+ c, err := resGetRemote(test.path, fs, cfg, cl)
if err != nil {
t.Errorf("Error getting resource content: %s", err)
}
@@ -153,7 +157,7 @@
if !bytes.Equal(c, test.content) {
t.Errorf("\nNet Expected: %s\nNet Actual: %s\n", string(test.content), string(c))
}
- cc, cErr := resGetCache(test.path, fs, test.ignore)
+ cc, cErr := resGetCache(test.path, fs, cfg, test.ignore)
if cErr != nil {
t.Error(cErr)
}
@@ -170,6 +174,7 @@
}
func TestParseCSV(t *testing.T) {
+ t.Parallel()
tests := []struct {
csv []byte
@@ -208,29 +213,11 @@
}
}
-// https://twitter.com/francesc/status/603066617124126720
-// for the construct: defer testRetryWhenDone().Reset()
-type wd struct {
- Reset func()
-}
-
-func testRetryWhenDone(f *templateFuncster) wd {
- cd := viper.GetString("cacheDir")
- viper.Set("cacheDir", helpers.GetTempDir("", f.Fs.Source))
- var tmpSleep time.Duration
- tmpSleep, resSleep = resSleep, time.Millisecond
- return wd{func() {
- viper.Set("cacheDir", cd)
- resSleep = tmpSleep
- }}
-}
-
func TestGetJSONFailParse(t *testing.T) {
+ t.Parallel()
f := newTestFuncster()
- defer testRetryWhenDone(f).Reset()
-
reqCount := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if reqCount > 0 {
@@ -244,7 +231,6 @@
}))
defer ts.Close()
url := ts.URL + "/test.json"
- defer os.Remove(getCacheFileID(url))
want := map[string]interface{}{"gomeetup": []interface{}{"Sydney", "San Francisco", "Stockholm"}}
have := f.getJSON(url)
@@ -255,10 +241,9 @@
}
func TestGetCSVFailParseSep(t *testing.T) {
+ t.Parallel()
f := newTestFuncster()
- defer testRetryWhenDone(f).Reset()
-
reqCount := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if reqCount > 0 {
@@ -275,7 +260,6 @@
}))
defer ts.Close()
url := ts.URL + "/test.csv"
- defer os.Remove(getCacheFileID(url))
want := [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}
have := f.getCSV(",", url)
@@ -286,11 +270,10 @@
}
func TestGetCSVFailParse(t *testing.T) {
+ t.Parallel()
f := newTestFuncster()
- defer testRetryWhenDone(f).Reset()
-
reqCount := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-type", "application/json")
@@ -309,7 +292,6 @@
}))
defer ts.Close()
url := ts.URL + "/test.csv"
- defer os.Remove(getCacheFileID(url))
want := [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}
have := f.getCSV(",", url)
--- a/tpl/template_test.go
+++ b/tpl/template_test.go
@@ -26,21 +26,15 @@
"github.com/spf13/afero"
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/helpers"
+
"github.com/spf13/hugo/tplapi"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
-func testReset() {
- viper.Reset()
-
- // TODO(bep) viper-globals
- viper.Set("currentContentLanguage", helpers.NewLanguage("en"))
-}
-
// Some tests for Issue #1178 -- Ace
func TestAceTemplates(t *testing.T) {
+ t.Parallel()
for i, this := range []struct {
basePath string
@@ -79,7 +73,7 @@
d := "DATA"
- config := newDefaultDepsCfg()
+ config := newDepsConfig(viper.New())
config.WithTemplate = func(templ tplapi.Template) error {
return templ.AddAceTemplate("mytemplate.ace", basePath, innerPath,
[]byte(this.baseContent), []byte(this.innerContent))
@@ -87,7 +81,7 @@
a := deps.New(config)
- if err := a.LoadTemplates(); err != nil {
+ if err := a.LoadResources(); err != nil {
t.Fatal(err)
}
@@ -124,6 +118,7 @@
}
func TestAddTemplateFileWithMaster(t *testing.T) {
+ t.Parallel()
if !isAtLeastGo16() {
t.Skip("This test only runs on Go >= 1.6")
@@ -148,8 +143,8 @@
masterTplName := "mt"
finalTplName := "tp"
- cfg := newDefaultDepsCfg()
- cfg.WithTemplate = func(templ tplapi.Template) error {
+ config := newDepsConfig(viper.New())
+ config.WithTemplate = func(templ tplapi.Template) error {
err := templ.AddTemplateFileWithMaster(finalTplName, overlayTplName, masterTplName)
@@ -189,13 +184,13 @@
}
if this.writeSkipper != 1 {
- afero.WriteFile(cfg.Fs.Source, masterTplName, []byte(this.masterTplContent), 0644)
+ afero.WriteFile(config.Fs.Source, masterTplName, []byte(this.masterTplContent), 0644)
}
if this.writeSkipper != 2 {
- afero.WriteFile(cfg.Fs.Source, overlayTplName, []byte(this.overlayTplContent), 0644)
+ afero.WriteFile(config.Fs.Source, overlayTplName, []byte(this.overlayTplContent), 0644)
}
- deps.New(cfg)
+ deps.New(config)
}
@@ -204,6 +199,7 @@
// A Go stdlib test for linux/arm. Will remove later.
// See #1771
func TestBigIntegerFunc(t *testing.T) {
+ t.Parallel()
var func1 = func(v int64) error {
return nil
}
@@ -234,6 +230,7 @@
return nil
}
func TestBigIntegerMethod(t *testing.T) {
+ t.Parallel()
data := &BI{}
@@ -253,6 +250,7 @@
// Test for bugs discovered by https://github.com/dvyukov/go-fuzz
func TestTplGoFuzzReports(t *testing.T) {
+ t.Parallel()
// The following test case(s) also fail
// See https://github.com/golang/go/issues/10634
@@ -284,13 +282,14 @@
H: "a,b,c,d,e,f",
}
- cfg := newDefaultDepsCfg()
- cfg.WithTemplate = func(templ tplapi.Template) error {
+ config := newDepsConfig(viper.New())
+
+ config.WithTemplate = func(templ tplapi.Template) error {
return templ.AddTemplate("fuzz", this.data)
}
- de := deps.New(cfg)
- require.NoError(t, de.LoadTemplates())
+ de := deps.New(config)
+ require.NoError(t, de.LoadResources())
templ := de.Tmpl.(*GoHTMLTemplate)
--- a/transform/livereloadinject.go
+++ b/transform/livereloadinject.go
@@ -16,24 +16,23 @@
import (
"bytes"
"fmt"
-
- "github.com/spf13/viper"
)
-func LiveReloadInject(ct contentTransformer) {
- endBodyTag := "</body>"
- match := []byte(endBodyTag)
- port := viper.Get("port")
- replaceTemplate := `<script data-no-instant>document.write('<script src="/livereload.js?port=%d&mindelay=10"></' + 'script>')</script>%s`
- replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag))
-
- newcontent := bytes.Replace(ct.Content(), match, replace, 1)
- if len(newcontent) == len(ct.Content()) {
- endBodyTag = "</BODY>"
- replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag))
+func LiveReloadInject(port int) func(ct contentTransformer) {
+ return func(ct contentTransformer) {
+ endBodyTag := "</body>"
match := []byte(endBodyTag)
- newcontent = bytes.Replace(ct.Content(), match, replace, 1)
- }
+ replaceTemplate := `<script data-no-instant>document.write('<script src="/livereload.js?port=%d&mindelay=10"></' + 'script>')</script>%s`
+ replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag))
- ct.Write(newcontent)
+ newcontent := bytes.Replace(ct.Content(), match, replace, 1)
+ if len(newcontent) == len(ct.Content()) {
+ endBodyTag = "</BODY>"
+ replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag))
+ match := []byte(endBodyTag)
+ newcontent = bytes.Replace(ct.Content(), match, replace, 1)
+ }
+
+ ct.Write(newcontent)
+ }
}
--- a/transform/livereloadinject_test.go
+++ b/transform/livereloadinject_test.go
@@ -18,8 +18,6 @@
"fmt"
"strings"
"testing"
-
- "github.com/spf13/viper"
)
func TestLiveReloadInject(t *testing.T) {
@@ -28,11 +26,10 @@
}
func doTestLiveReloadInject(t *testing.T, bodyEndTag string) {
- viper.Set("port", 1313)
out := new(bytes.Buffer)
in := strings.NewReader(bodyEndTag)
- tr := NewChain(LiveReloadInject)
+ tr := NewChain(LiveReloadInject(1313))
tr.Apply(out, in, []byte("path"))
expected := fmt.Sprintf(`<script data-no-instant>document.write('<script src="/livereload.js?port=1313&mindelay=10"></' + 'script>')</script>%s`, bodyEndTag)