diff --git a/commands/benchmark.go b/commands/benchmark.go index 42966c67..4f9ab828 100644 --- a/commands/benchmark.go +++ b/commands/benchmark.go @@ -54,7 +54,7 @@ func benchmark(cmd *cobra.Command, args []string) error { return err } - c := commandeer{cfg} + c := newCommandeer(cfg) var memProf *os.File if memProfileFile != "" { diff --git a/commands/commandeer.go b/commands/commandeer.go new file mode 100644 index 00000000..e7fd7065 --- /dev/null +++ b/commands/commandeer.go @@ -0,0 +1,46 @@ +// Copyright 2017 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "github.com/spf13/hugo/deps" + "github.com/spf13/hugo/helpers" +) + +type commandeer struct { + *deps.DepsCfg + pathSpec *helpers.PathSpec + configured bool +} + +func (c *commandeer) Set(key string, value interface{}) { + if c.configured { + panic("commandeer cannot be changed") + } + c.Cfg.Set(key, value) +} + +// PathSpec lazily creates a new PathSpec, as all the paths must +// be configured before it is created. +func (c *commandeer) PathSpec() *helpers.PathSpec { + c.configured = true + if c.pathSpec == nil { + c.pathSpec = helpers.NewPathSpec(c.Fs, c.Cfg) + } + return c.pathSpec +} + +func newCommandeer(cfg *deps.DepsCfg) *commandeer { + return &commandeer{DepsCfg: cfg} +} diff --git a/commands/convert.go b/commands/convert.go index 79c3c75e..c2428255 100644 --- a/commands/convert.go +++ b/commands/convert.go @@ -21,11 +21,8 @@ import ( "github.com/spf13/cast" "github.com/spf13/cobra" - "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/hugolib" "github.com/spf13/hugo/parser" - jww "github.com/spf13/jwalterweatherman" - "github.com/spf13/viper" ) var outputDir string @@ -86,7 +83,7 @@ func convertContents(mark rune) error { return err } - h, err := hugolib.NewHugoSitesFromConfiguration(cfg) + h, err := hugolib.NewHugoSites(*cfg) if err != nil { return err } @@ -104,10 +101,10 @@ func convertContents(mark rune) error { return errors.New("No source files found") } - contentDir := helpers.AbsPathify(viper.GetString("contentDir")) - jww.FEEDBACK.Println("processing", len(site.Source.Files()), "content files") + contentDir := site.PathSpec.AbsPathify(site.Cfg.GetString("contentDir")) + site.Log.FEEDBACK.Println("processing", len(site.Source.Files()), "content files") for _, file := range site.Source.Files() { - jww.INFO.Println("Attempting to convert", file.LogicalName()) + site.Log.INFO.Println("Attempting to convert", file.LogicalName()) page, err := site.NewPage(file.LogicalName()) if err != nil { return err @@ -115,12 +112,12 @@ func convertContents(mark rune) error { psr, err := parser.ReadFrom(file.Contents) if err != nil { - jww.ERROR.Println("Error processing file:", file.Path()) + site.Log.ERROR.Println("Error processing file:", file.Path()) return err } metadata, err := psr.Metadata() if err != nil { - jww.ERROR.Println("Error processing file:", file.Path()) + site.Log.ERROR.Println("Error processing file:", file.Path()) return err } @@ -139,7 +136,7 @@ func convertContents(mark rune) error { page.SetDir(filepath.Join(contentDir, file.Dir())) page.SetSourceContent(psr.Content()) if err = page.SetSourceMetaData(metadata, mark); err != nil { - jww.ERROR.Printf("Failed to set source metadata for file %q: %s. For more info see For more info see https://github.com/spf13/hugo/issues/2458", page.FullFilePath(), err) + site.Log.ERROR.Printf("Failed to set source metadata for file %q: %s. For more info see For more info see https://github.com/spf13/hugo/issues/2458", page.FullFilePath(), err) continue } @@ -153,7 +150,7 @@ func convertContents(mark rune) error { return fmt.Errorf("Failed to save file %q: %s", page.FullFilePath(), err) } } else { - jww.FEEDBACK.Println("Unsafe operation not allowed, use --unsafe or set a different output path") + site.Log.FEEDBACK.Println("Unsafe operation not allowed, use --unsafe or set a different output path") } } } diff --git a/commands/hugo.go b/commands/hugo.go index 566e6860..3e835165 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -27,8 +27,7 @@ import ( "sync" "time" - "github.com/spf13/hugo/tpl" - + "github.com/spf13/hugo/config" "github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/parser" @@ -51,10 +50,6 @@ import ( "github.com/spf13/viper" ) -type commandeer struct { - deps.DepsCfg -} - // Hugo represents the Hugo sites to build. This variable is exported as it // is used by at least one external library (the Hugo caddy plugin). We should // provide a cleaner external API, but until then, this is it. @@ -64,7 +59,6 @@ var Hugo *hugolib.HugoSites // for benchmark testing etc. via the CLI commands. func Reset() error { Hugo = nil - viper.Reset() return nil } @@ -124,10 +118,10 @@ Complete documentation is available at http://gohugo.io/.`, return err } - c := commandeer{cfg} + c := newCommandeer(cfg) if buildWatch { - viper.Set("disableLiveReload", true) + cfg.Cfg.Set("disableLiveReload", true) c.watchConfig() } @@ -148,16 +142,17 @@ var ( ) var ( - baseURL string - cacheDir string - contentDir string - layoutDir string - cfgFile string - destination string - logFile string - theme string - themesDir string - source string + baseURL string + cacheDir string + contentDir string + layoutDir string + cfgFile string + destination string + logFile string + theme string + themesDir string + source string + logI18nWarnings bool ) // Execute adds all child commands to the root command HugoCmd and sets flags appropriately. @@ -242,7 +237,7 @@ func initHugoBuildCommonFlags(cmd *cobra.Command) { cmd.Flags().BoolP("forceSyncStatic", "", false, "Copy all files when static is changed.") cmd.Flags().BoolP("noTimes", "", false, "Don't sync modification time of files") cmd.Flags().BoolP("noChmod", "", false, "Don't sync permission mode of files") - cmd.Flags().BoolVarP(&tpl.Logi18nWarnings, "i18n-warnings", "", false, "Print missing translations") + cmd.Flags().BoolVarP(&logI18nWarnings, "i18n-warnings", "", false, "Print missing translations") // Set bash-completion. // Each flag must first be defined before using the SetAnnotation() call. @@ -275,39 +270,56 @@ func init() { } // InitializeConfig initializes a config file with sensible default configuration flags. -func InitializeConfig(subCmdVs ...*cobra.Command) (deps.DepsCfg, error) { +func InitializeConfig(subCmdVs ...*cobra.Command) (*deps.DepsCfg, error) { - var cfg deps.DepsCfg + var cfg *deps.DepsCfg = &deps.DepsCfg{} - if err := hugolib.LoadGlobalConfig(source, cfgFile); err != nil { + // Init file systems. This may be changed at a later point. + osFs := hugofs.Os + + config, err := hugolib.LoadConfig(osFs, source, cfgFile) + if err != nil { return cfg, err } + cfg.Cfg = config + + c := newCommandeer(cfg) + for _, cmdV := range append([]*cobra.Command{hugoCmdV}, subCmdVs...) { - initializeFlags(cmdV) + c.initializeFlags(cmdV) } + logger, err := createLogger(cfg.Cfg) + if err != nil { + return cfg, err + } + + cfg.Logger = logger + + config.Set("logI18nWarnings", logI18nWarnings) + if baseURL != "" { if !strings.HasSuffix(baseURL, "/") { baseURL = baseURL + "/" } - viper.Set("baseURL", baseURL) + config.Set("baseURL", baseURL) } - if !viper.GetBool("relativeURLs") && viper.GetString("baseURL") == "" { - jww.ERROR.Println("No 'baseURL' set in configuration or as a flag. Features like page menus will not work without one.") + if !config.GetBool("relativeURLs") && config.GetString("baseURL") == "" { + cfg.Logger.ERROR.Println("No 'baseURL' set in configuration or as a flag. Features like page menus will not work without one.") } if theme != "" { - viper.Set("theme", theme) + config.Set("theme", theme) } if themesDir != "" { - viper.Set("themesDir", themesDir) + config.Set("themesDir", themesDir) } if destination != "" { - viper.Set("publishDir", destination) + config.Set("publishDir", destination) } var dir string @@ -316,24 +328,32 @@ func InitializeConfig(subCmdVs ...*cobra.Command) (deps.DepsCfg, error) { } else { dir, _ = os.Getwd() } - viper.Set("workingDir", dir) + config.Set("workingDir", dir) + + cfg.Fs = hugofs.NewFrom(osFs, config) + + // Hugo writes the output to memory instead of the disk. + // This is only used for benchmark testing. Cause the content is only visible + // in memory. + if renderToMemory { + c.Fs.Destination = new(afero.MemMapFs) + // Rendering to memoryFS, publish to Root regardless of publishDir. + c.Set("publishDir", "/") + } if contentDir != "" { - viper.Set("contentDir", contentDir) + config.Set("contentDir", contentDir) } if layoutDir != "" { - viper.Set("layoutDir", layoutDir) + config.Set("layoutDir", layoutDir) } if cacheDir != "" { - viper.Set("cacheDir", cacheDir) + config.Set("cacheDir", cacheDir) } - // Init file systems. This may be changed at a later point. - cfg.Fs = hugofs.NewDefault() - - cacheDir = viper.GetString("cacheDir") + cacheDir = config.GetString("cacheDir") if cacheDir != "" { if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] { cacheDir = cacheDir + helpers.FilePathSeparator @@ -343,39 +363,32 @@ func InitializeConfig(subCmdVs ...*cobra.Command) (deps.DepsCfg, error) { if !isDir { mkdir(cacheDir) } - viper.Set("cacheDir", cacheDir) + config.Set("cacheDir", cacheDir) } else { - viper.Set("cacheDir", helpers.GetTempDir("hugo_cache", cfg.Fs.Source)) + config.Set("cacheDir", helpers.GetTempDir("hugo_cache", cfg.Fs.Source)) } - jww.INFO.Println("Using config file:", viper.ConfigFileUsed()) + cfg.Logger.INFO.Println("Using config file:", viper.ConfigFileUsed()) - themeDir := helpers.GetThemeDir() + themeDir := c.PathSpec().GetThemeDir() if themeDir != "" { if _, err := cfg.Fs.Source.Stat(themeDir); os.IsNotExist(err) { return cfg, newSystemError("Unable to find theme Directory:", themeDir) } } - themeVersionMismatch, minVersion := isThemeVsHugoVersionMismatch(cfg.Fs.Source) + themeVersionMismatch, minVersion := c.isThemeVsHugoVersionMismatch() if themeVersionMismatch { - jww.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n", + cfg.Logger.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n", helpers.HugoReleaseVersion(), minVersion) } - logger, err := createLogger() - if err != nil { - return cfg, err - } - - cfg.Logger = logger - return cfg, nil } -func createLogger() (*jww.Notepad, error) { +func createLogger(cfg config.Provider) (*jww.Notepad, error) { var ( logHandle = ioutil.Discard outHandle = os.Stdout @@ -383,11 +396,11 @@ func createLogger() (*jww.Notepad, error) { logThreshold = jww.LevelWarn ) - if verboseLog || logging || (viper.IsSet("logFile") && viper.GetString("logFile") != "") { + if verboseLog || logging || (cfg.GetString("logFile") != "") { var err error - if viper.IsSet("logFile") && viper.GetString("logFile") != "" { - path := viper.GetString("logFile") + if cfg.GetString("logFile") != "" { + path := cfg.GetString("logFile") logHandle, err = os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) if err != nil { return nil, newSystemError("Failed to open log file:", path, err) @@ -398,7 +411,7 @@ func createLogger() (*jww.Notepad, error) { return nil, newSystemError(err) } } - } else if !quiet && viper.GetBool("verbose") { + } else if !quiet && cfg.GetBool("verbose") { stdoutThreshold = jww.LevelInfo } @@ -409,7 +422,7 @@ func createLogger() (*jww.Notepad, error) { return jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime), nil } -func initializeFlags(cmd *cobra.Command) { +func (c *commandeer) initializeFlags(cmd *cobra.Command) { persFlagKeys := []string{"verbose", "logFile"} flagKeys := []string{ "cleanDestinationDir", @@ -432,21 +445,21 @@ func initializeFlags(cmd *cobra.Command) { } for _, key := range persFlagKeys { - setValueFromFlag(cmd.PersistentFlags(), key) + c.setValueFromFlag(cmd.PersistentFlags(), key) } for _, key := range flagKeys { - setValueFromFlag(cmd.Flags(), key) + c.setValueFromFlag(cmd.Flags(), key) } } -func setValueFromFlag(flags *flag.FlagSet, key string) { - if flagChanged(flags, key) { +func (c *commandeer) setValueFromFlag(flags *flag.FlagSet, key string) { + if c.flagChanged(flags, key) { f := flags.Lookup(key) - viper.Set(key, f.Value.String()) + c.Set(key, f.Value.String()) } } -func flagChanged(flags *flag.FlagSet, key string) bool { +func (c *commandeer) flagChanged(flags *flag.FlagSet, key string) bool { flag := flags.Lookup(key) if flag == nil { return false @@ -454,31 +467,23 @@ func flagChanged(flags *flag.FlagSet, key string) bool { return flag.Changed } -func (c commandeer) watchConfig() { - viper.WatchConfig() - viper.OnConfigChange(func(e fsnotify.Event) { - jww.FEEDBACK.Println("Config file changed:", e.Name) +func (c *commandeer) watchConfig() { + v := c.Cfg.(*viper.Viper) + v.WatchConfig() + v.OnConfigChange(func(e fsnotify.Event) { + c.Logger.FEEDBACK.Println("Config file changed:", e.Name) // Force a full rebuild utils.CheckErr(c.recreateAndBuildSites(true)) - if !viper.GetBool("disableLiveReload") { + if !c.Cfg.GetBool("disableLiveReload") { // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized livereload.ForceRefresh() } }) } -func (c commandeer) build(watches ...bool) error { - // Hugo writes the output to memory instead of the disk. - // This is only used for benchmark testing. Cause the content is only visible - // in memory. - if renderToMemory { - c.Fs.Destination = new(afero.MemMapFs) - // Rendering to memoryFS, publish to Root regardless of publishDir. - viper.Set("publishDir", "/") - } - +func (c *commandeer) build(watches ...bool) error { if err := c.copyStatic(); err != nil { - return fmt.Errorf("Error copying static files to %s: %s", helpers.AbsPathify(viper.GetString("publishDir")), err) + return fmt.Errorf("Error copying static files to %s: %s", c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")), err) } watch := false if len(watches) > 0 && watches[0] { @@ -489,37 +494,35 @@ func (c commandeer) build(watches ...bool) error { } if buildWatch { - jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("contentDir"))) - jww.FEEDBACK.Println("Press Ctrl+C to stop") + c.Logger.FEEDBACK.Println("Watching for changes in", c.PathSpec().AbsPathify(c.Cfg.GetString("contentDir"))) + c.Logger.FEEDBACK.Println("Press Ctrl+C to stop") utils.CheckErr(c.newWatcher(0)) } return nil } -func (c commandeer) getStaticSourceFs() afero.Fs { +func (c *commandeer) getStaticSourceFs() afero.Fs { source := c.Fs.Source - pathSpec := helpers.NewPathSpec(c.Fs, viper.GetViper()) - themeDir, err := pathSpec.GetThemeStaticDirPath() - staticDir := helpers.GetStaticDirPath() + helpers.FilePathSeparator - + themeDir, err := c.PathSpec().GetThemeStaticDirPath() + staticDir := c.PathSpec().GetStaticDirPath() + helpers.FilePathSeparator useTheme := true useStatic := true if err != nil { if err != helpers.ErrThemeUndefined { - jww.WARN.Println(err) + c.Logger.WARN.Println(err) } useTheme = false } else { if _, err := source.Stat(themeDir); os.IsNotExist(err) { - jww.WARN.Println("Unable to find Theme Static Directory:", themeDir) + c.Logger.WARN.Println("Unable to find Theme Static Directory:", themeDir) useTheme = false } } if _, err := source.Stat(staticDir); os.IsNotExist(err) { - jww.WARN.Println("Unable to find Static Directory:", staticDir) + c.Logger.WARN.Println("Unable to find Static Directory:", staticDir) useStatic = false } @@ -528,25 +531,25 @@ func (c commandeer) getStaticSourceFs() afero.Fs { } if !useStatic { - jww.INFO.Println(themeDir, "is the only static directory available to sync from") + c.Logger.INFO.Println(themeDir, "is the only static directory available to sync from") return afero.NewReadOnlyFs(afero.NewBasePathFs(source, themeDir)) } if !useTheme { - jww.INFO.Println(staticDir, "is the only static directory available to sync from") + c.Logger.INFO.Println(staticDir, "is the only static directory available to sync from") return afero.NewReadOnlyFs(afero.NewBasePathFs(source, staticDir)) } - jww.INFO.Println("using a UnionFS for static directory comprised of:") - jww.INFO.Println("Base:", themeDir) - jww.INFO.Println("Overlay:", staticDir) + c.Logger.INFO.Println("using a UnionFS for static directory comprised of:") + c.Logger.INFO.Println("Base:", themeDir) + c.Logger.INFO.Println("Overlay:", staticDir) base := afero.NewReadOnlyFs(afero.NewBasePathFs(source, themeDir)) overlay := afero.NewReadOnlyFs(afero.NewBasePathFs(source, staticDir)) return afero.NewCopyOnWriteFs(base, overlay) } -func (c commandeer) copyStatic() error { - publishDir := helpers.AbsPathify(viper.GetString("publishDir")) + helpers.FilePathSeparator +func (c *commandeer) copyStatic() error { + publishDir := c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")) + helpers.FilePathSeparator // If root, remove the second '/' if publishDir == "//" { @@ -557,22 +560,22 @@ func (c commandeer) copyStatic() error { staticSourceFs := c.getStaticSourceFs() if staticSourceFs == nil { - jww.WARN.Println("No static directories found to sync") + c.Logger.WARN.Println("No static directories found to sync") return nil } syncer := fsync.NewSyncer() - syncer.NoTimes = viper.GetBool("noTimes") - syncer.NoChmod = viper.GetBool("noChmod") + syncer.NoTimes = c.Cfg.GetBool("noTimes") + syncer.NoChmod = c.Cfg.GetBool("noChmod") syncer.SrcFs = staticSourceFs syncer.DestFs = c.Fs.Destination // Now that we are using a unionFs for the static directories // We can effectively clean the publishDir on initial sync - syncer.Delete = viper.GetBool("cleanDestinationDir") + syncer.Delete = c.Cfg.GetBool("cleanDestinationDir") if syncer.Delete { - jww.INFO.Println("removing all files from destination that don't exist in static dirs") + c.Logger.INFO.Println("removing all files from destination that don't exist in static dirs") } - jww.INFO.Println("syncing static files to", publishDir) + c.Logger.INFO.Println("syncing static files to", publishDir) // because we are using a baseFs (to get the union right). // set sync src to root @@ -580,37 +583,37 @@ func (c commandeer) copyStatic() error { } // getDirList provides NewWatcher() with a list of directories to watch for changes. -func (c commandeer) getDirList() []string { +func (c *commandeer) getDirList() []string { var a []string - dataDir := helpers.AbsPathify(viper.GetString("dataDir")) - i18nDir := helpers.AbsPathify(viper.GetString("i18nDir")) - layoutDir := helpers.AbsPathify(viper.GetString("layoutDir")) - staticDir := helpers.AbsPathify(viper.GetString("staticDir")) + dataDir := c.PathSpec().AbsPathify(c.Cfg.GetString("dataDir")) + i18nDir := c.PathSpec().AbsPathify(c.Cfg.GetString("i18nDir")) + layoutDir := c.PathSpec().AbsPathify(c.Cfg.GetString("layoutDir")) + staticDir := c.PathSpec().AbsPathify(c.Cfg.GetString("staticDir")) var themesDir string - if helpers.ThemeSet() { - themesDir = helpers.AbsPathify(viper.GetString("themesDir") + "/" + viper.GetString("theme")) + if c.PathSpec().ThemeSet() { + themesDir = c.PathSpec().AbsPathify(c.Cfg.GetString("themesDir") + "/" + c.Cfg.GetString("theme")) } walker := func(path string, fi os.FileInfo, err error) error { if err != nil { if path == dataDir && os.IsNotExist(err) { - jww.WARN.Println("Skip dataDir:", err) + c.Logger.WARN.Println("Skip dataDir:", err) return nil } if path == i18nDir && os.IsNotExist(err) { - jww.WARN.Println("Skip i18nDir:", err) + c.Logger.WARN.Println("Skip i18nDir:", err) return nil } if path == layoutDir && os.IsNotExist(err) { - jww.WARN.Println("Skip layoutDir:", err) + c.Logger.WARN.Println("Skip layoutDir:", err) return nil } if path == staticDir && os.IsNotExist(err) { - jww.WARN.Println("Skip staticDir:", err) + c.Logger.WARN.Println("Skip staticDir:", err) return nil } @@ -619,23 +622,23 @@ func (c commandeer) getDirList() []string { return nil } - jww.ERROR.Println("Walker: ", err) + c.Logger.ERROR.Println("Walker: ", err) return nil } if fi.Mode()&os.ModeSymlink == os.ModeSymlink { link, err := filepath.EvalSymlinks(path) if err != nil { - jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err) + c.Logger.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err) return nil } linkfi, err := c.Fs.Source.Stat(link) if err != nil { - jww.ERROR.Printf("Cannot stat '%s', error was: %s", link, err) + c.Logger.ERROR.Printf("Cannot stat '%s', error was: %s", link, err) return nil } if !linkfi.Mode().IsRegular() { - jww.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", path) + c.Logger.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", path) } return nil } @@ -651,12 +654,12 @@ func (c commandeer) getDirList() []string { } helpers.SymbolicWalk(c.Fs.Source, dataDir, walker) - helpers.SymbolicWalk(c.Fs.Source, helpers.AbsPathify(viper.GetString("contentDir")), walker) + helpers.SymbolicWalk(c.Fs.Source, c.PathSpec().AbsPathify(c.Cfg.GetString("contentDir")), walker) helpers.SymbolicWalk(c.Fs.Source, i18nDir, walker) - helpers.SymbolicWalk(c.Fs.Source, helpers.AbsPathify(viper.GetString("layoutDir")), walker) + helpers.SymbolicWalk(c.Fs.Source, c.PathSpec().AbsPathify(c.Cfg.GetString("layoutDir")), walker) helpers.SymbolicWalk(c.Fs.Source, staticDir, walker) - if helpers.ThemeSet() { + if c.PathSpec().ThemeSet() { helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "layouts"), walker) helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "static"), walker) helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "i18n"), walker) @@ -667,32 +670,31 @@ func (c commandeer) getDirList() []string { return a } -func (c commandeer) recreateAndBuildSites(watching bool) (err error) { +func (c *commandeer) recreateAndBuildSites(watching bool) (err error) { if err := c.initSites(); err != nil { return err } if !quiet { - jww.FEEDBACK.Println("Started building sites ...") + c.Logger.FEEDBACK.Println("Started building sites ...") } return Hugo.Build(hugolib.BuildCfg{CreateSitesFromConfig: true, Watching: watching, PrintStats: !quiet}) } -func (c commandeer) resetAndBuildSites(watching bool) (err error) { +func (c *commandeer) resetAndBuildSites(watching bool) (err error) { if err = c.initSites(); err != nil { return } if !quiet { - jww.FEEDBACK.Println("Started building sites ...") + c.Logger.FEEDBACK.Println("Started building sites ...") } return Hugo.Build(hugolib.BuildCfg{ResetState: true, Watching: watching, PrintStats: !quiet}) } -func (c commandeer) initSites() error { +func (c *commandeer) initSites() error { if Hugo != nil { return nil } - - h, err := hugolib.NewHugoSitesFromConfiguration(c.DepsCfg) + h, err := hugolib.NewHugoSites(*c.DepsCfg) if err != nil { return err @@ -702,17 +704,17 @@ func (c commandeer) initSites() error { return nil } -func (c commandeer) buildSites(watching bool) (err error) { +func (c *commandeer) buildSites(watching bool) (err error) { if err := c.initSites(); err != nil { return err } if !quiet { - jww.FEEDBACK.Println("Started building sites ...") + c.Logger.FEEDBACK.Println("Started building sites ...") } return Hugo.Build(hugolib.BuildCfg{Watching: watching, PrintStats: !quiet}) } -func (c commandeer) rebuildSites(events []fsnotify.Event) error { +func (c *commandeer) rebuildSites(events []fsnotify.Event) error { if err := c.initSites(); err != nil { return err } @@ -720,13 +722,11 @@ func (c commandeer) rebuildSites(events []fsnotify.Event) error { } // newWatcher creates a new watcher to watch filesystem events. -func (c commandeer) newWatcher(port int) error { +func (c *commandeer) newWatcher(port int) error { if runtime.GOOS == "darwin" { tweakLimit() } - pathSpec := helpers.NewPathSpec(c.Fs, viper.GetViper()) - watcher, err := watcher.New(1 * time.Second) var wg sync.WaitGroup @@ -748,7 +748,7 @@ func (c commandeer) newWatcher(port int) error { for { select { case evs := <-watcher.Events: - jww.INFO.Println("Received System Events:", evs) + c.Logger.INFO.Println("Received System Events:", evs) staticEvents := []fsnotify.Event{} dynamicEvents := []fsnotify.Event{} @@ -794,7 +794,7 @@ func (c commandeer) newWatcher(port int) error { walkAdder := func(path string, f os.FileInfo, err error) error { if f.IsDir() { - jww.FEEDBACK.Println("adding created directory to watchlist", path) + c.Logger.FEEDBACK.Println("adding created directory to watchlist", path) watcher.Add(path) } return nil @@ -808,7 +808,7 @@ func (c commandeer) newWatcher(port int) error { } } - isstatic := strings.HasPrefix(ev.Name, helpers.GetStaticDirPath()) || (len(pathSpec.GetThemesDirPath()) > 0 && strings.HasPrefix(ev.Name, pathSpec.GetThemesDirPath())) + isstatic := strings.HasPrefix(ev.Name, c.PathSpec().GetStaticDirPath()) || (len(c.PathSpec().GetThemesDirPath()) > 0 && strings.HasPrefix(ev.Name, c.PathSpec().GetThemesDirPath())) if isstatic { staticEvents = append(staticEvents, ev) @@ -818,19 +818,19 @@ func (c commandeer) newWatcher(port int) error { } if len(staticEvents) > 0 { - publishDir := helpers.AbsPathify(viper.GetString("publishDir")) + helpers.FilePathSeparator + publishDir := c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")) + helpers.FilePathSeparator // If root, remove the second '/' if publishDir == "//" { publishDir = helpers.FilePathSeparator } - jww.FEEDBACK.Println("\nStatic file changes detected") + c.Logger.FEEDBACK.Println("\nStatic file changes detected") const layout = "2006-01-02 15:04 -0700" - jww.FEEDBACK.Println(time.Now().Format(layout)) + c.Logger.FEEDBACK.Println(time.Now().Format(layout)) - if viper.GetBool("forceSyncStatic") { - jww.FEEDBACK.Printf("Syncing all static files\n") + if c.Cfg.GetBool("forceSyncStatic") { + c.Logger.FEEDBACK.Printf("Syncing all static files\n") err := c.copyStatic() if err != nil { utils.StopOnErr(err, fmt.Sprintf("Error copying static files to %s", publishDir)) @@ -839,13 +839,13 @@ func (c commandeer) newWatcher(port int) error { staticSourceFs := c.getStaticSourceFs() if staticSourceFs == nil { - jww.WARN.Println("No static directories found to sync") + c.Logger.WARN.Println("No static directories found to sync") return } syncer := fsync.NewSyncer() - syncer.NoTimes = viper.GetBool("noTimes") - syncer.NoChmod = viper.GetBool("noChmod") + syncer.NoTimes = c.Cfg.GetBool("noTimes") + syncer.NoChmod = c.Cfg.GetBool("noChmod") syncer.SrcFs = staticSourceFs syncer.DestFs = c.Fs.Destination @@ -872,9 +872,9 @@ func (c commandeer) newWatcher(port int) error { fromPath := ev.Name // If we are here we already know the event took place in a static dir - relPath, err := pathSpec.MakeStaticPathRelative(fromPath) + relPath, err := c.PathSpec().MakeStaticPathRelative(fromPath) if err != nil { - jww.ERROR.Println(err) + c.Logger.ERROR.Println(err) continue } @@ -897,10 +897,10 @@ func (c commandeer) newWatcher(port int) error { // If file still exists, sync it logger.Println("Syncing", relPath, "to", publishDir) if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil { - jww.ERROR.Println(err) + c.Logger.ERROR.Println(err) } } else { - jww.ERROR.Println(err) + c.Logger.ERROR.Println(err) } continue @@ -909,18 +909,18 @@ func (c commandeer) newWatcher(port int) error { // For all other event operations Hugo will sync static. logger.Println("Syncing", relPath, "to", publishDir) if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil { - jww.ERROR.Println(err) + c.Logger.ERROR.Println(err) } } } - if !buildWatch && !viper.GetBool("disableLiveReload") { + if !buildWatch && !c.Cfg.GetBool("disableLiveReload") { // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized // force refresh when more than one file if len(staticEvents) > 0 { for _, ev := range staticEvents { - path, _ := pathSpec.MakeStaticPathRelative(ev.Name) + path, _ := c.PathSpec().MakeStaticPathRelative(ev.Name) livereload.RefreshPath(path) } @@ -931,27 +931,27 @@ func (c commandeer) newWatcher(port int) error { } if len(dynamicEvents) > 0 { - jww.FEEDBACK.Println("\nChange detected, rebuilding site") + c.Logger.FEEDBACK.Println("\nChange detected, rebuilding site") const layout = "2006-01-02 15:04 -0700" - jww.FEEDBACK.Println(time.Now().Format(layout)) + c.Logger.FEEDBACK.Println(time.Now().Format(layout)) c.rebuildSites(dynamicEvents) - if !buildWatch && !viper.GetBool("disableLiveReload") { + if !buildWatch && !c.Cfg.GetBool("disableLiveReload") { // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized livereload.ForceRefresh() } } case err := <-watcher.Errors: if err != nil { - jww.ERROR.Println(err) + c.Logger.ERROR.Println(err) } } } }() if port > 0 { - if !viper.GetBool("disableLiveReload") { + if !c.Cfg.GetBool("disableLiveReload") { livereload.Initialize() http.HandleFunc("/livereload.js", livereload.ServeJS) http.HandleFunc("/livereload", livereload.Handler) @@ -966,30 +966,30 @@ func (c commandeer) newWatcher(port int) error { // isThemeVsHugoVersionMismatch returns whether the current Hugo version is // less than the theme's min_version. -func isThemeVsHugoVersionMismatch(fs afero.Fs) (mismatch bool, requiredMinVersion string) { - if !helpers.ThemeSet() { +func (c *commandeer) isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinVersion string) { + if !c.PathSpec().ThemeSet() { return } - themeDir := helpers.GetThemeDir() + themeDir := c.PathSpec().GetThemeDir() path := filepath.Join(themeDir, "theme.toml") - exists, err := helpers.Exists(path, fs) + exists, err := helpers.Exists(path, c.Fs.Source) if err != nil || !exists { return } - b, err := afero.ReadFile(fs, path) + b, err := afero.ReadFile(c.Fs.Source, path) - c, err := parser.HandleTOMLMetaData(b) + tomlMeta, err := parser.HandleTOMLMetaData(b) if err != nil { return } - config := c.(map[string]interface{}) + config := tomlMeta.(map[string]interface{}) if minVersion, ok := config["min_version"]; ok { switch minVersion.(type) { diff --git a/commands/list.go b/commands/list.go index 870370f7..3f3286f3 100644 --- a/commands/list.go +++ b/commands/list.go @@ -19,7 +19,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/hugo/hugolib" jww "github.com/spf13/jwalterweatherman" - "github.com/spf13/viper" ) func init() { @@ -50,9 +49,11 @@ var listDraftsCmd = &cobra.Command{ return err } - viper.Set("buildDrafts", true) + c := newCommandeer(cfg) - sites, err := hugolib.NewHugoSitesFromConfiguration(cfg) + c.Set("buildDrafts", true) + + sites, err := hugolib.NewHugoSites(*cfg) if err != nil { return newSystemError("Error creating sites", err) @@ -86,9 +87,11 @@ posted in the future.`, return err } - viper.Set("buildFuture", true) + c := newCommandeer(cfg) - sites, err := hugolib.NewHugoSitesFromConfiguration(cfg) + c.Set("buildFuture", true) + + sites, err := hugolib.NewHugoSites(*cfg) if err != nil { return newSystemError("Error creating sites", err) @@ -122,9 +125,11 @@ expired.`, return err } - viper.Set("buildExpired", true) + c := newCommandeer(cfg) - sites, err := hugolib.NewHugoSitesFromConfiguration(cfg) + c.Set("buildExpired", true) + + sites, err := hugolib.NewHugoSites(*cfg) if err != nil { return newSystemError("Error creating sites", err) diff --git a/commands/list_config.go b/commands/list_config.go index c04cb66b..f47f6e14 100644 --- a/commands/list_config.go +++ b/commands/list_config.go @@ -29,15 +29,17 @@ var configCmd = &cobra.Command{ } func init() { - configCmd.RunE = config + configCmd.RunE = printConfig } -func config(cmd *cobra.Command, args []string) error { - if _, err := InitializeConfig(configCmd); err != nil { +func printConfig(cmd *cobra.Command, args []string) error { + cfg, err := InitializeConfig(configCmd) + + if err != nil { return err } - allSettings := viper.AllSettings() + allSettings := cfg.Cfg.(*viper.Viper).AllSettings() var separator string if allSettings["metadataformat"] == "toml" { diff --git a/commands/new.go b/commands/new.go index b4a2740f..a54ad5d2 100644 --- a/commands/new.go +++ b/commands/new.go @@ -93,12 +93,14 @@ func NewContent(cmd *cobra.Command, args []string) error { return err } - if flagChanged(cmd.Flags(), "format") { - viper.Set("metaDataFormat", configFormat) + c := newCommandeer(cfg) + + if c.flagChanged(cmd.Flags(), "format") { + c.Set("metaDataFormat", configFormat) } - if flagChanged(cmd.Flags(), "editor") { - viper.Set("newContentEditor", contentEditor) + if c.flagChanged(cmd.Flags(), "editor") { + c.Set("newContentEditor", contentEditor) } if len(args) < 1 { @@ -115,7 +117,7 @@ func NewContent(cmd *cobra.Command, args []string) error { kind = contentType } - s, err := hugolib.NewSite(cfg) + s, err := hugolib.NewSite(*cfg) if err != nil { return newSystemError(err) @@ -203,7 +205,7 @@ func NewSite(cmd *cobra.Command, args []string) error { forceNew, _ := cmd.Flags().GetBool("force") - return doNewSite(hugofs.NewDefault(), createpath, forceNew) + return doNewSite(hugofs.NewDefault(viper.New()), createpath, forceNew) } // NewTheme creates a new Hugo theme. @@ -215,11 +217,12 @@ func NewTheme(cmd *cobra.Command, args []string) error { } if len(args) < 1 { - return newUserError("theme name needs to be provided") } - createpath := helpers.AbsPathify(filepath.Join(viper.GetString("themesDir"), args[0])) + c := newCommandeer(cfg) + + createpath := c.PathSpec().AbsPathify(filepath.Join(c.Cfg.GetString("themesDir"), args[0])) jww.INFO.Println("creating theme at", createpath) if x, _ := helpers.Exists(createpath, cfg.Fs.Source); x { diff --git a/commands/new_test.go b/commands/new_test.go index acb3d759..dafe0400 100644 --- a/commands/new_test.go +++ b/commands/new_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/spf13/hugo/hugofs" + "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -48,7 +49,7 @@ func checkNewSiteInited(fs *hugofs.Fs, basepath string, t *testing.T) { func TestDoNewSite(t *testing.T) { basepath := filepath.Join("base", "blog") - fs := hugofs.NewMem() + _, fs := newTestCfg() require.NoError(t, doNewSite(fs, basepath, false)) @@ -57,7 +58,7 @@ func TestDoNewSite(t *testing.T) { func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) { basepath := filepath.Join("base", "blog") - fs := hugofs.NewMem() + _, fs := newTestCfg() require.NoError(t, fs.Source.MkdirAll(basepath, 777)) @@ -66,7 +67,7 @@ func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) { func TestDoNewSite_error_base_exists(t *testing.T) { basepath := filepath.Join("base", "blog") - fs := hugofs.NewMem() + _, fs := newTestCfg() require.NoError(t, fs.Source.MkdirAll(basepath, 777)) _, err := fs.Source.Create(filepath.Join(basepath, "foo")) @@ -78,7 +79,7 @@ func TestDoNewSite_error_base_exists(t *testing.T) { func TestDoNewSite_force_empty_dir(t *testing.T) { basepath := filepath.Join("base", "blog") - fs := hugofs.NewMem() + _, fs := newTestCfg() require.NoError(t, fs.Source.MkdirAll(basepath, 777)) @@ -89,7 +90,7 @@ func TestDoNewSite_force_empty_dir(t *testing.T) { func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) { basepath := filepath.Join("base", "blog") - fs := hugofs.NewMem() + _, fs := newTestCfg() contentPath := filepath.Join(basepath, "content") @@ -99,7 +100,7 @@ func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) { func TestDoNewSite_error_force_config_inside_exists(t *testing.T) { basepath := filepath.Join("base", "blog") - fs := hugofs.NewMem() + _, fs := newTestCfg() configPath := filepath.Join(basepath, "config.toml") require.NoError(t, fs.Source.MkdirAll(basepath, 777)) @@ -108,3 +109,14 @@ func TestDoNewSite_error_force_config_inside_exists(t *testing.T) { require.Error(t, doNewSite(fs, basepath, true)) } + +func newTestCfg() (*viper.Viper, *hugofs.Fs) { + + v := viper.New() + fs := hugofs.NewMem(v) + + v.SetFs(fs.Source) + + return v, fs + +} diff --git a/commands/server.go b/commands/server.go index 6b1776f4..8e35d30e 100644 --- a/commands/server.go +++ b/commands/server.go @@ -28,9 +28,9 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" + "github.com/spf13/hugo/config" "github.com/spf13/hugo/helpers" jww "github.com/spf13/jwalterweatherman" - "github.com/spf13/viper" ) var ( @@ -42,8 +42,6 @@ var ( serverWatch bool ) -//var serverCmdV *cobra.Command - var serverCmd = &cobra.Command{ Use: "server", Aliases: []string{"serve"}, @@ -108,17 +106,17 @@ func server(cmd *cobra.Command, args []string) error { return err } - c := commandeer{cfg} + c := newCommandeer(cfg) - if flagChanged(cmd.Flags(), "disableLiveReload") { - viper.Set("disableLiveReload", disableLiveReload) + if c.flagChanged(cmd.Flags(), "disableLiveReload") { + c.Set("disableLiveReload", disableLiveReload) } if serverWatch { - viper.Set("watch", true) + c.Set("watch", true) } - if viper.GetBool("watch") { + if c.Cfg.GetBool("watch") { serverWatch = true c.watchConfig() } @@ -127,7 +125,7 @@ func server(cmd *cobra.Command, args []string) error { if err == nil { l.Close() } else { - if flagChanged(serverCmd.Flags(), "port") { + if c.flagChanged(serverCmd.Flags(), "port") { // port set explicitly by user -- he/she probably meant it! return newSystemErrorF("Server startup failed: %s", err) } @@ -139,13 +137,13 @@ func server(cmd *cobra.Command, args []string) error { serverPort = sp.Port } - viper.Set("port", serverPort) + c.Set("port", serverPort) - baseURL, err = fixURL(baseURL) + baseURL, err = fixURL(c.Cfg, baseURL) if err != nil { return err } - viper.Set("baseURL", baseURL) + c.Set("baseURL", baseURL) if err := memStats(); err != nil { jww.ERROR.Println("memstats error:", err) @@ -160,7 +158,7 @@ func server(cmd *cobra.Command, args []string) error { if !renderToDisk { cfg.Fs.Destination = new(afero.MemMapFs) // Rendering to memoryFS, publish to Root regardless of publishDir. - viper.Set("publishDir", "/") + c.Set("publishDir", "/") } if err := c.build(serverWatch); err != nil { @@ -170,7 +168,7 @@ func server(cmd *cobra.Command, args []string) error { // Watch runs its own server as part of the routine if serverWatch { watchDirs := c.getDirList() - baseWatchDir := viper.GetString("workingDir") + baseWatchDir := c.Cfg.GetString("workingDir") for i, dir := range watchDirs { watchDirs[i], _ = helpers.GetRelativePath(dir, baseWatchDir) } @@ -190,19 +188,19 @@ func server(cmd *cobra.Command, args []string) error { return nil } -func (c commandeer) serve(port int) { +func (c *commandeer) serve(port int) { if renderToDisk { - jww.FEEDBACK.Println("Serving pages from " + helpers.AbsPathify(viper.GetString("publishDir"))) + jww.FEEDBACK.Println("Serving pages from " + c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir"))) } else { jww.FEEDBACK.Println("Serving pages from memory") } httpFs := afero.NewHttpFs(c.Fs.Destination) - fs := filesOnlyFs{httpFs.Dir(helpers.AbsPathify(viper.GetString("publishDir")))} + fs := filesOnlyFs{httpFs.Dir(c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")))} fileserver := http.FileServer(fs) // We're only interested in the path - u, err := url.Parse(viper.GetString("baseURL")) + u, err := url.Parse(c.Cfg.GetString("baseURL")) if err != nil { jww.ERROR.Fatalf("Invalid baseURL: %s", err) } @@ -225,10 +223,10 @@ func (c commandeer) serve(port int) { // fixURL massages the baseURL into a form needed for serving // all pages correctly. -func fixURL(s string) (string, error) { +func fixURL(cfg config.Provider, s string) (string, error) { useLocalhost := false if s == "" { - s = viper.GetString("baseURL") + s = cfg.GetString("baseURL") useLocalhost = true } diff --git a/commands/server_test.go b/commands/server_test.go index dea79ed7..3f1518aa 100644 --- a/commands/server_test.go +++ b/commands/server_test.go @@ -20,8 +20,6 @@ import ( ) func TestFixURL(t *testing.T) { - defer viper.Reset() - type data struct { TestName string CLIBaseURL string @@ -44,12 +42,12 @@ func TestFixURL(t *testing.T) { } for i, test := range tests { - viper.Reset() + v := viper.New() baseURL = test.CLIBaseURL - viper.Set("baseURL", test.CfgBaseURL) + v.Set("baseURL", test.CfgBaseURL) serverAppend = test.AppendPort serverPort = test.Port - result, err := fixURL(baseURL) + result, err := fixURL(v, baseURL) if err != nil { t.Errorf("Test #%d %s: unexpected error %s", i, test.TestName, err) } diff --git a/helpers/configProvider.go b/config/configProvider.go similarity index 60% rename from helpers/configProvider.go rename to config/configProvider.go index b9601825..a2c8ebb6 100644 --- a/helpers/configProvider.go +++ b/config/configProvider.go @@ -1,4 +1,4 @@ -// Copyright 2016-present The Hugo Authors. All rights reserved. +// 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. @@ -15,11 +15,7 @@ // 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" -) +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 @@ -28,36 +24,17 @@ import ( // // TODO(bep) Get rid of these. var ( - currentConfigProvider ConfigProvider + currentConfigProvider Provider ) -// ConfigProvider provides the configuration settings for Hugo. -type ConfigProvider interface { +// 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{} -} - -// 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 - + Set(key string, value interface{}) + IsSet(key string) bool } diff --git a/create/content.go b/create/content.go index 6a03c8c9..d38d3c7f 100644 --- a/create/content.go +++ b/create/content.go @@ -29,7 +29,6 @@ import ( "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 @@ import ( 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 @@ func NewContent(s *hugolib.Site, kind, name string) (err error) { 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 @@ func createMetadata(archetype parser.Page, name string) (map[string]interface{}, 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 @@ func createMetadata(archetype parser.Page, name string) (map[string]interface{}, // 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 @@ func FindArchetype(fs afero.Fs, kind string) (outpath string) { 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 } diff --git a/create/content_test.go b/create/content_test.go index fcfecff1..4ef23020 100644 --- a/create/content_test.go +++ b/create/content_test.go @@ -19,6 +19,8 @@ import ( "strings" "testing" + "github.com/spf13/hugo/deps" + "github.com/spf13/hugo/hugolib" "fmt" @@ -33,7 +35,8 @@ import ( ) func TestNewContent(t *testing.T) { - initViper() + v := viper.New() + initViper(v) cases := []struct { kind string @@ -48,14 +51,17 @@ func TestNewContent(t *testing.T) { } 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 TestNewContent(t *testing.T) { } } -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 { @@ -143,3 +149,16 @@ func readFileFromFs(t *testing.T, fs afero.Fs, filename string) string { } 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 + +} diff --git a/deps/deps.go b/deps/deps.go index ee3ba94c..39a3d31a 100644 --- a/deps/deps.go +++ b/deps/deps.go @@ -5,6 +5,7 @@ import ( "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 @@ type Deps struct { // 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 @@ func New(cfg DepsCfg) *Deps { 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 @@ func New(cfg DepsCfg) *Deps { } 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 New(cfg DepsCfg) *Deps { 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 @@ type DepsCfg struct { // 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 } diff --git a/helpers/content.go b/helpers/content.go index d8130e83..0eb687af 100644 --- a/helpers/content.go +++ b/helpers/content.go @@ -24,12 +24,13 @@ import ( "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 @@ var SummaryLength = 70 // SummaryDivider denotes where content summarization should end. The default is "". var SummaryDivider = []byte("") +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 @@ type Blackfriday struct { } // 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 @@ func NewBlackfriday(c ConfigProvider) *Blackfriday { ToLowerMap(defaultParam) - siteParam := c.GetStringMap("blackfriday") + siteParam := c.cfg.GetStringMap("blackfriday") siteConfig := make(map[string]interface{}) @@ -187,10 +196,10 @@ func BytesToHTML(b []byte) template.HTML { } // 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 @@ func getMarkdownExtensions(ctx *RenderingContext) int { 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 @@ func getMmarkHTMLRenderer(defaultFlags int, ctx *RenderingContext) mmark.Rendere return &HugoMmarkHTMLRenderer{ mmark.HtmlRendererWithParameters(htmlFlags, "", "", renderParameters), + c.cfg, } } @@ -321,8 +331,8 @@ func getMmarkExtensions(ctx *RenderingContext) int { 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,42 +375,44 @@ func ExtractTOC(content []byte) (newcontent []byte, toc []byte) { // 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 } // 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) } diff --git a/helpers/content_renderer.go b/helpers/content_renderer.go index dbc29f43..6082ae94 100644 --- a/helpers/content_renderer.go +++ b/helpers/content_renderer.go @@ -19,8 +19,8 @@ import ( "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 @@ type HugoHTMLRenderer struct { 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 @@ func (renderer *HugoHTMLRenderer) ListItem(out *bytes.Buffer, text []byte, flags text = append([]byte(``), 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 @@ func (renderer *HugoHTMLRenderer) List(out *bytes.Buffer, text func() bool, flag // 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) } } diff --git a/helpers/content_renderer_test.go b/helpers/content_renderer_test.go index 82168fcf..ab663966 100644 --- a/helpers/content_renderer_test.go +++ b/helpers/content_renderer_test.go @@ -22,9 +22,9 @@ import ( ) // 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 @@ func render(input string) string { } // 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 @@ func TestCodeFence(t *testing.T) { {false, "", `(?s)^
.*?
\n$`}, } - viper.Reset() - defer viper.Reset() - - viper.Set("pygmentsStyle", "monokai") - viper.Set("pygmentsUseClasses", true) - for i, d := range data { - viper.Set("pygmentsCodeFences", d.enabled) + v := viper.New() - result := render(d.input) + v.Set("pygmentsStyle", "monokai") + v.Set("pygmentsUseClasses", true) + v.Set("pygmentsCodeFences", d.enabled) + + c := NewContentSpec(v) + + result := c.render(d.input) expectedRe, err := regexp.Compile(d.expected) @@ -81,7 +81,7 @@ func TestCodeFence(t *testing.T) { 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 TestCodeFence(t *testing.T) { } func TestBlackfridayTaskList(t *testing.T) { + c := newTestContentSpec() + for i, this := range []struct { markdown string taskListEnabled bool @@ -118,11 +120,11 @@ END `}, } { - 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) diff --git a/helpers/content_test.go b/helpers/content_test.go index 67c50f9d..52bb8509 100644 --- a/helpers/content_test.go +++ b/helpers/content_test.go @@ -21,7 +21,6 @@ import ( "github.com/miekg/mmark" "github.com/russross/blackfriday" - "github.com/spf13/viper" "github.com/stretchr/testify/assert" ) @@ -152,8 +151,9 @@ func TestTruncateWordsByRune(t *testing.T) { } 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 TestGetHTMLRendererFlags(t *testing.T) { } func TestGetHTMLRendererAllFlags(t *testing.T) { + c := newTestContentSpec() + type data struct { testFlag int } @@ -176,7 +178,7 @@ func TestGetHTMLRendererAllFlags(t *testing.T) { {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 @@ func TestGetHTMLRendererAllFlags(t *testing.T) { 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 TestGetHTMLRendererAllFlags(t *testing.T) { } 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 TestGetHTMLRendererAnchors(t *testing.T) { } 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 TestGetMmarkHTMLRenderer(t *testing.T) { } 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 @@ func TestGetMarkdownExtensionsByDefaultAllExtensionsAreEnabled(t *testing.T) { 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 TestGetMarkdownExtensionsByDefaultAllExtensionsAreEnabled(t *testing.T) { } 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 TestGetMarkdownExtensionsAddingFlagsThroughRenderingContext(t *testing.T) { } 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("

testContent

\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 TestGetMarkdownRenderer(t *testing.T) { } 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("\n\n

testContent

\n") if !bytes.Equal(actualRenderedMarkdown, expectedRenderedMarkdown) { t.Errorf("Actual rendered Markdown (%s) did not match expected markdown (%s)", actualRenderedMarkdown, expectedRenderedMarkdown) @@ -332,7 +341,8 @@ func TestGetMmarkExtensions(t *testing.T) { 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 TestGetMmarkExtensions(t *testing.T) { } 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("

testContent

\n") if !bytes.Equal(actualRenderedMarkdown, expectedRenderedMarkdown) { t.Errorf("Actual rendered Markdown (%s) did not match expected markdown (%s)", actualRenderedMarkdown, expectedRenderedMarkdown) diff --git a/helpers/general.go b/helpers/general.go index 1088d9dd..91c25c04 100644 --- a/helpers/general.go +++ b/helpers/general.go @@ -32,7 +32,6 @@ import ( 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 @@ func ReaderContains(r io.Reader, subslice []byte) bool { } // 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 { diff --git a/helpers/language.go b/helpers/language.go index 000c846b..9b713685 100644 --- a/helpers/language.go +++ b/helpers/language.go @@ -19,8 +19,7 @@ import ( "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,26 +40,28 @@ type Language struct { 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 { 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 @@ func (l *Language) Params() map[string]interface{} { // 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 @@ func (l *Language) Get(key string) interface{} { 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) + } diff --git a/helpers/language_test.go b/helpers/language_test.go index f812d26a..902177e1 100644 --- a/helpers/language_test.go +++ b/helpers/language_test.go @@ -21,11 +21,12 @@ import ( ) 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")) diff --git a/helpers/path.go b/helpers/path.go index 83a91deb..14d98e2c 100644 --- a/helpers/path.go +++ b/helpers/path.go @@ -24,7 +24,6 @@ import ( "unicode" "github.com/spf13/afero" - "github.com/spf13/viper" "golang.org/x/text/transform" "golang.org/x/text/unicode/norm" ) @@ -153,41 +152,41 @@ func ReplaceExtension(path string, newExt string) string { // AbsPathify creates an absolute path if given a relative path. If already // absolute, the path is just cleaned. -func 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 "" } // 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) GetThemeI18nDirPath() (string, error) { } 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 @@ func (p *PathSpec) GetThemesDirPath() string { // 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 @@ func GetRelativePath(path, base string) (final string, err error) { } // 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. diff --git a/helpers/path_test.go b/helpers/path_test.go index 46141bef..90dd9528 100644 --- a/helpers/path_test.go +++ b/helpers/path_test.go @@ -34,15 +34,7 @@ import ( "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 @@ func TestMakePath(t *testing.T) { } 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,11 +69,9 @@ func TestMakePath(t *testing.T) { } func TestMakePathSanitized(t *testing.T) { - viper.Reset() - defer viper.Reset() - initCommonTestConfig() - - p := NewPathSpec(hugofs.NewMem(), viper.GetViper()) + v := viper.New() + l := NewDefaultLanguage(v) + p := NewPathSpec(hugofs.NewMem(v), l) tests := []struct { input string @@ -102,12 +94,12 @@ func TestMakePathSanitized(t *testing.T) { } 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 @@ -553,9 +545,9 @@ func TestAbsPathify(t *testing.T) { 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,18 +555,18 @@ func TestAbsPathify(t *testing.T) { 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) } } } 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) } diff --git a/helpers/pathspec.go b/helpers/pathspec.go index 0fc957b3..ddc18338 100644 --- a/helpers/pathspec.go +++ b/helpers/pathspec.go @@ -16,6 +16,7 @@ package helpers import ( "fmt" + "github.com/spf13/hugo/config" "github.com/spf13/hugo/hugofs" ) @@ -26,11 +27,20 @@ type PathSpec struct { 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 @@ type PathSpec struct { } 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") - } - - return &PathSpec{ + ps := &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"), + 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"), } + + if language, ok := cfg.(*Language); ok { + ps.language = language + } + + return ps } // PaginatePath returns the configured root path used for paginator pages. diff --git a/helpers/pathspec_test.go b/helpers/pathspec_test.go index 42d82851..07948bb6 100644 --- a/helpers/pathspec_test.go +++ b/helpers/pathspec_test.go @@ -23,17 +23,24 @@ import ( ) 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 @@ func TestNewPathSpecFromConfig(t *testing.T) { 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) } diff --git a/helpers/pygments.go b/helpers/pygments.go index 8e6d1a99..05161260 100644 --- a/helpers/pygments.go +++ b/helpers/pygments.go @@ -24,9 +24,9 @@ import ( "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 @@ func HasPygments() bool { } // 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 @@ func Highlight(code, lang, optsStr string) string { 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 @@ func createOptionsString(options map[string]string) string { 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 @@ func parseDefaultPygmentsOpts() (map[string]string, error) { 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 } diff --git a/helpers/pygments_test.go b/helpers/pygments_test.go index 1cca68ea..1fce1785 100644 --- a/helpers/pygments_test.go +++ b/helpers/pygments_test.go @@ -34,11 +34,12 @@ func TestParsePygmentsArgs(t *testing.T) { {"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 @@ func TestParseDefaultPygmentsArgs(t *testing.T) { {"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 diff --git a/helpers/testhelpers_test.go b/helpers/testhelpers_test.go new file mode 100644 index 00000000..303f9feb --- /dev/null +++ b/helpers/testhelpers_test.go @@ -0,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) +} diff --git a/helpers/url.go b/helpers/url.go index 2f35b059..3a060eee 100644 --- a/helpers/url.go +++ b/helpers/url.go @@ -21,7 +21,6 @@ import ( "github.com/PuerkitoBio/purell" jww "github.com/spf13/jwalterweatherman" - "github.com/spf13/viper" ) type pathBridge struct { @@ -158,7 +157,7 @@ func (p *PathSpec) AbsURL(in string, addLanguage bool) string { 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 @@ func (p *PathSpec) getLanguagePrefix() string { defaultLang := p.defaultContentLanguage defaultInSubDir := p.defaultContentLanguageInSubdir - currentLang := p.currentContentLanguage.Lang + currentLang := p.language.Lang if currentLang == "" || (currentLang == defaultLang && !defaultInSubDir) { return "" } @@ -220,7 +219,7 @@ func IsAbsURL(path string) bool { // 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 diff --git a/helpers/url_test.go b/helpers/url_test.go index b50a9efd..b53e2e6c 100644 --- a/helpers/url_test.go +++ b/helpers/url_test.go @@ -25,9 +25,10 @@ import ( ) 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 TestAbsURL(t *testing.T) { } 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 @@ func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool, {"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 TestRelURL(t *testing.T) { } 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 @@ func doTestRelURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool, } 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 @@ func TestURLPrep(t *testing.T) { } 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 { diff --git a/hugofs/fs.go b/hugofs/fs.go index 3afa1795..1ddb9821 100644 --- a/hugofs/fs.go +++ b/hugofs/fs.go @@ -16,7 +16,7 @@ package hugofs 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 @@ type Fs struct { // 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) diff --git a/hugofs/fs_test.go b/hugofs/fs_test.go index 5482e6d2..95900e6a 100644 --- a/hugofs/fs_test.go +++ b/hugofs/fs_test.go @@ -22,10 +22,8 @@ import ( ) func TestNewDefault(t *testing.T) { - viper.Reset() - defer viper.Reset() - - f := NewDefault() + v := viper.New() + f := NewDefault(v) assert.NotNil(t, f.Source) assert.IsType(t, new(afero.OsFs), f.Source) @@ -39,10 +37,8 @@ func TestNewDefault(t *testing.T) { } func TestNewMem(t *testing.T) { - viper.Reset() - defer viper.Reset() - - f := NewMem() + v := viper.New() + f := NewMem(v) assert.NotNil(t, f.Source) assert.IsType(t, new(afero.MemMapFs), f.Source) @@ -53,12 +49,11 @@ func TestNewMem(t *testing.T) { } 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) diff --git a/hugolib/alias_test.go b/hugolib/alias_test.go index 22803d22..65315649 100644 --- a/hugolib/alias_test.go +++ b/hugolib/alias_test.go @@ -18,7 +18,6 @@ import ( "testing" "github.com/spf13/hugo/deps" - "github.com/spf13/hugo/hugofs" "github.com/stretchr/testify/require" ) @@ -33,38 +32,44 @@ const basicTemplate = "{{.Content}}" const aliasTemplate = "ALIASTEMPLATE" 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, "}} `) - 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) diff --git a/hugolib/embedded_shortcodes_test.go b/hugolib/embedded_shortcodes_test.go index 64a92247..513767eb 100644 --- a/hugolib/embedded_shortcodes_test.go +++ b/hugolib/embedded_shortcodes_test.go @@ -17,41 +17,36 @@ import ( "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) } } 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 @@ func doTestShortcodeCrossrefs(t *testing.T, relative bool) { 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,216 +78,243 @@ func doTestShortcodeCrossrefs(t *testing.T, relative bool) { } 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
.*?void do().*?
\n$", + "(?s)
.*?void do().*?
\n", }, - {` -{{< highlight java "style=friendly" >}} + {`{{< highlight java "style=friendly" >}} void do(); {{< /highlight >}}`, - "(?s)^\n
.*?void.*?do.*?().*?
\n$", + "(?s)
.*?void.*?do.*?().*?
\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 }}`) + + buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) + + 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 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
.*?.*?
\n$", + "(?s)\n
.*?.*?
\n", }, { // set alt `{{< figure src="/img/hugo-logo.png" alt="Hugo logo" >}}`, - "(?s)^\n
.*?\"Hugo.*?
\n$", + "(?s)\n
.*?\"Hugo.*?
\n", }, // set title { `{{< figure src="/img/hugo-logo.png" title="Hugo logo" >}}`, - "(?s)^\n
.*?.*?
.*?

Hugo logo

.*?
.*?
\n$", + "(?s)\n
.*?.*?
.*?

Hugo logo

.*?
.*?
\n", }, // set attr and attrlink { `{{< figure src="/img/hugo-logo.png" attr="Hugo logo" attrlink="/img/hugo-logo.png" >}}`, - "(?s)^\n
.*?.*?
.*?

.*?.*?Hugo logo.*?.*?

.*?
.*?
\n$", + "(?s)\n
.*?.*?
.*?

.*?.*?Hugo logo.*?.*?

.*?
.*?
\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 }}`) + + buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) + + 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 TestShortcodeSpeakerdeck(t *testing.T) { - for i, this := range []struct { + t.Parallel() + + for _, this := range []struct { in, expected string }{ { `{{< speakerdeck 4e8126e72d853c0060001f97 >}}`, - "(?s)^$", + "(?s)", }, } { - 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
.*?.*?
\n$", + "(?s)\n
.*?.*?
\n", }, // set class { `{{< youtube w7Ft2ymGmfc video>}}`, - "(?s)^\n
.*?.*?
\n$", + "(?s)\n
.*?.*?
\n", }, // set class and autoplay (using named params) { `{{< youtube id="w7Ft2ymGmfc" class="video" autoplay="true" >}}`, - "(?s)^\n
.*?.*?
$", + "(?s)\n
.*?.*?
", }, } { - 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
.*?.*?
\n$", + "(?s)\n
.*?.*?
\n", }, // set class { `{{< vimeo 146022717 video >}}`, - "(?s)^\n
.*?.*?
\n$", + "(?s)\n
.*?.*?
\n", }, // set class (using named params) { `{{< vimeo id="146022717" class="video" >}}`, - "(?s)^
.*?.*?
$", + "(?s)^
.*?.*?
", }, } { - 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{}) + + 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 TestShortcodeGist(t *testing.T) { - for i, this := range []struct { + t.Parallel() + + for _, this := range []struct { in, expected string }{ { `{{< gist spf13 7896402 >}}`, - "(?s)^$", + "(?s)^", }, { `{{< gist spf13 7896402 "img.html" >}}`, - "(?s)^$", + "(?s)^", }, } { - 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{}) + + 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 TestShortcodeTweet(t *testing.T) { + t.Parallel() + for i, this := range []struct { in, resp, expected string }{ { `{{< 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)^

Hugo 0.15 will have 30%. faster render times thanks to this commit https://t.co/FfzhM8bNhT #gohugo #golang https://t.co/ITbMNU2BUf

— Steve Francia .@spf13. November 17, 2015
.*?$`, + `(?s)^

Hugo 0.15 will have 30%. faster render times thanks to this commit https://t.co/FfzhM8bNhT #gohugo #golang https://t.co/ITbMNU2BUf

— Steve Francia .@spf13. November 17, 2015
.*?`, }, } { // overload getJSON to return mock API response from Twitter @@ -310,28 +330,32 @@ func TestShortcodeTweet(t *testing.T) { }, } - 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) - - matched, err := regexp.MatchString(this.expected, output) - - if err != nil { - t.Fatalf("[%d] Regexp error", i) } - if !matched { - t.Errorf("[%d] unexpected rendering, got %s\n", i, 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 }}`) + + buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{}) + + th.assertFileContentRegexp(t, fs, filepath.Join("public", "simple", "index.html"), false, this.expected) + } } func TestShortcodeInstagram(t *testing.T) { + t.Parallel() + for i, this := range []struct { in, hidecaption, resp, expected string }{ @@ -339,15 +363,13 @@ func TestShortcodeInstagram(t *testing.T) { `{{< 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 taps 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"}`, - `

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 photo posted by Instagram (@instagram) on

-`, + `(?s)
`, }, { `{{< instagram BMokmydjG-M hidecaption >}}`, `1`, `{"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-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=\" 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"}`, - `

A photo posted by Instagram (@instagram) on

-`, + `(?s)
`, }, } { // overload getJSON to return mock API response from Instagram @@ -363,21 +385,25 @@ func TestShortcodeInstagram(t *testing.T) { }, } - 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(instagramFuncMap) return nil - }) - - cacheFileID := viper.GetString("cacheDir") + url.QueryEscape("https://api.instagram.com/oembed/?url=https://instagram.com/p/BMokmydjG-M/&hidecaption="+this.hidecaption) - defer os.Remove(cacheFileID) - output, err := HandleShortcodes(this.in, p) - - if err != nil { - t.Fatalf("[%d] Failed to render shortcodes", i) } - if this.expected != output { - t.Errorf("[%d] unexpected rendering, got %s expected: %s", i, output, this.expected) - } + 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 | safeHTML }}`) + + buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{}) + + th.assertFileContentRegexp(t, fs, filepath.Join("public", "simple", "index.html"), false, this.expected) + } } diff --git a/hugolib/gitinfo.go b/hugolib/gitinfo.go index 82baa325..dfa2b4dc 100644 --- a/hugolib/gitinfo.go +++ b/hugolib/gitinfo.go @@ -20,17 +20,16 @@ import ( "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, "") diff --git a/hugolib/handler_page.go b/hugolib/handler_page.go index 6b6b1717..01c041ff 100644 --- a/hugolib/handler_page.go +++ b/hugolib/handler_page.go @@ -19,7 +19,6 @@ import ( "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/source" - "github.com/spf13/viper" ) func init() { @@ -43,6 +42,13 @@ func (b basicPageHandler) Read(f *source.File, s *Site) HandledResult { 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 @@ func commonConvert(p *Page) HandledResult { // 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) } diff --git a/hugolib/handler_test.go b/hugolib/handler_test.go index 01e6793a..76a9b38d 100644 --- a/hugolib/handler_test.go +++ b/hugolib/handler_test.go @@ -19,18 +19,18 @@ import ( "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"), "more content") @@ -46,7 +46,7 @@ func TestDefaultHandler(t *testing.T) { writeSource(t, fs, filepath.FromSlash("head"), "") writeSource(t, fs, filepath.FromSlash("head_abs"), " 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,14 +196,14 @@ func (h *HugoSites) reset() { 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 { return err } - langConfig, err := newMultiLingualFromSites(sites...) + langConfig, err := newMultiLingualFromSites(depsCfg.Cfg, sites...) if err != nil { return err @@ -251,12 +257,12 @@ func (h *HugoSites) renderCrossSitesArtifacts() error { 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 @@ func (h *HugoSites) createMissingPages() error { 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 @@ func (h *HugoSites) setupTranslations() { 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 } } @@ -577,32 +601,3 @@ func (h *HugoSites) findAllPagesByKind(kind string) Pages { 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) -} diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index e915d11d..a214e523 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -121,6 +121,7 @@ func (h *HugoSites) process(config *BuildCfg, events ...fsnotify.Event) error { // 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 @@ func (h *HugoSites) assemble(config *BuildCfg) error { } for _, s := range h.Sites { - if err := s.setCurrentLanguageConfig(); err != nil { - return err - } s.preparePagesForRender(config) } diff --git a/hugolib/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go index 9abb17d5..ccc056d1 100644 --- a/hugolib/hugo_sites_build_test.go +++ b/hugolib/hugo_sites_build_test.go @@ -14,52 +14,35 @@ import ( "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) } } 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 @@ func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) { 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 @@ func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) { 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, ``) + th.assertFileContent(t, fs, "public/index.html", true, ``) } else { // should have redirect back to root - assertFileContent(t, fs, "public/fr/index.html", true, ``) + th.assertFileContent(t, fs, "public/fr/index.html", true, ``) } - 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, "http://example.com/blog/en/sitemap.xml", "http://example.com/blog/fr/sitemap.xml") if defaultInSubDir { - assertFileContent(t, fs, "public/fr/sitemap.xml", true, "http://example.com/blog/fr/") + th.assertFileContent(t, fs, "public/fr/sitemap.xml", true, "http://example.com/blog/fr/") } else { - assertFileContent(t, fs, "public/fr/sitemap.xml", true, "http://example.com/blog/") + th.assertFileContent(t, fs, "public/fr/sitemap.xml", true, "http://example.com/blog/") } - assertFileContent(t, fs, "public/en/sitemap.xml", true, "http://example.com/blog/en/") + th.assertFileContent(t, fs, "public/en/sitemap.xml", true, "http://example.com/blog/en/") // Check rss - assertFileContent(t, fs, "public/fr/index.xml", defaultInSubDir, `Content!", "# Pages: 4", @@ -89,7 +90,7 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) { "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,24 +116,24 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) { 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 Content!", "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 Content!", "Date: 2009-01-06", "Lastmod: 2009-01-07", ) // 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 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) { 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 Content!", "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 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) { 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 Content!", "Date: 2009-01-10", @@ -164,12 +165,12 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) { ) // 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 Content!", "k/v: hugo", "Date: 2009-01-14", "Lastmod: 2009-01-15", @@ -178,34 +179,37 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) { // 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", "Content!") - 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!", "Content!") // 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", " 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) } diff --git a/hugolib/pageSort_test.go b/hugolib/pageSort_test.go index c0dedbe2..f5f28f1d 100644 --- a/hugolib/pageSort_test.go +++ b/hugolib/pageSort_test.go @@ -21,19 +21,19 @@ import ( "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) @@ -61,13 +61,14 @@ func TestDefaultSort(t *testing.T) { } 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 TestSortByN(t *testing.T) { } 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 TestLimit(t *testing.T) { } 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 TestPageSortReverse(t *testing.T) { } 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,8 +150,8 @@ func TestPageSortByParam(t *testing.T) { } func BenchmarkSortByWeightAndReverse(b *testing.B) { - - p := createSortTestPages(300) + s := newTestSite(b) + p := createSortTestPages(s, 300) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -169,32 +176,25 @@ func setSortVals(dates [4]time.Time, titles [4]string, weights [4]int, pages Pag 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 diff --git a/hugolib/page_permalink_test.go b/hugolib/page_permalink_test.go index 5ed8a8b4..0d7e983d 100644 --- 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,51 +50,53 @@ func _TestPermalink(t *testing.T) { {"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() 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) } } } diff --git a/hugolib/page_taxonomy_test.go b/hugolib/page_taxonomy_test.go index 4998c3ff..e0dc1ffb 100644 --- a/hugolib/page_taxonomy_test.go +++ b/hugolib/page_taxonomy_test.go @@ -57,6 +57,7 @@ categories = "d" 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 @@ func TestParseTaxonomies(t *testing.T) { 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) diff --git a/hugolib/page_test.go b/hugolib/page_test.go index b40d122a..90a4d124 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -28,8 +28,6 @@ import ( "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 @@ activity = "exam" 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 checkError(t *testing.T, err error, expected string) { } 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 TestDegenerateEmptyPageZeroLengthName(t *testing.T) { } 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 @@ func testAllMarkdownEnginesForPages(t *testing.T, continue } - testCommonResetState() - - fs := hugofs.NewMem() + cfg, fs := newTestCfg() 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 @@ func testAllMarkdownEnginesForPages(t *testing.T, 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 testAllMarkdownEnginesForPages(t *testing.T, } func TestCreateNewPage(t *testing.T) { - + t.Parallel() assertFunc := func(t *testing.T, ext string, pages Pages) { p := pages[0] @@ -671,7 +663,7 @@ func TestCreateNewPage(t *testing.T) { } func TestSplitSummaryAndContent(t *testing.T) { - + t.Parallel() for i, this := range []struct { markup string content string @@ -727,7 +719,7 @@ func TestSplitSummaryAndContent(t *testing.T) { } 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 @@ func TestPageWithDelimiter(t *testing.T) { // Issue #1076 func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) { - - testCommonResetState() - - fs := hugofs.NewMem() + t.Parallel() + cfg, fs := newTestCfg() 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,9 +757,8 @@ func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) { // Issue #2601 func TestPageRawContent(t *testing.T) { - testCommonResetState() - - fs := hugofs.NewMem() + t.Parallel() + cfg, fs := newTestCfg() writeSource(t, fs, filepath.Join("content", "raw.md"), `--- title: Raw @@ -778,7 +767,7 @@ title: Raw 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 @@ title: Raw } 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 TestPageWithShortCodeInSummary(t *testing.T) { } 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 TestPageWithEmbeddedScriptTag(t *testing.T) { } func TestPageWithAdditionalExtension(t *testing.T) { - - fs := hugofs.NewMem() + t.Parallel() + cfg, fs := newTestCfg() 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 TestPageWithAdditionalExtension(t *testing.T) { 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 TestTableOfContents(t *testing.T) { } 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 TestPageWithMoreTag(t *testing.T) { } 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 TestPageWithDate(t *testing.T) { } 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 TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) { } 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 TestWordCountWithAllCJKRunesHasCJKLanguage(t *testing.T) { } 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 TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) { } 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 @@ func TestWordCountWithIsCJKLanguageFalse(t *testing.T) { } } - 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 TestWordCount(t *testing.T) { } func TestCreatePage(t *testing.T) { + t.Parallel() var tests = []struct { r string }{ @@ -972,7 +966,8 @@ func TestCreatePage(t *testing.T) { } 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 TestCreatePage(t *testing.T) { } func TestDegenerateInvalidFrontMatterShortDelim(t *testing.T) { + t.Parallel() var tests = []struct { r string err string @@ -987,14 +983,15 @@ func TestDegenerateInvalidFrontMatterShortDelim(t *testing.T) { {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) } } func TestShouldRenderContent(t *testing.T) { + t.Parallel() var tests = []struct { text string render bool @@ -1010,8 +1007,8 @@ func TestShouldRenderContent(t *testing.T) { } 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 @@ func TestShouldRenderContent(t *testing.T) { // 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 TestCalendarParamsVariants(t *testing.T) { } 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 TestDifferentFrontMatterVarTypes(t *testing.T) { } 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 TestDegenerateInvalidFrontMatterLeadingWhitespace(t *testing.T) { } 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 L(s ...string) []string { } 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 @@ func TestLayoutOverride(t *testing.T) { {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 TestLayoutOverride(t *testing.T) { } func TestSliceToLower(t *testing.T) { + t.Parallel() tests := []struct { value []string expected []string @@ -1155,10 +1163,9 @@ func TestSliceToLower(t *testing.T) { } 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 @@ func TestPagePaths(t *testing.T) { 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 @@ some content ` 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,14 +1245,16 @@ some content ` 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) } 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 @@ some content } 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 @@ some content } func TestPageParams(t *testing.T) { + t.Parallel() + s := newTestSite(t) want := map[string]interface{}{ "tags": []string{"hugo", "web"}, // Issue #2752 @@ -1324,13 +1346,15 @@ func TestPageParams(t *testing.T) { } 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) } } 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 TestPageSimpleMethods(t *testing.T) { {func(p *Page) bool { return strings.Join(p.PlainWords(), " ") == "Do Be Do Be Do" }}, } { - p, _ := pageTestSite.NewPage("Test") + p, _ := s.NewPage("Test") p.Content = "

Do Be Do Be Do

" if !this.assertFunc(p) { t.Errorf("[%d] Page method error", i) @@ -1349,6 +1373,7 @@ func TestPageSimpleMethods(t *testing.T) { } func TestIndexPageSimpleMethods(t *testing.T) { + t.Parallel() for i, this := range []struct { assertFunc func(n *Page) bool }{ @@ -1371,7 +1396,7 @@ func TestIndexPageSimpleMethods(t *testing.T) { } 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 TestKind(t *testing.T) { } 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 compareObjects(a interface{}, b interface{}) bool { } 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 TestShouldBuild(t *testing.T) { } 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())) } } diff --git a/hugolib/page_time_integration_test.go b/hugolib/page_time_integration_test.go index 4544d83e..dde1587d 100644 --- a/hugolib/page_time_integration_test.go +++ b/hugolib/page_time_integration_test.go @@ -89,13 +89,17 @@ Page With Date HugoLong` ) 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) } } func TestParsingDateInFrontMatter(t *testing.T) { + t.Parallel() + s := newTestSite(t) tests := []struct { buf string dt string @@ -131,7 +135,7 @@ func TestParsingDateInFrontMatter(t *testing.T) { 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.") } diff --git a/hugolib/pagesPrevNext_test.go b/hugolib/pagesPrevNext_test.go index 46b57d1c..5945d8fe 100644 --- a/hugolib/pagesPrevNext_test.go +++ b/hugolib/pagesPrevNext_test.go @@ -35,6 +35,7 @@ var pagePNTestSources = []pagePNTestObject{ } 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 TestPrev(t *testing.T) { } 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 TestNext(t *testing.T) { } 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 prepareWeightedPagesPrevNext(t *testing.T) WeightedPages { } 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 TestWeightedPagesPrev(t *testing.T) { } 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) diff --git a/hugolib/pagination.go b/hugolib/pagination.go index acfa2b75..aa20f8d5 100644 --- a/hugolib/pagination.go +++ b/hugolib/pagination.go @@ -21,6 +21,8 @@ import ( "path" "reflect" + "github.com/spf13/hugo/config" + "github.com/spf13/cast" "github.com/spf13/hugo/helpers" ) @@ -266,7 +268,7 @@ func (p *Page) Paginator(options ...interface{}) (*Pager, error) { 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 @@ func (p *Page) Paginate(seq interface{}, options ...interface{}) (*Pager, error) 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 @@ func (p *Page) Paginate(seq interface{}, options ...interface{}) (*Pager, error) 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 { diff --git a/hugolib/pagination_test.go b/hugolib/pagination_test.go index a7f2d939..cad0d27d 100644 --- a/hugolib/pagination_test.go +++ b/hugolib/pagination_test.go @@ -20,46 +20,44 @@ import ( "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 @@ func TestSplitPageGroups(t *testing.T) { 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 TestSplitPageGroups(t *testing.T) { } 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 @@ func TestPager(t *testing.T) { } _, 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 @@ func doTestPages(t *testing.T, paginator *paginator) { 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,54 +162,55 @@ func TestPagerNoPages(t *testing.T) { 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()) } 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,30 +219,32 @@ func TestPaginationURLFactory(t *testing.T) { 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) } } 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,50 +257,45 @@ func doTestPaginator(t *testing.T, useViper bool) { 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) } } func TestPaginatorURL(t *testing.T) { + t.Parallel() + cfg, fs := newTestCfg() - testCommonResetState() - viper.Set("paginate", 2) - viper.Set("paginatePath", "testing") - - fs := hugofs.NewMem() + cfg.Set("paginate", 2) + cfg.Set("paginatePath", "testing") for i := 0; i < 10; i++ { // Issue #2177, do not double encode URLs @@ -318,23 +318,29 @@ Pages: {{ .Paginator.TotalPages }} {{ end }} `) - 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 @@ func doTestPaginate(t *testing.T, useViper bool) { 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 @@ func doTestPaginate(t *testing.T, useViper bool) { } 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) + require.Nil(t, err) _, err = n1.Paginate(p1) - assert.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 @@ func TestProbablyEqualPageLists(t *testing.T) { {"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,12 +488,15 @@ func TestProbablyEqualPageLists(t *testing.T) { } 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 @@ func TestPage(t *testing.T) { 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 diff --git a/hugolib/path_separators_test.go b/hugolib/path_separators_test.go index 55790c9d..bc1bcec0 100644 --- a/hugolib/path_separators_test.go +++ b/hugolib/path_separators_test.go @@ -26,7 +26,9 @@ Sample Text ` 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 TestDegenerateMissingFolderInPageFilename(t *testing.T) { } func TestNewPageWithFilePath(t *testing.T) { + t.Parallel() + s := newTestSite(t) toCheck := []struct { input string section string @@ -48,7 +52,7 @@ func TestNewPageWithFilePath(t *testing.T) { } 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) } diff --git a/hugolib/path_separators_windows_test.go b/hugolib/path_separators_windows_test.go index 2fe07b3a..d28589a4 100644 --- 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 @@ const ( ) 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") diff --git a/hugolib/permalinks_test.go b/hugolib/permalinks_test.go index 2622dc7c..7a4bf78c 100644 --- a/hugolib/permalinks_test.go +++ b/hugolib/permalinks_test.go @@ -16,8 +16,6 @@ package hugolib import ( "strings" "testing" - - "github.com/spf13/hugo/helpers" ) // testdataPermalinks is used by a couple of tests; the expandsTo content is @@ -54,6 +52,7 @@ var testdataPermalinks = []struct { } func TestPermalinkValidation(t *testing.T) { + t.Parallel() for _, item := range testdataPermalinks { pp := pathPattern(item.spec) have := pp.validate() @@ -71,9 +70,10 @@ func TestPermalinkValidation(t *testing.T) { } 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) } diff --git a/hugolib/robotstxt_test.go b/hugolib/robotstxt_test.go index 1b42011c..ace5162c 100644 --- a/hugolib/robotstxt_test.go +++ b/hugolib/robotstxt_test.go @@ -17,10 +17,7 @@ import ( "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 @@ const robotTxtTemplate = `User-agent: Googlebot ` 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) - - fs := hugofs.NewMem() + cfg.Set("baseURL", "http://auth/bub/") + cfg.Set("enableRobotsTXT", true) 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") } diff --git a/hugolib/rss_test.go b/hugolib/rss_test.go index 74a59be6..0a7f84a4 100644 --- a/hugolib/rss_test.go +++ b/hugolib/rss_test.go @@ -18,31 +18,32 @@ import ( "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, " 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 @@ func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page) string { } 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 diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index d4494dba..b1f28d53 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -24,20 +24,22 @@ import ( "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 CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func } 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 @@ title: "Title" 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,43 +91,15 @@ title: "Title" } } -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 - }{ - {"{{`) return nil @@ -183,6 +160,7 @@ func TestNamedParamSC(t *testing.T) { // Issue #2294 func TestNestedNamedMissingParam(t *testing.T) { + t.Parallel() wt := func(tem tplapi.Template) error { tem.AddInternalShortcode("acc.html", `
{{ .Inner }}
`) tem.AddInternalShortcode("div.html", `
{{ .Inner }}
`) @@ -195,6 +173,7 @@ func TestNestedNamedMissingParam(t *testing.T) { } func TestIsNamedParamsSC(t *testing.T) { + t.Parallel() wt := func(tem tplapi.Template) error { tem.AddInternalShortcode("byposition.html", `
`) tem.AddInternalShortcode("byname.html", `
`) @@ -210,6 +189,7 @@ func TestIsNamedParamsSC(t *testing.T) { } func TestInnerSC(t *testing.T) { + t.Parallel() wt := func(tem tplapi.Template) error { tem.AddInternalShortcode("inside.html", `{{ .Inner }}
`) return nil @@ -220,6 +200,7 @@ func TestInnerSC(t *testing.T) { } func TestInnerSCWithMarkdown(t *testing.T) { + t.Parallel() wt := func(tem tplapi.Template) error { tem.AddInternalShortcode("inside.html", `{{ .Inner }}
`) return nil @@ -233,6 +214,7 @@ func TestInnerSCWithMarkdown(t *testing.T) { } func TestInnerSCWithAndWithoutMarkdown(t *testing.T) { + t.Parallel() wt := func(tem tplapi.Template) error { tem.AddInternalShortcode("inside.html", `{{ .Inner }}`) return nil @@ -256,12 +238,14 @@ This is **plain** text. } 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
\n \n \n \n \n
\n", nil) CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" caption="This is a caption" %}}`, "\n
\n \n \"This\n \n \n
\n

\n This is a caption\n \n \n \n

\n
\n \n
\n", nil) } func TestNestedSC(t *testing.T) { + t.Parallel() wt := func(tem tplapi.Template) error { tem.AddInternalShortcode("scn1.html", `
Outer, inner is {{ .Inner }}
`) tem.AddInternalShortcode("scn2.html", `
SC2
`) @@ -273,6 +257,7 @@ func TestNestedSC(t *testing.T) { } 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 TestNestedComplexSC(t *testing.T) { } 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 TestParentShortcode(t *testing.T) { } func TestFigureImgWidth(t *testing.T) { + t.Parallel() CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" width="100px" %}}`, "\n
\n \n \"apple\"\n \n \n
\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
.*?void do().*?
\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,17 +412,8 @@ func TestExtractShortcodes(t *testing.T) { } 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 @@ -579,11 +527,21 @@ tags: } - 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 @@ tags: continue } - assertFileContent(t, fs, test.outFile, true, test.expected) + th.assertFileContent(t, fs, test.outFile, true, test.expected) } } @@ -665,6 +623,7 @@ func BenchmarkReplaceShortcodeTokens(b *testing.B) { } func TestReplaceShortcodeTokens(t *testing.T) { + t.Parallel() for i, this := range []struct { input string prefix string diff --git a/hugolib/shortcodeparser_test.go b/hugolib/shortcodeparser_test.go index 662ce0f6..3103fd4d 100644 --- a/hugolib/shortcodeparser_test.go +++ b/hugolib/shortcodeparser_test.go @@ -152,6 +152,7 @@ var shortCodeLexerTests = []shortCodeLexerTest{ } func TestShortcodeLexer(t *testing.T) { + t.Parallel() for i, test := range shortCodeLexerTests { items := collect(&test) if !equal(items, test.items) { diff --git a/hugolib/site.go b/hugolib/site.go index 76fcb3c0..bd015684 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -37,11 +37,9 @@ import ( 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 @@ type Site struct { // 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 @@ func newSite(cfg deps.DepsCfg) (*Site, error) { c := newPageCollections() if cfg.Language == nil { - cfg.Language = helpers.NewDefaultLanguage() + cfg.Language = helpers.NewDefaultLanguage(cfg.Cfg) } s := &Site{PageCollections: c, Language: cfg.Language} @@ -148,37 +146,22 @@ func NewSite(cfg deps.DepsCfg) (*Site, error) { 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. // 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...) -} - -// 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) + v := viper.New() + loadDefaultSettingsFor(v) + return newSiteForLang(helpers.NewLanguage("en", v), withTemplate...) } // newSiteForLang creates a new site in the given language. @@ -191,13 +174,17 @@ func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tplapi.Te } 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,21 +256,20 @@ func (s *SiteInfo) String() string { // 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, BaseURL: template.URL(cfg.baseURL), multilingual: newMultiLingualForLanguage(cfg.language), PageCollections: cfg.pageCollections, + Params: make(map[string]interface{}), } } @@ -586,12 +572,12 @@ func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) { } } - 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 @@ func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) { 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 @@ func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) { 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) reProcess(events []fsnotify.Event) (whatChanged, error) { } 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 @@ func (s *Site) readData(f *source.File) (interface{}, error) { 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 @@ func (s *Site) process(config BuildCfg) (err error) { return } - if err = s.readI18nSources(); err != nil { - return - } - s.timerStep("load i18n") return s.createPages() @@ -858,20 +818,7 @@ func (s *Site) setupPrevNext() { } } -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 @@ func (s *Site) initialize() (err error) { 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 @@ func (s *SiteInfo) HomeAbsURL() string { // 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 @@ func (s *Site) initializeSiteInfo() { 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 @@ func (s *Site) initializeSiteInfo() { } 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 @@ func (s *Site) initializeSiteInfo() { 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) initializeSiteInfo() { } 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 @@ func (s *Site) getThemeI18nDir(path string) string { 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 @@ func (s *Site) getThemeDataDir(path string) string { 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 @@ func (s *Site) getThemeLayoutDir(path string) string { 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) getRealDir(base, path string) string { } 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,7 +1108,9 @@ func (s *Site) reReadFile(absFilePath string) (*source.File, error) { 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 @@ func (s *Site) convertSource() chan error { 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 readSourceFile(s *Site, file *source.File, results chan<- HandledResult) { } } -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 @@ func pageConverter(s *Site, pages <-chan *Page, results HandleResults, wg *sync. 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 @@ func (s *Site) assembleMenus() { //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) permalink(plink string) string { 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 @@ func (s *Site) renderAndWriteXML(name string, dest string, d interface{}, layout 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,31 +1763,31 @@ func (s *Site) renderAndWritePage(name string, dest string, d interface{}, layou 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) } } 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 @@ func (s *Site) renderAndWritePage(name string, dest string, d interface{}, layou 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 @@ Your rendered home page is blank: /index.html is zero-length %s =============================================================`, filepath.Base(viper.ConfigFileUsed()), - viper.GetString("theme"), + s.Cfg.GetString("theme"), debugAddend) } @@ -1956,7 +1908,7 @@ func (s *Site) initTargetList() { 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) writeDestAlias(path, permalink string, p *Page) (err error) { } 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 @@ func (s *Site) draftStats() string { 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 @@ func (s *Site) futureStats() string { 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 @@ func (s *Site) expiredStats() string { 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 getGoMaxProcs() int { 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 @@ func (s *Site) newSectionPage(name string, section WeightedPages) *Page { } sectionName = helpers.FirstUpper(sectionName) - if viper.GetBool("pluralizeListTitles") { + if s.Cfg.GetBool("pluralizeListTitles") { p.Title = inflect.Pluralize(sectionName) } else { p.Title = sectionName diff --git a/hugolib/siteJSONEncode_test.go b/hugolib/siteJSONEncode_test.go index 1218bfd3..4b01a914 100644 --- a/hugolib/siteJSONEncode_test.go +++ b/hugolib/siteJSONEncode_test.go @@ -20,15 +20,14 @@ import ( "path/filepath" "github.com/spf13/hugo/deps" - "github.com/spf13/hugo/hugofs" ) // Issue #1123 // Testing prevention of cyclic refs in JSON encoding // May be smart to run with: -timeout 4000ms func TestEncodePage(t *testing.T) { - - fs := hugofs.NewMem() + t.Parallel() + cfg, fs := newTestCfg() // borrowed from menu_test.go for _, src := range menuPageSources { @@ -36,7 +35,7 @@ func TestEncodePage(t *testing.T) { } - 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) diff --git a/hugolib/site_render.go b/hugolib/site_render.go index 84df78c1..fa398aa0 100644 --- a/hugolib/site_render.go +++ b/hugolib/site_render.go @@ -21,8 +21,6 @@ import ( "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 @@ func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.Wa 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 pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.Wa 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) renderPaginator(p *Page) error { 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) renderRSS(p *Page) error { } func (s *Site) render404() error { - if viper.GetBool("disable404") { + if s.Cfg.GetBool("disable404") { return nil } @@ -185,11 +184,11 @@ func (s *Site) render404() error { } 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) renderSitemap() error { } func (s *Site) renderRobotsTXT() error { - if !viper.GetBool("enableRobotsTXT") { + if !s.Cfg.GetBool("enableRobotsTXT") { return nil } @@ -265,9 +264,9 @@ func (s *Site) renderAliases() error { } 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 @@ func (s *Site) renderAliases() error { } 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 } } diff --git a/hugolib/site_test.go b/hugolib/site_test.go index ddf9da5b..8418e2a8 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -27,7 +27,6 @@ import ( "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 pageMust(p *Page, err error) *Page { } func TestDegenerateRenderThingMissingTemplate(t *testing.T) { - - fs := hugofs.NewMem() + t.Parallel() + cfg, fs := newTestCfg() 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 TestDegenerateRenderThingMissingTemplate(t *testing.T) { } func TestRenderWithInvalidTemplate(t *testing.T) { - - fs := hugofs.NewMem() + t.Parallel() + cfg, fs := newTestCfg() 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 TestRenderWithInvalidTemplate(t *testing.T) { } 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 @@ func TestDraftAndFutureRender(t *testing.T) { {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,54 +123,50 @@ func TestDraftAndFutureRender(t *testing.T) { } // 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*")}, } 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)) } - 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 @@ func TestFutureExpirationRender(t *testing.T) { // 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 TestCrossrefs(t *testing.T) { } 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,7 +240,12 @@ THE END.`, refShortcode)), }, } - 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 @@ THE END.`, refShortcode)), t, deps.DepsCfg{ Fs: fs, + Cfg: cfg, WithTemplate: createWithTemplateFromNameValues("_default/single.html", "{{.Content}}")}, BuildCfg{}) @@ -263,6 +263,8 @@ THE END.`, refShortcode)), t.Fatalf("Expected 3 got %d pages", len(s.AllPages)) } + th := testHelper{s.Cfg} + tests := []struct { doc string expected string @@ -273,7 +275,7 @@ THE END.`, refShortcode)), } for _, test := range tests { - assertFileContent(t, fs, test.doc, true, test.expected) + th.assertFileContent(t, fs, test.doc, true, test.expected) } @@ -282,33 +284,33 @@ THE END.`, refShortcode)), // Issue #939 // Issue #1923 func TestShouldAlwaysHaveUglyURLs(t *testing.T) { + t.Parallel() for _, uglyURLs := range []bool{true, false} { doTestShouldAlwaysHaveUglyURLs(t, uglyURLs) } } 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*")}, {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 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) { writeSource(t, fs, filepath.Join("layouts", "rss.xml"), "RSS") writeSource(t, fs, filepath.Join("layouts", "sitemap.xml"), "SITEMAP") - 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 doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) { } 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 @@ func TestNewSiteDefaultLang(t *testing.T) { // 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,13 +378,6 @@ func TestSectionNaming(t *testing.T) { } 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 @@ -397,7 +393,13 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) { {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 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) { 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 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) { 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("more content")}, @@ -447,13 +448,13 @@ func TestSkipRender(t *testing.T) { {Name: filepath.FromSlash("doc9.html"), Content: []byte("doc9: {{< myshortcode >}}")}, } - 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 @@ func TestSkipRender(t *testing.T) { writeSource(t, fs, filepath.Join("layouts", "head_abs"), "") 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,21 +498,20 @@ func TestSkipRender(t *testing.T) { } 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("link")}, {Name: filepath.FromSlash("blue/doc2.html"), Content: []byte("---\nf: t\n---\nmore content")}, } 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 @@ func TestAbsURLify(t *testing.T) { 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 @@ func TestAbsURLify(t *testing.T) { 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 @@ var weightedSources = []source.ByteSource{ } func TestOrderedPages(t *testing.T) { - testCommonResetState() - - viper.Set("baseURL", "http://auth/bub") - - fs := hugofs.NewMem() + t.Parallel() + cfg, fs := newTestCfg() + cfg.Set("baseURL", "http://auth/bub") 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,19 +655,18 @@ var groupedSources = []source.ByteSource{ } func TestGroupedPages(t *testing.T) { - testCommonResetState() - + t.Parallel() defer func() { if r := recover(); r != nil { fmt.Println("Recovered in f", r) } }() - 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 @@ date = 2010-05-27T07:32:00Z 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 @@ func TestWeightedTaxonomies(t *testing.T) { taxonomies["tag"] = "tags" taxonomies["category"] = "categories" - viper.Set("baseURL", "http://auth/bub") - viper.Set("taxonomies", taxonomies) + cfg, fs := newTestCfg() + + cfg.Set("baseURL", "http://auth/bub") + cfg.Set("taxonomies", taxonomies) - fs := hugofs.NewMem() 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 TestWeightedTaxonomies(t *testing.T) { } 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 @@ func setupLinkingMockSite(t *testing.T) *Site { {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 TestRefLinking(t *testing.T) { } func TestSourceRelativeLinksing(t *testing.T) { - testCommonResetState() - + t.Parallel() site := setupLinkingMockSite(t) type resultMap map[string]string @@ -1077,8 +1075,7 @@ func TestSourceRelativeLinksing(t *testing.T) { } func TestSourceRelativeLinkFileing(t *testing.T) { - testCommonResetState() - + t.Parallel() site := setupLinkingMockSite(t) type resultMap map[string]string diff --git a/hugolib/site_url_test.go b/hugolib/site_url_test.go index 5706b9fb..6f75846e 100644 --- a/hugolib/site_url_test.go +++ b/hugolib/site_url_test.go @@ -20,9 +20,7 @@ import ( "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: slug-doc-2 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 @@ var urlFakeSource = []source.ByteSource{ // Issue #1105 func TestShouldNotAddTrailingSlashToBaseURL(t *testing.T) { - testCommonResetState() - + t.Parallel() for i, this := range []struct { in string expected string @@ -61,8 +50,10 @@ func TestShouldNotAddTrailingSlashToBaseURL(t *testing.T) { {"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 @@ func TestShouldNotAddTrailingSlashToBaseURL(t *testing.T) { 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 { diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go index 15d71cc6..8bbcb487 100644 --- a/hugolib/sitemap_test.go +++ b/hugolib/sitemap_test.go @@ -19,9 +19,7 @@ import ( "reflect" "github.com/spf13/hugo/deps" - "github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/tplapi" - "github.com/spf13/viper" ) const sitemapTemplate = ` @@ -36,19 +34,18 @@ const sitemapTemplate = `http://auth/bub/sect/doc1/", // Home page @@ -76,6 +74,7 @@ func doTestSitemapOutput(t *testing.T, internal bool) { } func TestParseSitemap(t *testing.T) { + t.Parallel() expected := Sitemap{Priority: 3.0, Filename: "doo.xml", ChangeFreq: "3"} input := map[string]interface{}{ "changefreq": "3", diff --git a/hugolib/taxonomy_test.go b/hugolib/taxonomy_test.go index 5cbd58d1..f5abe2af 100644 --- a/hugolib/taxonomy_test.go +++ b/hugolib/taxonomy_test.go @@ -19,26 +19,22 @@ import ( "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() { diff --git a/hugolib/template_engines_test.go b/hugolib/template_engines_test.go index 424f2562..e7010436 100644 --- a/hugolib/template_engines_test.go +++ b/hugolib/template_engines_test.go @@ -20,13 +20,11 @@ import ( "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,10 +55,7 @@ func TestAllTemplateEngines(t *testing.T) { func doTestTemplateEngine(t *testing.T, suffix string, templateFixer func(s string) string) { - testCommonResetState() - - fs := hugofs.NewMem() - viper.SetFs(fs.Source) + cfg, fs := newTestCfg() writeSource(t, fs, filepath.Join("content", "p.md"), ` --- @@ -88,9 +83,10 @@ p 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", diff --git a/hugolib/template_test.go b/hugolib/template_test.go index 2690b172..f9e14c8f 100644 --- a/hugolib/template_test.go +++ b/hugolib/template_test.go @@ -24,8 +24,12 @@ import ( ) func TestBaseGoTemplate(t *testing.T) { - - var fs *hugofs.Fs + t.Parallel() + var ( + fs *hugofs.Fs + cfg *viper.Viper + th testHelper + ) // Variants: // 1. /-baseof., e.g. list-baseof.. @@ -44,7 +48,7 @@ func TestBaseGoTemplate(t *testing.T) { }, 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 TestBaseGoTemplate(t *testing.T) { }, 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 TestBaseGoTemplate(t *testing.T) { }, 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,64 +81,63 @@ func TestBaseGoTemplate(t *testing.T) { }, 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 }}`) }, 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 }}`) }, 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() - - fs = hugofs.NewMem() + cfg, fs = newTestCfg() + th = testHelper{cfg} writeSource(t, fs, filepath.Join("content", "sect", "page.md"), `--- title: Template test @@ -143,7 +146,7 @@ Some content `) this.setup(t) - buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{}) + buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) this.assert(t) diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go index 1d775aca..33e78e12 100644 --- a/hugolib/testhelpers_test.go +++ b/hugolib/testhelpers_test.go @@ -6,20 +6,64 @@ import ( "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 { @@ -36,11 +80,21 @@ func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ } 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)) return h.Sites[0] diff --git a/tpl/template_i18n.go b/i18n/i18n.go similarity index 51% rename from tpl/template_i18n.go rename to i18n/i18n.go index a7ec0df9..ce268fac 100644 --- a/tpl/template_i18n.go +++ b/i18n/i18n.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// 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. @@ -11,48 +11,52 @@ // See the License for the specific language governing permissions and // limitations under the License. -package tpl +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" - "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 { +// Translator handles i18n translations. +type Translator struct { translateFuncs map[string]bundle.TranslateFunc - - current bundle.TranslateFunc + cfg config.Provider + logger *jww.Notepad } -// TODO(bep) global translator -var translator *translate +// 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 +} -// 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")] +// 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 } - return nil + 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 "" + } + } -// 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") +func (t Translator) initFuncs(bndl *bundle.Bundle) { + defaultContentLanguage := t.cfg.GetString("defaultContentLanguage") var ( defaultT bundle.TranslateFunc err error @@ -64,18 +68,18 @@ func SetI18nTfuncs(bndl *bundle.Bundle) { jww.WARN.Printf("No translation bundle found for default language %q", defaultContentLanguage) } - enableMissingTranslationPlaceholders := viper.GetBool("enableMissingTranslationPlaceholders") + enableMissingTranslationPlaceholders := t.cfg.GetBool("enableMissingTranslationPlaceholders") for _, lang := range bndl.LanguageTags() { currentLang := lang - translator.translateFuncs[currentLang] = func(translationID string, args ...interface{}) string { + 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 Logi18nWarnings { + if t.cfg.GetBool("logI18nWarnings") { i18nWarningLogger.Printf("i18n|MISSING_TRANSLATION|%s|%s", currentLang, translationID) } if enableMissingTranslationPlaceholders { @@ -90,11 +94,3 @@ func SetI18nTfuncs(bndl *bundle.Bundle) { } } } - -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 -} diff --git a/tpl/template_i18n_test.go b/i18n/i18n_test.go similarity index 84% rename from tpl/template_i18n_test.go rename to i18n/i18n_test.go index fb7ff584..fd9c91b3 100644 --- a/tpl/template_i18n_test.go +++ b/i18n/i18n_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// 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. @@ -11,17 +11,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -package tpl +package i18n import ( "testing" + "io/ioutil" + "os" + + "log" + "github.com/nicksnyder/go-i18n/i18n/bundle" - "github.com/spf13/hugo/helpers" + "github.com/spf13/hugo/config" + jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" - "github.com/stretchr/testify/assert" + "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{} @@ -106,7 +114,7 @@ var i18nTests = []i18nTest{ }, } -func doTestI18nTranslate(t *testing.T, data map[string][]byte, lang, id string, args interface{}) string { +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 { @@ -116,25 +124,23 @@ func doTestI18nTranslate(t *testing.T, data map[string][]byte, lang, id string, } } - SetI18nTfuncs(i18nBundle) - SetTranslateLang(helpers.NewLanguage(lang)) + translator := NewTranslator(i18nBundle, cfg, logger) + + f := translator.Func(lang) + + translated := f(id, args) - 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")) + v := viper.New() + v.SetDefault("defaultContentLanguage", "en") // Test without and with placeholders for _, enablePlaceholders := range []bool{false, true} { - viper.Set("enableMissingTranslationPlaceholders", enablePlaceholders) + v.Set("enableMissingTranslationPlaceholders", enablePlaceholders) for _, test := range i18nTests { if enablePlaceholders { @@ -142,8 +148,8 @@ func TestI18nTranslate(t *testing.T) { } else { expected = test.expected } - actual = doTestI18nTranslate(t, test.data, test.lang, test.id, test.args) - assert.Equal(t, expected, actual) + actual = doTestI18nTranslate(t, test.data, test.lang, test.id, test.args, v) + require.Equal(t, expected, actual) } } } diff --git a/i18n/translationProvider.go b/i18n/translationProvider.go new file mode 100644 index 00000000..34558cff --- /dev/null +++ b/i18n/translationProvider.go @@ -0,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 +} diff --git a/source/content_directory_test.go b/source/content_directory_test.go index 107ca983..48c3ae90 100644 --- 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 @@ func TestIgnoreDotFilesAndDirectories(t *testing.T) { 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) } } diff --git a/source/file.go b/source/file.go index 18ed9d6f..1dff9ac0 100644 --- a/source/file.go +++ b/source/file.go @@ -18,10 +18,21 @@ import ( "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,15 +121,15 @@ func (f *File) Path() string { // 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 } // 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 @@ func NewFile(relpath string) *File { 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 @@ func NewFile(relpath string) *File { // 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 } diff --git a/source/file_test.go b/source/file_test.go index 51747401..d87a154f 100644 --- a/source/file_test.go +++ b/source/file_test.go @@ -18,28 +18,40 @@ import ( "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} } diff --git a/source/filesystem.go b/source/filesystem.go index 6089824a..a1312814 100644 --- a/source/filesystem.go +++ b/source/filesystem.go @@ -21,13 +21,10 @@ import ( "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 @@ type Filesystem struct { 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 @@ func (f *Filesystem) add(name string, reader io.Reader) (err error) { 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 @@ func (f *Filesystem) captureFiles() { 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 @@ func (f *Filesystem) captureFiles() { 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 @@ func (f *Filesystem) shouldRead(filePath string, fi os.FileInfo) (bool, error) { 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 @@ func (f *Filesystem) shouldRead(filePath string, fi os.FileInfo) (bool, error) { } 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,14 +158,14 @@ func (f *Filesystem) avoid(filePath string) bool { 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, "#") || 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) diff --git a/source/filesystem_test.go b/source/filesystem_test.go index 598a1b81..90512ce3 100644 --- a/source/filesystem_test.go +++ b/source/filesystem_test.go @@ -19,12 +19,11 @@ import ( "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 @@ type TestPath struct { } 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 @@ func TestUnicodeNorm(t *testing.T) { {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 { diff --git a/source/inmemory.go b/source/inmemory.go index 9d3af519..431236a5 100644 --- a/source/inmemory.go +++ b/source/inmemory.go @@ -13,8 +13,6 @@ package source -import "bytes" - type ByteSource struct { Name string Content []byte @@ -23,15 +21,3 @@ type ByteSource struct { 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 -} diff --git a/target/page_test.go b/target/page_test.go index 84412004..687c4d91 100644 --- a/target/page_test.go +++ b/target/page_test.go @@ -18,10 +18,11 @@ import ( "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 diff --git a/tpl/template.go b/tpl/template.go index 84460801..9a6364d5 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -21,6 +21,8 @@ import ( "path/filepath" "strings" + "sync" + "github.com/eknkc/amber" "github.com/spf13/afero" bp "github.com/spf13/hugo/bufferpool" @@ -31,6 +33,9 @@ import ( // 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 @@ func (t *GoHTMLTemplate) initFuncs(d *deps.Deps) { t.amberFuncMap = template.FuncMap{} + amberMu.Lock() for k, v := range amber.FuncMap { t.amberFuncMap[k] = v } @@ -143,6 +149,7 @@ func (t *GoHTMLTemplate) initFuncs(d *deps.Deps) { panic("should never be invoked") } } + amberMu.Unlock() } @@ -362,7 +369,9 @@ func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) er return err } + amberMu.Lock() templ, err := t.CompileAmberWithTemplate(b, path, t.New(templateName)) + amberMu.Unlock() if err != nil { return err } @@ -482,11 +491,11 @@ func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) { } 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 diff --git a/tpl/template_ast_transformers_test.go b/tpl/template_ast_transformers_test.go index 8ffb1cab..43d78284 100644 --- a/tpl/template_ast_transformers_test.go +++ b/tpl/template_ast_transformers_test.go @@ -113,6 +113,7 @@ F3: {{ Echo (printf "themes/%s-theme" .Site.Params.LOWER) }} ) func TestParamsKeysToLower(t *testing.T) { + t.Parallel() require.Error(t, applyTemplateTransformers(nil)) @@ -190,6 +191,7 @@ func BenchmarkTemplateParamsKeysToLower(b *testing.B) { } func TestParamsKeysToLowerVars(t *testing.T) { + t.Parallel() var ( ctx = map[string]interface{}{ "Params": map[string]interface{}{ @@ -227,6 +229,7 @@ Blue: {{ $__amber_1.Blue}} } func TestParamsKeysToLowerInBlockTemplate(t *testing.T) { + t.Parallel() var ( ctx = map[string]interface{}{ diff --git a/tpl/template_func_truncate_test.go b/tpl/template_func_truncate_test.go index 95368f83..9213c6fa 100644 --- a/tpl/template_func_truncate_test.go +++ b/tpl/template_func_truncate_test.go @@ -21,6 +21,7 @@ import ( ) func TestTruncate(t *testing.T) { + t.Parallel() var err error cases := []struct { v1 interface{} diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index 01b97566..9777bf61 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -46,7 +46,6 @@ import ( "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 @@ import ( type templateFuncster struct { funcMap template.FuncMap cachedPartials partialCache - *deps.Deps } @@ -398,6 +396,7 @@ func intersect(l1, l2 interface{}) (interface{}, error) { } // ResetCaches resets all caches that might be used during build. +// TODO(bep) globals move image config cache to funcster func ResetCaches() { resetImageConfigCache() } @@ -1357,31 +1356,29 @@ func returnWhenSet(a, k interface{}) interface{} { } // 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 { 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("

") var markdownTrimSuffix = []byte("

\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 @@ func (t *templateFuncster) initFuncMap() { "getenv": getenv, "gt": gt, "hasPrefix": hasPrefix, - "highlight": highlight, + "highlight": t.highlight, "htmlEscape": htmlEscape, "htmlUnescape": htmlUnescape, "humanize": humanize, @@ -2159,7 +2156,7 @@ func (t *templateFuncster) initFuncMap() { "le": le, "lower": lower, "lt": lt, - "markdownify": markdownify, + "markdownify": t.markdownify, "md5": md5, "mod": mod, "modBool": modBool, @@ -2211,8 +2208,8 @@ func (t *templateFuncster) initFuncMap() { "upper": upper, "urlize": t.PathSpec.URLize, "where": where, - "i18n": i18nTranslate, - "T": i18nTranslate, + "i18n": t.Translate, + "T": t.Translate, } t.funcMap = funcMap diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index e4af75b3..35214b64 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -42,7 +42,9 @@ import ( "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 @@ var ( 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,22 +94,17 @@ func tstIsLt(tp tstCompareType) bool { return tp == tstLt || tp == tstLe } -func tstInitTemplates() { - viper.Set("CurrentContentLanguage", helpers.NewLanguage("en")) - helpers.ResetConfigProvider() -} - func TestFuncsInTemplate(t *testing.T) { - - testReset() + t.Parallel() 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) @@ -268,11 +269,10 @@ urlize: bat-man 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 @@ urlize: bat-man 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 @@ urlize: bat-man } func TestCompare(t *testing.T) { + t.Parallel() for _, this := range []struct { tstCompareType funcUnderTest func(a, b interface{}) bool @@ -370,6 +371,7 @@ func doTestCompare(t *testing.T, tp tstCompareType, funcUnderTest func(a, b inte } func TestMod(t *testing.T) { + t.Parallel() for i, this := range []struct { a interface{} b interface{} @@ -405,6 +407,7 @@ func TestMod(t *testing.T) { } func TestModBool(t *testing.T) { + t.Parallel() for i, this := range []struct { a interface{} b interface{} @@ -445,6 +448,7 @@ func TestModBool(t *testing.T) { } func TestFirst(t *testing.T) { + t.Parallel() for i, this := range []struct { count interface{} sequence interface{} @@ -480,6 +484,7 @@ func TestFirst(t *testing.T) { } func TestLast(t *testing.T) { + t.Parallel() for i, this := range []struct { count interface{} sequence interface{} @@ -515,6 +520,7 @@ func TestLast(t *testing.T) { } func TestAfter(t *testing.T) { + t.Parallel() for i, this := range []struct { count interface{} sequence interface{} @@ -550,6 +556,7 @@ func TestAfter(t *testing.T) { } func TestShuffleInputAndOutputFormat(t *testing.T) { + t.Parallel() for i, this := range []struct { sequence interface{} success bool @@ -588,6 +595,7 @@ func TestShuffleInputAndOutputFormat(t *testing.T) { } 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 TestShuffleRandomising(t *testing.T) { } func TestDictionary(t *testing.T) { + t.Parallel() for i, this := range []struct { v1 []interface{} expecterr bool @@ -647,13 +656,15 @@ func blankImage(width, height int) []byte { } 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 @@ -754,6 +765,7 @@ func TestImageConfig(t *testing.T) { } func TestIn(t *testing.T) { + t.Parallel() for i, this := range []struct { v1 interface{} v2 interface{} @@ -783,6 +795,7 @@ func TestIn(t *testing.T) { } func TestSlicestr(t *testing.T) { + t.Parallel() var err error for i, this := range []struct { v1 interface{} @@ -848,6 +861,7 @@ func TestSlicestr(t *testing.T) { } func TestHasPrefix(t *testing.T) { + t.Parallel() cases := []struct { s interface{} prefix interface{} @@ -875,6 +889,7 @@ func TestHasPrefix(t *testing.T) { } func TestSubstr(t *testing.T) { + t.Parallel() var err error var n int for i, this := range []struct { @@ -952,6 +967,7 @@ func TestSubstr(t *testing.T) { } func TestSplit(t *testing.T) { + t.Parallel() for i, this := range []struct { v1 interface{} v2 string @@ -982,6 +998,7 @@ func TestSplit(t *testing.T) { } func TestIntersect(t *testing.T) { + t.Parallel() for i, this := range []struct { sequence1 interface{} sequence2 interface{} @@ -1025,6 +1042,7 @@ func TestIntersect(t *testing.T) { } 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 @@ type TstX struct { } 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 TestTimeUnix(t *testing.T) { } func TestEvaluateSubElem(t *testing.T) { + t.Parallel() tstx := TstX{A: "foo", B: "bar"} var inner struct { S fmt.Stringer @@ -1146,6 +1166,7 @@ func TestEvaluateSubElem(t *testing.T) { } func TestCheckCondition(t *testing.T) { + t.Parallel() type expect struct { result bool isError bool @@ -1266,6 +1287,7 @@ func TestCheckCondition(t *testing.T) { } func TestWhere(t *testing.T) { + t.Parallel() type Mid struct { Tst TstX @@ -1671,6 +1693,7 @@ func TestWhere(t *testing.T) { } func TestDelimit(t *testing.T) { + t.Parallel() for i, this := range []struct { sequence interface{} delimiter interface{} @@ -1720,6 +1743,7 @@ func TestDelimit(t *testing.T) { } func TestSort(t *testing.T) { + t.Parallel() type ts struct { MyInt int MyFloat float64 @@ -1932,6 +1956,7 @@ func TestSort(t *testing.T) { } func TestReturnWhenSet(t *testing.T) { + t.Parallel() for i, this := range []struct { data interface{} key interface{} @@ -1957,7 +1982,10 @@ func TestReturnWhenSet(t *testing.T) { } 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{} @@ -1966,7 +1994,7 @@ func TestMarkdownify(t *testing.T) { {"Hello **World!**", template.HTML("Hello World!")}, {[]byte("Hello Bytes **World!**"), template.HTML("Hello Bytes World!")}, } { - 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 @@ func TestMarkdownify(t *testing.T) { } } - 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 TestApply(t *testing.T) { } 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 TestChomp(t *testing.T) { } func TestLower(t *testing.T) { + t.Parallel() cases := []struct { s interface{} want string @@ -2069,6 +2100,7 @@ func TestLower(t *testing.T) { } func TestTitle(t *testing.T) { + t.Parallel() cases := []struct { s interface{} want string @@ -2092,6 +2124,7 @@ func TestTitle(t *testing.T) { } func TestUpper(t *testing.T) { + t.Parallel() cases := []struct { s interface{} want string @@ -2115,8 +2148,12 @@ func TestUpper(t *testing.T) { } 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 @@ func TestHighlight(t *testing.T) { 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 TestHighlight(t *testing.T) { } 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 TestInflect(t *testing.T) { } 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 TestCounterFuncs(t *testing.T) { } 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 TestReplace(t *testing.T) { } func TestReplaceRE(t *testing.T) { + t.Parallel() for i, val := range []struct { pattern interface{} repl interface{} @@ -2234,6 +2275,7 @@ func TestReplaceRE(t *testing.T) { } func TestFindRE(t *testing.T) { + t.Parallel() for i, this := range []struct { expr string content interface{} @@ -2264,6 +2306,7 @@ func TestFindRE(t *testing.T) { } func TestTrim(t *testing.T) { + t.Parallel() for i, this := range []struct { v1 interface{} @@ -2294,6 +2337,7 @@ func TestTrim(t *testing.T) { } func TestDateFormat(t *testing.T) { + t.Parallel() for i, this := range []struct { layout string value interface{} @@ -2328,6 +2372,7 @@ func TestDateFormat(t *testing.T) { } func TestDefaultFunc(t *testing.T) { + t.Parallel() then := time.Now() now := time.Now() @@ -2385,6 +2430,7 @@ func TestDefaultFunc(t *testing.T) { } func TestDefault(t *testing.T) { + t.Parallel() for i, this := range []struct { input interface{} tpl string @@ -2414,6 +2460,7 @@ func TestDefault(t *testing.T) { } func TestSafeHTML(t *testing.T) { + t.Parallel() for i, this := range []struct { str string tmplStr string @@ -2454,6 +2501,7 @@ func TestSafeHTML(t *testing.T) { } func TestSafeHTMLAttr(t *testing.T) { + t.Parallel() for i, this := range []struct { str string tmplStr string @@ -2494,6 +2542,7 @@ func TestSafeHTMLAttr(t *testing.T) { } func TestSafeCSS(t *testing.T) { + t.Parallel() for i, this := range []struct { str string tmplStr string @@ -2535,6 +2584,7 @@ func TestSafeCSS(t *testing.T) { // 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 @@ func TestSafeJS(t *testing.T) { // 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 TestSafeURL(t *testing.T) { } func TestBase64Decode(t *testing.T) { + t.Parallel() testStr := "abc123!?$*&()'-=@~" enc := base64.StdEncoding.EncodeToString([]byte(testStr)) result, err := base64Decode(enc) @@ -2635,6 +2687,7 @@ func TestBase64Decode(t *testing.T) { } func TestBase64Encode(t *testing.T) { + t.Parallel() testStr := "YWJjMTIzIT8kKiYoKSctPUB+" dec, err := base64.StdEncoding.DecodeString(testStr) @@ -2659,6 +2712,7 @@ func TestBase64Encode(t *testing.T) { } func TestMD5(t *testing.T) { + t.Parallel() for i, this := range []struct { input string expectedHash string @@ -2683,6 +2737,7 @@ func TestMD5(t *testing.T) { } func TestSHA1(t *testing.T) { + t.Parallel() for i, this := range []struct { input string expectedHash string @@ -2707,6 +2762,7 @@ func TestSHA1(t *testing.T) { } func TestSHA256(t *testing.T) { + t.Parallel() for i, this := range []struct { input string expectedHash string @@ -2731,13 +2787,15 @@ func TestSHA256(t *testing.T) { } 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 TestReadFile(t *testing.T) { } func TestPartialCached(t *testing.T) { + t.Parallel() testCases := []struct { name string partial string @@ -2793,7 +2852,6 @@ func TestPartialCached(t *testing.T) { 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 @@ func TestPartialCached(t *testing.T) { 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 @@ func TestPartialCached(t *testing.T) { 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 TestPartialCached(t *testing.T) { } 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 @@ func BenchmarkPartial(b *testing.B) { 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 BenchmarkPartial(b *testing.B) { } 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 @@ func BenchmarkPartialCached(b *testing.B) { 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 BenchmarkPartialCached(b *testing.B) { } 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 newTestFuncster() *templateFuncster { } 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 @@ func newTestTemplate(t *testing.T, name, template string) *template.Template { 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) } diff --git a/tpl/template_resources.go b/tpl/template_resources.go index 13ebfb69..bb1b11eb 100644 --- a/tpl/template_resources.go +++ b/tpl/template_resources.go @@ -27,9 +27,9 @@ import ( "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 @@ func (l *remoteLock) URLUnlock(url string) { } // 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 @@ func resGetCache(id string, fs afero.Fs, ignoreCache bool) ([]byte, error) { } // 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 @@ func resWriteCache(id string, c []byte, fs afero.Fs, ignoreCache bool) error { 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 @@ func resGetRemote(url string, fs afero.Fs, hc *http.Client) ([]byte, error) { 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 @@ func resGetRemote(url string, fs afero.Fs, hc *http.Client) ([]byte, error) { 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 @@ func (t *templateFuncster) resGetResource(url string) ([]byte, error) { 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 @@ func (t *templateFuncster) getJSON(urlParts ...string) interface{} { 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 @@ func (t *templateFuncster) getCSV(sep string, urlParts ...string) [][]string { 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++ { diff --git a/tpl/template_resources_test.go b/tpl/template_resources_test.go index 5a92f98d..82764977 100644 --- a/tpl/template_resources_test.go +++ b/tpl/template_resources_test.go @@ -19,10 +19,8 @@ import ( "net/http" "net/http/httptest" "net/url" - "os" "strings" "testing" - "time" "github.com/spf13/afero" "github.com/spf13/hugo/helpers" @@ -32,6 +30,7 @@ import ( ) func TestScpCache(t *testing.T) { + t.Parallel() tests := []struct { path string @@ -50,7 +49,8 @@ func TestScpCache(t *testing.T) { 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 @@ func TestScpCache(t *testing.T) { 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 TestScpCache(t *testing.T) { } 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 @@ func TestScpGetLocal(t *testing.T) { 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 getTestServer(handler func(w http.ResponseWriter, r *http.Request)) (*httpt } func TestScpGetRemote(t *testing.T) { + t.Parallel() fs := new(afero.MemMapFs) tests := []struct { @@ -146,14 +148,16 @@ func TestScpGetRemote(t *testing.T) { }) 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) } 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 TestScpGetRemote(t *testing.T) { } func TestParseCSV(t *testing.T) { + t.Parallel() tests := []struct { csv []byte @@ -208,29 +213,11 @@ func TestParseCSV(t *testing.T) { } } -// 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 @@ func TestGetJSONFailParse(t *testing.T) { })) 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 TestGetJSONFailParse(t *testing.T) { } 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 @@ func TestGetCSVFailParseSep(t *testing.T) { })) 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 TestGetCSVFailParseSep(t *testing.T) { } 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 @@ func TestGetCSVFailParse(t *testing.T) { })) 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) diff --git a/tpl/template_test.go b/tpl/template_test.go index 60950bb3..5bb6d89d 100644 --- a/tpl/template_test.go +++ b/tpl/template_test.go @@ -26,21 +26,15 @@ import ( "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 @@ html lang=en 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 @@ html lang=en a := deps.New(config) - if err := a.LoadTemplates(); err != nil { + if err := a.LoadResources(); err != nil { t.Fatal(err) } @@ -124,6 +118,7 @@ func isAtLeastGo16() bool { } func TestAddTemplateFileWithMaster(t *testing.T) { + t.Parallel() if !isAtLeastGo16() { t.Skip("This test only runs on Go >= 1.6") @@ -148,8 +143,8 @@ func TestAddTemplateFileWithMaster(t *testing.T) { 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 @@ func TestAddTemplateFileWithMaster(t *testing.T) { } 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 @@ func TestAddTemplateFileWithMaster(t *testing.T) { // 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 @@ func (b BI) A(v int64) error { return nil } func TestBigIntegerMethod(t *testing.T) { + t.Parallel() data := &BI{} @@ -253,6 +250,7 @@ func TestBigIntegerMethod(t *testing.T) { // 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 @@ func TestTplGoFuzzReports(t *testing.T) { 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) diff --git a/transform/livereloadinject.go b/transform/livereloadinject.go index 95608185..83fe7c10 100644 --- a/transform/livereloadinject.go +++ b/transform/livereloadinject.go @@ -16,24 +16,23 @@ package transform import ( "bytes" "fmt" - - "github.com/spf13/viper" ) -func LiveReloadInject(ct contentTransformer) { - endBodyTag := "" - match := []byte(endBodyTag) - port := viper.Get("port") - replaceTemplate := `%s` - replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag)) - - newcontent := bytes.Replace(ct.Content(), match, replace, 1) - if len(newcontent) == len(ct.Content()) { - endBodyTag = "" - replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag)) +func LiveReloadInject(port int) func(ct contentTransformer) { + return func(ct contentTransformer) { + endBodyTag := "" match := []byte(endBodyTag) - newcontent = bytes.Replace(ct.Content(), match, replace, 1) - } + replaceTemplate := `%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 = "" + replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag)) + match := []byte(endBodyTag) + newcontent = bytes.Replace(ct.Content(), match, replace, 1) + } + + ct.Write(newcontent) + } } diff --git a/transform/livereloadinject_test.go b/transform/livereloadinject_test.go index 9f28e05e..3337243b 100644 --- a/transform/livereloadinject_test.go +++ b/transform/livereloadinject_test.go @@ -18,8 +18,6 @@ import ( "fmt" "strings" "testing" - - "github.com/spf13/viper" ) func TestLiveReloadInject(t *testing.T) { @@ -28,11 +26,10 @@ func TestLiveReloadInject(t *testing.T) { } 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(`%s`, bodyEndTag)