all: Refactor to nonglobal Viper, i18n etc.

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

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

View File

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

46
commands/commandeer.go Normal file
View File

@ -0,0 +1,46 @@
// Copyright 2017 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
)
type commandeer struct {
*deps.DepsCfg
pathSpec *helpers.PathSpec
configured bool
}
func (c *commandeer) Set(key string, value interface{}) {
if c.configured {
panic("commandeer cannot be changed")
}
c.Cfg.Set(key, value)
}
// PathSpec lazily creates a new PathSpec, as all the paths must
// be configured before it is created.
func (c *commandeer) PathSpec() *helpers.PathSpec {
c.configured = true
if c.pathSpec == nil {
c.pathSpec = helpers.NewPathSpec(c.Fs, c.Cfg)
}
return c.pathSpec
}
func newCommandeer(cfg *deps.DepsCfg) *commandeer {
return &commandeer{DepsCfg: cfg}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

67
deps/deps.go vendored
View File

@ -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
}

View File

@ -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 "<!--more-->".
var SummaryDivider = []byte("<!--more-->")
type ContentSpec struct {
cfg config.Provider
}
func NewContentSpec(cfg config.Provider) *ContentSpec {
return &ContentSpec{cfg}
}
// Blackfriday holds configuration values for Blackfriday rendering.
type Blackfriday struct {
Smartypants bool
@ -58,7 +67,7 @@ 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)
}

View File

@ -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(`<input type="checkbox" checked disabled class="task-list-item">`), text[3:]...)
}
renderer.Renderer.ListItem(out, text, flags)
r.Renderer.ListItem(out, text, flags)
}
// List adds task list support to the Blackfriday renderer.
func (renderer *HugoHTMLRenderer) List(out *bytes.Buffer, text func() bool, flags int) {
if !renderer.Config.TaskLists {
renderer.Renderer.List(out, text, flags)
func (r *HugoHTMLRenderer) List(out *bytes.Buffer, text func() bool, flags int) {
if !r.Config.TaskLists {
r.Renderer.List(out, text, flags)
return
}
marker := out.Len()
renderer.Renderer.List(out, text, flags)
r.Renderer.List(out, text, flags)
if out.Len() > marker {
list := out.Bytes()[marker:]
if bytes.Contains(list, []byte("task-list-item")) {
@ -114,13 +114,14 @@ 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)
}
}

View File

@ -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, "<html></html>", `(?s)^<pre><code class="language-html">.*?</code></pre>\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
</ul>
`},
} {
blackFridayConfig := NewBlackfriday(viper.GetViper())
blackFridayConfig := c.NewBlackfriday()
blackFridayConfig.TaskLists = this.taskListEnabled
ctx := &RenderingContext{Content: []byte(this.markdown), PageFmt: "markdown", Config: blackFridayConfig}
result := string(RenderBytes(ctx))
result := string(c.RenderBytes(ctx))
if result != this.expect {
t.Errorf("[%d] got \n%v but expected \n%v", i, result, this.expect)

View File

@ -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("<p>testContent</p>\n")
if !bytes.Equal(actualRenderedMarkdown, expectedRenderedMarkdown) {
t.Errorf("Actual rendered Markdown (%s) did not match expected markdown (%s)", actualRenderedMarkdown, expectedRenderedMarkdown)
@ -317,10 +325,11 @@ func 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("<nav>\n</nav>\n\n<p>testContent</p>\n")
if !bytes.Equal(actualRenderedMarkdown, expectedRenderedMarkdown) {
t.Errorf("Actual rendered Markdown (%s) did not match expected markdown (%s)", actualRenderedMarkdown, expectedRenderedMarkdown)
@ -332,7 +341,8 @@ 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("<p>testContent</p>\n")
if !bytes.Equal(actualRenderedMarkdown, expectedRenderedMarkdown) {
t.Errorf("Actual rendered Markdown (%s) did not match expected markdown (%s)", actualRenderedMarkdown, expectedRenderedMarkdown)

View File

@ -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 {

View File

@ -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)
}

View File

@ -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"))

View File

@ -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.

View File

@ -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)
}

View File

@ -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.

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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 = "<html><body>{{.Content}}</body></html>"
const aliasTemplate = "<html><body>ALIASTEMPLATE</body></html>"
func TestAlias(t *testing.T) {
testCommonResetState()
t.Parallel()
fs := hugofs.NewMem()
var (
cfg, fs = newTestCfg()
th = testHelper{cfg}
)
writeSource(t, fs, filepath.Join("content", "page.md"), pageWithAlias)
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), basicTemplate)
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
// the real page
assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
th.assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
// the alias redirector
assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "<meta http-equiv=\"refresh\" content=\"0; ")
th.assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "<meta http-equiv=\"refresh\" content=\"0; ")
}
func TestAliasTemplate(t *testing.T) {
testCommonResetState()
t.Parallel()
fs := hugofs.NewMem()
var (
cfg, fs = newTestCfg()
th = testHelper{cfg}
)
writeSource(t, fs, filepath.Join("content", "page.md"), pageWithAlias)
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), basicTemplate)
writeSource(t, fs, filepath.Join("layouts", "alias.html"), aliasTemplate)
sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
require.NoError(t, sites.Build(BuildCfg{}))
// the real page
assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
th.assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
// the alias redirector
assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "ALIASTEMPLATE")
th.assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "ALIASTEMPLATE")
}

View File

@ -19,10 +19,10 @@ import (
"strings"
"testing"
"github.com/spf13/viper"
"github.com/spf13/afero"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
"github.com/stretchr/testify/require"
)
var (
@ -111,26 +111,28 @@ ColorS:
`
)
func caseMixingTestsWriteCommonSources(t *testing.T, fs *hugofs.Fs) {
writeSource(t, fs, filepath.Join("content", "sect1", "page1.md"), caseMixingPage1)
writeSource(t, fs, filepath.Join("content", "sect2", "page2.md"), caseMixingPage2)
writeSource(t, fs, filepath.Join("content", "sect1", "page1.en.md"), caseMixingPage1En)
func caseMixingTestsWriteCommonSources(t *testing.T, fs afero.Fs) {
writeToFs(t, fs, filepath.Join("content", "sect1", "page1.md"), caseMixingPage1)
writeToFs(t, fs, filepath.Join("content", "sect2", "page2.md"), caseMixingPage2)
writeToFs(t, fs, filepath.Join("content", "sect1", "page1.en.md"), caseMixingPage1En)
writeSource(t, fs, "layouts/shortcodes/shortcode.html", `
writeToFs(t, fs, "layouts/shortcodes/shortcode.html", `
Shortcode Page: {{ .Page.Params.COLOR }}|{{ .Page.Params.Colors.Blue }}
Shortcode Site: {{ .Page.Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
`)
writeSource(t, fs, "layouts/partials/partial.html", `
writeToFs(t, fs, "layouts/partials/partial.html", `
Partial Page: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}
Partial Site: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
`)
writeSource(t, fs, "config.toml", caseMixingSiteConfigTOML)
writeToFs(t, fs, "config.toml", caseMixingSiteConfigTOML)
}
func TestCaseInsensitiveConfigurationVariations(t *testing.T) {
t.Parallel()
// See issues 2615, 1129, 2590 and maybe some others
// Also see 2598
//
@ -143,22 +145,22 @@ func TestCaseInsensitiveConfigurationVariations(t *testing.T) {
// language: new and overridden values, in regular fields and nested paramsmap
// page frontmatter: regular fields, blackfriday config, param with nested map
testCommonResetState()
mm := afero.NewMemMapFs()
depsCfg := newTestDepsConfig()
viper.SetFs(depsCfg.Fs.Source)
caseMixingTestsWriteCommonSources(t, mm)
caseMixingTestsWriteCommonSources(t, depsCfg.Fs)
cfg, err := LoadConfig(mm, "", "config.toml")
require.NoError(t, err)
if err := LoadGlobalConfig("", "config.toml"); err != nil {
t.Fatalf("Failed to load config: %s", err)
}
fs := hugofs.NewFrom(mm, cfg)
writeSource(t, depsCfg.Fs, filepath.Join("layouts", "_default", "baseof.html"), `
th := testHelper{cfg}
writeSource(t, fs, filepath.Join("layouts", "_default", "baseof.html"), `
Block Page Colors: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}
{{ block "main" . }}default{{end}}`)
writeSource(t, depsCfg.Fs, filepath.Join("layouts", "sect2", "single.html"), `
writeSource(t, fs, filepath.Join("layouts", "sect2", "single.html"), `
{{ define "main"}}
Page Colors: {{ .Params.CoLOR }}|{{ .Params.Colors.Blue }}
Site Colors: {{ .Site.Params.COlOR }}|{{ .Site.Params.COLORS.YELLOW }}
@ -167,7 +169,7 @@ Site Colors: {{ .Site.Params.COlOR }}|{{ .Site.Params.COLORS.YELLOW }}
{{ end }}
`)
writeSource(t, depsCfg.Fs, filepath.Join("layouts", "_default", "single.html"), `
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `
Page Title: {{ .Title }}
Site Title: {{ .Site.Title }}
Site Lang Mood: {{ .Site.Language.Params.MOoD }}
@ -177,7 +179,7 @@ Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
{{ partial "partial.html" . }}
`)
sites, err := NewHugoSitesFromConfiguration(depsCfg)
sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@ -189,7 +191,7 @@ Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
t.Fatalf("Failed to build sites: %s", err)
}
assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
th.assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
"Page Colors: red|heavenly",
"Site Colors: green|yellow",
"Site Lang Mood: Happy",
@ -202,7 +204,7 @@ Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
"&laquo;Hi&raquo;", // angled quotes
)
assertFileContent(t, sites.Fs, filepath.Join("public", "en", "sect1", "page1", "index.html"), true,
th.assertFileContent(t, sites.Fs, filepath.Join("public", "en", "sect1", "page1", "index.html"), true,
"Site Colors: Pink|golden",
"Page Colors: black|bluesy",
"Site Lang Mood: Thoughtful",
@ -211,7 +213,7 @@ Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
"&ldquo;Hi&rdquo;",
)
assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect2", "page2", "index.html"), true,
th.assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect2", "page2", "index.html"), true,
"Page Colors: black|sky",
"Site Colors: green|yellow",
"Shortcode Page: black|sky",
@ -222,6 +224,8 @@ Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
}
func TestCaseInsensitiveConfigurationForAllTemplateEngines(t *testing.T) {
t.Parallel()
noOp := func(s string) string {
return s
}
@ -252,16 +256,16 @@ func TestCaseInsensitiveConfigurationForAllTemplateEngines(t *testing.T) {
func doTestCaseInsensitiveConfigurationForTemplateEngine(t *testing.T, suffix string, templateFixer func(s string) string) {
testCommonResetState()
mm := afero.NewMemMapFs()
fs := hugofs.NewMem()
viper.SetFs(fs.Source)
caseMixingTestsWriteCommonSources(t, mm)
caseMixingTestsWriteCommonSources(t, fs)
cfg, err := LoadConfig(mm, "", "config.toml")
require.NoError(t, err)
if err := LoadGlobalConfig("", "config.toml"); err != nil {
t.Fatalf("Failed to load config: %s", err)
}
fs := hugofs.NewFrom(mm, cfg)
th := testHelper{cfg}
t.Log("Testing", suffix)
@ -280,7 +284,7 @@ p
writeSource(t, fs, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@ -292,7 +296,7 @@ p
t.Fatalf("Failed to build sites: %s", err)
}
assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
th.assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
"Page Colors: red|heavenly",
"Site Colors: green|yellow",
"Shortcode Page: red|heavenly",

View File

@ -16,94 +16,100 @@ package hugolib
import (
"fmt"
"github.com/spf13/afero"
"github.com/spf13/hugo/helpers"
"github.com/spf13/viper"
)
// LoadGlobalConfig loads Hugo configuration into the global Viper.
func LoadGlobalConfig(relativeSourcePath, configFilename string) error {
// LoadConfig loads Hugo configuration into a new Viper and then adds
// a set of defaults.
func LoadConfig(fs afero.Fs, relativeSourcePath, configFilename string) (*viper.Viper, error) {
v := viper.New()
v.SetFs(fs)
if relativeSourcePath == "" {
relativeSourcePath = "."
}
viper.AutomaticEnv()
viper.SetEnvPrefix("hugo")
viper.SetConfigFile(configFilename)
v.AutomaticEnv()
v.SetEnvPrefix("hugo")
v.SetConfigFile(configFilename)
// See https://github.com/spf13/viper/issues/73#issuecomment-126970794
if relativeSourcePath == "" {
viper.AddConfigPath(".")
v.AddConfigPath(".")
} else {
viper.AddConfigPath(relativeSourcePath)
v.AddConfigPath(relativeSourcePath)
}
err := viper.ReadInConfig()
err := v.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigParseError); ok {
return err
return nil, err
}
return fmt.Errorf("Unable to locate Config file. Perhaps you need to create a new site.\n Run `hugo help new` for details. (%s)\n", err)
return nil, fmt.Errorf("Unable to locate Config file. Perhaps you need to create a new site.\n Run `hugo help new` for details. (%s)\n", err)
}
viper.RegisterAlias("indexes", "taxonomies")
v.RegisterAlias("indexes", "taxonomies")
loadDefaultSettings()
loadDefaultSettingsFor(v)
return nil
return v, nil
}
func loadDefaultSettings() {
viper.SetDefault("cleanDestinationDir", false)
viper.SetDefault("watch", false)
viper.SetDefault("metaDataFormat", "toml")
viper.SetDefault("disable404", false)
viper.SetDefault("disableRSS", false)
viper.SetDefault("disableSitemap", false)
viper.SetDefault("disableRobotsTXT", false)
viper.SetDefault("contentDir", "content")
viper.SetDefault("layoutDir", "layouts")
viper.SetDefault("staticDir", "static")
viper.SetDefault("archetypeDir", "archetypes")
viper.SetDefault("publishDir", "public")
viper.SetDefault("dataDir", "data")
viper.SetDefault("i18nDir", "i18n")
viper.SetDefault("themesDir", "themes")
viper.SetDefault("defaultLayout", "post")
viper.SetDefault("buildDrafts", false)
viper.SetDefault("buildFuture", false)
viper.SetDefault("buildExpired", false)
viper.SetDefault("uglyURLs", false)
viper.SetDefault("verbose", false)
viper.SetDefault("ignoreCache", false)
viper.SetDefault("canonifyURLs", false)
viper.SetDefault("relativeURLs", false)
viper.SetDefault("removePathAccents", false)
viper.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
viper.SetDefault("permalinks", make(PermalinkOverrides, 0))
viper.SetDefault("sitemap", Sitemap{Priority: -1, Filename: "sitemap.xml"})
viper.SetDefault("defaultExtension", "html")
viper.SetDefault("pygmentsStyle", "monokai")
viper.SetDefault("pygmentsUseClasses", false)
viper.SetDefault("pygmentsCodeFences", false)
viper.SetDefault("pygmentsOptions", "")
viper.SetDefault("disableLiveReload", false)
viper.SetDefault("pluralizeListTitles", true)
viper.SetDefault("preserveTaxonomyNames", false)
viper.SetDefault("forceSyncStatic", false)
viper.SetDefault("footnoteAnchorPrefix", "")
viper.SetDefault("footnoteReturnLinkContents", "")
viper.SetDefault("newContentEditor", "")
viper.SetDefault("paginate", 10)
viper.SetDefault("paginatePath", "page")
viper.SetDefault("blackfriday", helpers.NewBlackfriday(viper.GetViper()))
viper.SetDefault("rSSUri", "index.xml")
viper.SetDefault("sectionPagesMenu", "")
viper.SetDefault("disablePathToLower", false)
viper.SetDefault("hasCJKLanguage", false)
viper.SetDefault("enableEmoji", false)
viper.SetDefault("pygmentsCodeFencesGuessSyntax", false)
viper.SetDefault("useModTimeAsFallback", false)
viper.SetDefault("currentContentLanguage", helpers.NewDefaultLanguage())
viper.SetDefault("defaultContentLanguage", "en")
viper.SetDefault("defaultContentLanguageInSubdir", false)
viper.SetDefault("enableMissingTranslationPlaceholders", false)
viper.SetDefault("enableGitInfo", false)
func loadDefaultSettingsFor(v *viper.Viper) {
c := helpers.NewContentSpec(v)
v.SetDefault("cleanDestinationDir", false)
v.SetDefault("watch", false)
v.SetDefault("metaDataFormat", "toml")
v.SetDefault("disable404", false)
v.SetDefault("disableRSS", false)
v.SetDefault("disableSitemap", false)
v.SetDefault("disableRobotsTXT", false)
v.SetDefault("contentDir", "content")
v.SetDefault("layoutDir", "layouts")
v.SetDefault("staticDir", "static")
v.SetDefault("archetypeDir", "archetypes")
v.SetDefault("publishDir", "public")
v.SetDefault("dataDir", "data")
v.SetDefault("i18nDir", "i18n")
v.SetDefault("themesDir", "themes")
v.SetDefault("defaultLayout", "post")
v.SetDefault("buildDrafts", false)
v.SetDefault("buildFuture", false)
v.SetDefault("buildExpired", false)
v.SetDefault("uglyURLs", false)
v.SetDefault("verbose", false)
v.SetDefault("ignoreCache", false)
v.SetDefault("canonifyURLs", false)
v.SetDefault("relativeURLs", false)
v.SetDefault("removePathAccents", false)
v.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
v.SetDefault("permalinks", make(PermalinkOverrides, 0))
v.SetDefault("sitemap", Sitemap{Priority: -1, Filename: "sitemap.xml"})
v.SetDefault("defaultExtension", "html")
v.SetDefault("pygmentsStyle", "monokai")
v.SetDefault("pygmentsUseClasses", false)
v.SetDefault("pygmentsCodeFences", false)
v.SetDefault("pygmentsOptions", "")
v.SetDefault("disableLiveReload", false)
v.SetDefault("pluralizeListTitles", true)
v.SetDefault("preserveTaxonomyNames", false)
v.SetDefault("forceSyncStatic", false)
v.SetDefault("footnoteAnchorPrefix", "")
v.SetDefault("footnoteReturnLinkContents", "")
v.SetDefault("newContentEditor", "")
v.SetDefault("paginate", 10)
v.SetDefault("paginatePath", "page")
v.SetDefault("blackfriday", c.NewBlackfriday())
v.SetDefault("rSSUri", "index.xml")
v.SetDefault("sectionPagesMenu", "")
v.SetDefault("disablePathToLower", false)
v.SetDefault("hasCJKLanguage", false)
v.SetDefault("enableEmoji", false)
v.SetDefault("pygmentsCodeFencesGuessSyntax", false)
v.SetDefault("useModTimeAsFallback", false)
v.SetDefault("defaultContentLanguage", "en")
v.SetDefault("defaultContentLanguageInSubdir", false)
v.SetDefault("enableMissingTranslationPlaceholders", false)
v.SetDefault("enableGitInfo", false)
}

View File

@ -16,28 +16,28 @@ package hugolib
import (
"testing"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLoadGlobalConfig(t *testing.T) {
func TestLoadConfig(t *testing.T) {
t.Parallel()
// Add a random config variable for testing.
// side = page in Norwegian.
configContent := `
PaginatePath = "side"
`
fs := hugofs.NewMem()
viper.SetFs(fs.Source)
mm := afero.NewMemMapFs()
writeSource(t, fs, "hugo.toml", configContent)
writeToFs(t, mm, "hugo.toml", configContent)
require.NoError(t, LoadGlobalConfig("", "hugo.toml"))
assert.Equal(t, "side", helpers.Config().GetString("paginatePath"))
cfg, err := LoadConfig(mm, "", "hugo.toml")
require.NoError(t, err)
assert.Equal(t, "side", cfg.GetString("paginatePath"))
// default
assert.Equal(t, "layouts", viper.GetString("layoutDir"))
assert.Equal(t, "layouts", cfg.GetString("layoutDir"))
}

View File

@ -19,15 +19,24 @@ import (
"strings"
"testing"
"io/ioutil"
"log"
"os"
"github.com/spf13/hugo/deps"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/hugo/parser"
"github.com/spf13/hugo/source"
"github.com/stretchr/testify/require"
)
func TestDataDirJSON(t *testing.T) {
t.Parallel()
sources := []source.ByteSource{
{Name: filepath.FromSlash("test/foo.json"), Content: []byte(`{ "bar": "foofoo" }`)},
{Name: filepath.FromSlash("test.json"), Content: []byte(`{ "hello": [ { "world": "foo" } ] }`)},
{Name: filepath.FromSlash("data/test/foo.json"), Content: []byte(`{ "bar": "foofoo" }`)},
{Name: filepath.FromSlash("data/test.json"), Content: []byte(`{ "hello": [ { "world": "foo" } ] }`)},
}
expected, err := parser.HandleJSONMetaData([]byte(`{ "test": { "hello": [{ "world": "foo" }] , "foo": { "bar":"foofoo" } } }`))
@ -36,12 +45,14 @@ func TestDataDirJSON(t *testing.T) {
t.Fatalf("Error %s", err)
}
doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: sources}})
doTestDataDir(t, expected, sources)
}
func TestDataDirToml(t *testing.T) {
t.Parallel()
sources := []source.ByteSource{
{Name: filepath.FromSlash("test/kung.toml"), Content: []byte("[foo]\nbar = 1")},
{Name: filepath.FromSlash("data/test/kung.toml"), Content: []byte("[foo]\nbar = 1")},
}
expected, err := parser.HandleTOMLMetaData([]byte("[test]\n[test.kung]\n[test.kung.foo]\nbar = 1"))
@ -50,78 +61,102 @@ func TestDataDirToml(t *testing.T) {
t.Fatalf("Error %s", err)
}
doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: sources}})
doTestDataDir(t, expected, sources)
}
func TestDataDirYAMLWithOverridenValue(t *testing.T) {
t.Parallel()
sources := []source.ByteSource{
// filepath.Walk walks the files in lexical order, '/' comes before '.'. Simulate this:
{Name: filepath.FromSlash("a.yaml"), Content: []byte("a: 1")},
{Name: filepath.FromSlash("test/v1.yaml"), Content: []byte("v1-2: 2")},
{Name: filepath.FromSlash("test/v2.yaml"), Content: []byte("v2:\n- 2\n- 3")},
{Name: filepath.FromSlash("test.yaml"), Content: []byte("v1: 1")},
{Name: filepath.FromSlash("data/a.yaml"), Content: []byte("a: 1")},
{Name: filepath.FromSlash("data/test/v1.yaml"), Content: []byte("v1-2: 2")},
{Name: filepath.FromSlash("data/test/v2.yaml"), Content: []byte("v2:\n- 2\n- 3")},
{Name: filepath.FromSlash("data/test.yaml"), Content: []byte("v1: 1")},
}
expected := map[string]interface{}{"a": map[string]interface{}{"a": 1},
"test": map[string]interface{}{"v1": map[string]interface{}{"v1-2": 2}, "v2": map[string]interface{}{"v2": []interface{}{2, 3}}}}
doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: sources}})
doTestDataDir(t, expected, sources)
}
// issue 892
func TestDataDirMultipleSources(t *testing.T) {
s1 := []source.ByteSource{
{Name: filepath.FromSlash("test/first.toml"), Content: []byte("bar = 1")},
}
t.Parallel()
s2 := []source.ByteSource{
{Name: filepath.FromSlash("test/first.toml"), Content: []byte("bar = 2")},
{Name: filepath.FromSlash("test/second.toml"), Content: []byte("tender = 2")},
sources := []source.ByteSource{
{Name: filepath.FromSlash("data/test/first.toml"), Content: []byte("bar = 1")},
{Name: filepath.FromSlash("themes/mytheme/data/test/first.toml"), Content: []byte("bar = 2")},
{Name: filepath.FromSlash("data/test/second.toml"), Content: []byte("tender = 2")},
}
expected, _ := parser.HandleTOMLMetaData([]byte("[test.first]\nbar = 1\n[test.second]\ntender=2"))
doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: s1}, &source.InMemorySource{ByteSource: s2}})
doTestDataDir(t, expected, sources,
"theme", "mytheme")
}
func TestDataDirUnknownFormat(t *testing.T) {
t.Parallel()
sources := []source.ByteSource{
{Name: filepath.FromSlash("test.roml"), Content: []byte("boo")},
{Name: filepath.FromSlash("data/test.roml"), Content: []byte("boo")},
}
s, err := NewSiteDefaultLang()
require.NoError(t, err)
require.NoError(t, s.loadData([]source.Input{&source.InMemorySource{ByteSource: sources}}))
doTestDataDir(t, true, sources)
}
func doTestDataDir(t *testing.T, expected interface{}, sources []source.Input) {
s, err := NewSiteDefaultLang()
require.NoError(t, err)
require.NoError(t, s.loadData(sources))
func doTestDataDir(t *testing.T, expected interface{}, sources []source.ByteSource, configKeyValues ...interface{}) {
var (
cfg, fs = newTestCfg()
)
if !reflect.DeepEqual(expected, s.Data) {
for i := 0; i < len(configKeyValues); i += 2 {
cfg.Set(configKeyValues[i].(string), configKeyValues[i+1])
}
var (
logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
depsCfg = deps.DepsCfg{Fs: fs, Cfg: cfg, Logger: logger}
)
writeSource(t, fs, filepath.Join("content", "dummy.md"), "content")
writeSourcesToSource(t, "", fs, sources...)
expectBuildError := false
if ok, shouldFail := expected.(bool); ok && shouldFail {
expectBuildError = true
}
s := buildSingleSiteExpected(t, expectBuildError, depsCfg, BuildCfg{SkipRender: true})
if !expectBuildError && !reflect.DeepEqual(expected, s.Data) {
t.Errorf("Expected structure\n%#v got\n%#v", expected, s.Data)
}
}
func TestDataFromShortcode(t *testing.T) {
testCommonResetState()
t.Parallel()
cfg := newTestDepsConfig()
var (
cfg, fs = newTestCfg()
)
writeSource(t, cfg.Fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
writeSource(t, cfg.Fs, "layouts/_default/single.html", `
writeSource(t, fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
writeSource(t, fs, "layouts/_default/single.html", `
* Slogan from template: {{ .Site.Data.hugo.slogan }}
* {{ .Content }}`)
writeSource(t, cfg.Fs, "layouts/shortcodes/d.html", `{{ .Page.Site.Data.hugo.slogan }}`)
writeSource(t, cfg.Fs, "content/c.md", `---
writeSource(t, fs, "layouts/shortcodes/d.html", `{{ .Page.Site.Data.hugo.slogan }}`)
writeSource(t, fs, "content/c.md", `---
---
Slogan from shortcode: {{< d >}}
`)
buildSingleSite(t, cfg, BuildCfg{})
buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
content := readSource(t, cfg.Fs, "public/c/index.html")
content := readSource(t, fs, "public/c/index.html")
require.True(t, strings.Contains(content, "Slogan from template: Hugo Rocks!"), content)
require.True(t, strings.Contains(content, "Slogan from shortcode: Hugo Rocks!"), content)

File diff suppressed because one or more lines are too long

View File

@ -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, "")

View File

@ -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)
}

View File

@ -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"), "<!doctype html><html><body>more content</body></html>")
@ -46,7 +46,7 @@ func TestDefaultHandler(t *testing.T) {
writeSource(t, fs, filepath.FromSlash("head"), "<head><script src=\"script.js\"></script></head>")
writeSource(t, fs, filepath.FromSlash("head_abs"), "<head><script src=\"/script.js\"></script></head")
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
tests := []struct {
doc string

View File

@ -22,9 +22,7 @@ import (
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/viper"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/i18n"
"github.com/spf13/hugo/tpl"
"github.com/spf13/hugo/tplapi"
)
@ -48,7 +46,7 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
return nil, errors.New("Cannot provide Language in Cfg when sites are provided")
}
langConfig, err := newMultiLingualFromSites(sites...)
langConfig, err := newMultiLingualFromSites(cfg.Cfg, sites...)
if err != nil {
return nil, err
@ -62,6 +60,9 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
s.owner = h
}
// TODO(bep)
cfg.Cfg.Set("multilingual", sites[0].multilingualEnabled())
applyDepsIfNeeded(cfg, sites...)
h.Deps = sites[0].Deps
@ -70,11 +71,14 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
}
func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
if cfg.TemplateProvider == nil {
cfg.TemplateProvider = tpl.DefaultTemplateProvider
}
if cfg.TranslationProvider == nil {
cfg.TranslationProvider = i18n.NewTranslationProvider()
}
var (
d *deps.Deps
err error
@ -89,7 +93,9 @@ func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
cfg.Language = s.Language
cfg.WithTemplate = s.withSiteTemplates(cfg.WithTemplate)
d = deps.New(cfg)
if err := d.LoadTemplates(); err != nil {
s.Deps = d
if err := d.LoadResources(); err != nil {
return err
}
@ -98,16 +104,16 @@ func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
if err != nil {
return err
}
s.Deps = d
}
s.Deps = d
}
return nil
}
// NewHugoSitesFromConfiguration creates HugoSites from the global Viper config.
// TODO(bep) globals rename this when all the globals are gone.
func NewHugoSitesFromConfiguration(cfg deps.DepsCfg) (*HugoSites, error) {
// NewHugoSites creates HugoSites from the given config.
func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
sites, err := createSitesFromConfig(cfg)
if err != nil {
return nil, err
@ -141,10 +147,10 @@ func createSitesFromConfig(cfg deps.DepsCfg) ([]*Site, error) {
sites []*Site
)
multilingual := viper.GetStringMap("languages")
multilingual := cfg.Cfg.GetStringMap("languages")
if len(multilingual) == 0 {
l := helpers.NewDefaultLanguage()
l := helpers.NewDefaultLanguage(cfg.Cfg)
cfg.Language = l
s, err := newSite(cfg)
if err != nil {
@ -156,7 +162,7 @@ func createSitesFromConfig(cfg deps.DepsCfg) ([]*Site, error) {
if len(multilingual) > 0 {
var err error
languages, err := toSortedLanguages(multilingual)
languages, err := toSortedLanguages(cfg.Cfg, multilingual)
if err != nil {
return nil, fmt.Errorf("Failed to parse multilingual config: %s", err)
@ -190,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)
}

View File

@ -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)
}

View File

@ -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, `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`)
th.assertFileContent(t, fs, "public/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`)
} else {
// should have redirect back to root
assertFileContent(t, fs, "public/fr/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`)
th.assertFileContent(t, fs, "public/fr/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`)
}
assertFileContent(t, fs, "public/fr/index.html", defaultInSubDir, "Home", "Bonjour")
assertFileContent(t, fs, "public/en/index.html", defaultInSubDir, "Home", "Hello")
th.assertFileContent(t, fs, "public/fr/index.html", defaultInSubDir, "Home", "Bonjour")
th.assertFileContent(t, fs, "public/en/index.html", defaultInSubDir, "Home", "Hello")
// Check list pages
assertFileContent(t, fs, "public/fr/sect/index.html", defaultInSubDir, "List", "Bonjour")
assertFileContent(t, fs, "public/en/sect/index.html", defaultInSubDir, "List", "Hello")
assertFileContent(t, fs, "public/fr/plaques/frtag1/index.html", defaultInSubDir, "List", "Bonjour")
assertFileContent(t, fs, "public/en/tags/tag1/index.html", defaultInSubDir, "List", "Hello")
th.assertFileContent(t, fs, "public/fr/sect/index.html", defaultInSubDir, "List", "Bonjour")
th.assertFileContent(t, fs, "public/en/sect/index.html", defaultInSubDir, "List", "Hello")
th.assertFileContent(t, fs, "public/fr/plaques/frtag1/index.html", defaultInSubDir, "List", "Bonjour")
th.assertFileContent(t, fs, "public/en/tags/tag1/index.html", defaultInSubDir, "List", "Hello")
// Check sitemaps
// Sitemaps behaves different: In a multilanguage setup there will always be a index file and
// one sitemap in each lang folder.
assertFileContent(t, fs, "public/sitemap.xml", true,
th.assertFileContent(t, fs, "public/sitemap.xml", true,
"<loc>http://example.com/blog/en/sitemap.xml</loc>",
"<loc>http://example.com/blog/fr/sitemap.xml</loc>")
if defaultInSubDir {
assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/fr/</loc>")
th.assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/fr/</loc>")
} else {
assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/</loc>")
th.assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/</loc>")
}
assertFileContent(t, fs, "public/en/sitemap.xml", true, "<loc>http://example.com/blog/en/</loc>")
th.assertFileContent(t, fs, "public/en/sitemap.xml", true, "<loc>http://example.com/blog/en/</loc>")
// Check rss
assertFileContent(t, fs, "public/fr/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/index.xml"`)
assertFileContent(t, fs, "public/en/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/index.xml"`)
assertFileContent(t, fs, "public/fr/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/sect/index.xml"`)
assertFileContent(t, fs, "public/en/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/sect/index.xml"`)
assertFileContent(t, fs, "public/fr/plaques/frtag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/plaques/frtag1/index.xml"`)
assertFileContent(t, fs, "public/en/tags/tag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`)
th.assertFileContent(t, fs, "public/fr/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/index.xml"`)
th.assertFileContent(t, fs, "public/en/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/index.xml"`)
th.assertFileContent(t, fs, "public/fr/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/sect/index.xml"`)
th.assertFileContent(t, fs, "public/en/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/sect/index.xml"`)
th.assertFileContent(t, fs, "public/fr/plaques/frtag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/plaques/frtag1/index.xml"`)
th.assertFileContent(t, fs, "public/en/tags/tag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`)
// Check paginators
assertFileContent(t, fs, "public/fr/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/"`)
assertFileContent(t, fs, "public/en/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/"`)
assertFileContent(t, fs, "public/fr/page/2/index.html", defaultInSubDir, "Home Page 2", "Bonjour", "http://example.com/blog/fr/")
assertFileContent(t, fs, "public/en/page/2/index.html", defaultInSubDir, "Home Page 2", "Hello", "http://example.com/blog/en/")
assertFileContent(t, fs, "public/fr/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/sect/"`)
assertFileContent(t, fs, "public/en/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/sect/"`)
assertFileContent(t, fs, "public/fr/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/sect/")
assertFileContent(t, fs, "public/en/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/sect/")
assertFileContent(t, fs, "public/fr/plaques/frtag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/plaques/frtag1/"`)
assertFileContent(t, fs, "public/en/tags/tag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`)
assertFileContent(t, fs, "public/fr/plaques/frtag1/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/plaques/frtag1/")
assertFileContent(t, fs, "public/en/tags/tag1/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/")
th.assertFileContent(t, fs, "public/fr/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/"`)
th.assertFileContent(t, fs, "public/en/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/"`)
th.assertFileContent(t, fs, "public/fr/page/2/index.html", defaultInSubDir, "Home Page 2", "Bonjour", "http://example.com/blog/fr/")
th.assertFileContent(t, fs, "public/en/page/2/index.html", defaultInSubDir, "Home Page 2", "Hello", "http://example.com/blog/en/")
th.assertFileContent(t, fs, "public/fr/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/sect/"`)
th.assertFileContent(t, fs, "public/en/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/sect/"`)
th.assertFileContent(t, fs, "public/fr/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/sect/")
th.assertFileContent(t, fs, "public/en/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/sect/")
th.assertFileContent(t, fs, "public/fr/plaques/frtag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/plaques/frtag1/"`)
th.assertFileContent(t, fs, "public/en/tags/tag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`)
th.assertFileContent(t, fs, "public/fr/plaques/frtag1/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/plaques/frtag1/")
th.assertFileContent(t, fs, "public/en/tags/tag1/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/")
// nn (Nynorsk) and nb (Bokmål) have custom pagePath: side ("page" in Norwegian)
assertFileContent(t, fs, "public/nn/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nn/"`)
assertFileContent(t, fs, "public/nb/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nb/"`)
th.assertFileContent(t, fs, "public/nn/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nn/"`)
th.assertFileContent(t, fs, "public/nb/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nb/"`)
}
func replaceDefaultContentLanguageValue(value string, defaultInSubDir bool) string {
replace := viper.GetString("defaultContentLanguage") + "/"
type testHelper struct {
Cfg config.Provider
}
func (th testHelper) replaceDefaultContentLanguageValue(value string, defaultInSubDir bool) string {
replace := th.Cfg.GetString("defaultContentLanguage") + "/"
if !defaultInSubDir {
value = strings.Replace(value, replace, "", 1)
@ -167,36 +153,33 @@ func replaceDefaultContentLanguageValue(value string, defaultInSubDir bool) stri
}
func assertFileContent(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
func (th testHelper) assertFileContent(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
filename = th.replaceDefaultContentLanguageValue(filename, defaultInSubDir)
content := readDestination(t, fs, filename)
for _, match := range matches {
match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
match = th.replaceDefaultContentLanguageValue(match, defaultInSubDir)
require.True(t, strings.Contains(content, match), fmt.Sprintf("File no match for\n%q in\n%q:\n%s", strings.Replace(match, "%", "%%", -1), filename, strings.Replace(content, "%", "%%", -1)))
}
}
func assertFileContentRegexp(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
func (th testHelper) assertFileContentRegexp(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
filename = th.replaceDefaultContentLanguageValue(filename, defaultInSubDir)
content := readDestination(t, fs, filename)
for _, match := range matches {
match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
match = th.replaceDefaultContentLanguageValue(match, defaultInSubDir)
r := regexp.MustCompile(match)
require.True(t, r.MatchString(content), fmt.Sprintf("File no match for\n%q in\n%q:\n%s", match, filename, content))
require.True(t, r.MatchString(content), fmt.Sprintf("File no match for\n%q in\n%q:\n%s", strings.Replace(match, "%", "%%", -1), filename, strings.Replace(content, "%", "%%", -1)))
}
}
func TestMultiSitesWithTwoLanguages(t *testing.T) {
testCommonResetState()
t.Parallel()
mm := afero.NewMemMapFs()
viper.Set("defaultContentLanguage", "nn")
writeToFs(t, mm, "config.toml", `
fs := hugofs.NewMem()
defaultContentLanguage = "nn"
depsCfg := deps.DepsCfg{Fs: fs}
viper.SetFs(depsCfg.Fs.Source)
writeSource(t, depsCfg.Fs, "config.toml", `
[languages]
[languages.nn]
languageName = "Nynorsk"
@ -210,11 +193,12 @@ weight = 2
`,
)
if err := LoadGlobalConfig("", "config.toml"); err != nil {
t.Fatalf("Failed to load config: %s", err)
}
cfg, err := LoadConfig(mm, "", "config.toml")
require.NoError(t, err)
sites, err := NewHugoSitesFromConfiguration(depsCfg)
fs := hugofs.NewFrom(mm, cfg)
sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@ -238,100 +222,108 @@ weight = 2
//
func TestMultiSitesBuild(t *testing.T) {
t.Parallel()
for _, config := range []struct {
content string
suffix string
}{
{multiSiteTOMLConfigTemplate, "toml"},
{multiSiteYAMLConfig, "yml"},
{multiSiteJSONConfig, "json"},
{multiSiteYAMLConfigTemplate, "yml"},
{multiSiteJSONConfigTemplate, "json"},
} {
doTestMultiSitesBuild(t, config.content, config.suffix)
}
}
func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
defer leaktest.Check(t)()
testCommonResetState()
fs := hugofs.NewMem()
siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: true}
sites := createMultiTestSitesForConfig(t, siteConfig, configTemplate, configSuffix)
require.Len(t, sites.Sites, 4)
fs := sites.Fs
th := testHelper{sites.Cfg}
err := sites.Build(BuildCfg{})
if err != nil {
t.Fatalf("Failed to build sites: %s", err)
}
// Check site config
for _, s := range sites.Sites {
require.True(t, s.Info.defaultContentLanguageInSubdir, s.Info.Title)
}
enSite := sites.Sites[0]
enSiteHome := enSite.getPage(KindHome)
require.True(t, enSiteHome.IsTranslated())
assert.Equal(t, "en", enSite.Language.Lang)
require.Equal(t, "en", enSite.Language.Lang)
if len(enSite.RegularPages) != 4 {
t.Fatal("Expected 4 english pages")
}
assert.Len(t, enSite.Source.Files(), 14, "should have 13 source files")
assert.Len(t, enSite.AllPages, 28, "should have 28 total pages (including translations and index types)")
require.Len(t, enSite.Source.Files(), 14, "should have 13 source files")
require.Len(t, enSite.AllPages, 28, "should have 28 total pages (including translations and index types)")
doc1en := enSite.RegularPages[0]
permalink := doc1en.Permalink()
assert.NoError(t, err, "permalink call failed")
assert.Equal(t, "http://example.com/blog/en/sect/doc1-slug/", permalink, "invalid doc1.en permalink")
assert.Len(t, doc1en.Translations(), 1, "doc1-en should have one translation, excluding itself")
require.NoError(t, err, "permalink call failed")
require.Equal(t, "http://example.com/blog/en/sect/doc1-slug/", permalink, "invalid doc1.en permalink")
require.Len(t, doc1en.Translations(), 1, "doc1-en should have one translation, excluding itself")
doc2 := enSite.RegularPages[1]
permalink = doc2.Permalink()
assert.NoError(t, err, "permalink call failed")
assert.Equal(t, "http://example.com/blog/en/sect/doc2/", permalink, "invalid doc2 permalink")
require.NoError(t, err, "permalink call failed")
require.Equal(t, "http://example.com/blog/en/sect/doc2/", permalink, "invalid doc2 permalink")
doc3 := enSite.RegularPages[2]
permalink = doc3.Permalink()
assert.NoError(t, err, "permalink call failed")
require.NoError(t, err, "permalink call failed")
// Note that /superbob is a custom URL set in frontmatter.
// We respect that URL literally (it can be /search.json)
// and do no not do any language code prefixing.
assert.Equal(t, "http://example.com/blog/superbob", permalink, "invalid doc3 permalink")
require.Equal(t, "http://example.com/blog/superbob", permalink, "invalid doc3 permalink")
assert.Equal(t, "/superbob", doc3.URL(), "invalid url, was specified on doc3")
assertFileContent(t, fs, "public/superbob/index.html", true, "doc3|Hello|en")
assert.Equal(t, doc2.Next, doc3, "doc3 should follow doc2, in .Next")
require.Equal(t, "/superbob", doc3.URL(), "invalid url, was specified on doc3")
th.assertFileContent(t, fs, "public/superbob/index.html", true, "doc3|Hello|en")
require.Equal(t, doc2.Next, doc3, "doc3 should follow doc2, in .Next")
doc1fr := doc1en.Translations()[0]
permalink = doc1fr.Permalink()
assert.NoError(t, err, "permalink call failed")
assert.Equal(t, "http://example.com/blog/fr/sect/doc1/", permalink, "invalid doc1fr permalink")
require.NoError(t, err, "permalink call failed")
require.Equal(t, "http://example.com/blog/fr/sect/doc1/", permalink, "invalid doc1fr permalink")
assert.Equal(t, doc1en.Translations()[0], doc1fr, "doc1-en should have doc1-fr as translation")
assert.Equal(t, doc1fr.Translations()[0], doc1en, "doc1-fr should have doc1-en as translation")
assert.Equal(t, "fr", doc1fr.Language().Lang)
require.Equal(t, doc1en.Translations()[0], doc1fr, "doc1-en should have doc1-fr as translation")
require.Equal(t, doc1fr.Translations()[0], doc1en, "doc1-fr should have doc1-en as translation")
require.Equal(t, "fr", doc1fr.Language().Lang)
doc4 := enSite.AllPages[4]
permalink = doc4.Permalink()
assert.Equal(t, "http://example.com/blog/fr/sect/doc4/", permalink, "invalid doc4 permalink")
assert.Equal(t, "/blog/fr/sect/doc4/", doc4.URL())
require.Equal(t, "http://example.com/blog/fr/sect/doc4/", permalink, "invalid doc4 permalink")
require.Equal(t, "/blog/fr/sect/doc4/", doc4.URL())
assert.Len(t, doc4.Translations(), 0, "found translations for doc4")
require.Len(t, doc4.Translations(), 0, "found translations for doc4")
doc5 := enSite.AllPages[5]
permalink = doc5.Permalink()
assert.Equal(t, "http://example.com/blog/fr/somewhere/else/doc5", permalink, "invalid doc5 permalink")
require.Equal(t, "http://example.com/blog/fr/somewhere/else/doc5", permalink, "invalid doc5 permalink")
// Taxonomies and their URLs
assert.Len(t, enSite.Taxonomies, 1, "should have 1 taxonomy")
require.Len(t, enSite.Taxonomies, 1, "should have 1 taxonomy")
tags := enSite.Taxonomies["tags"]
assert.Len(t, tags, 2, "should have 2 different tags")
assert.Equal(t, tags["tag1"][0].Page, doc1en, "first tag1 page should be doc1")
require.Len(t, tags, 2, "should have 2 different tags")
require.Equal(t, tags["tag1"][0].Page, doc1en, "first tag1 page should be doc1")
frSite := sites.Sites[1]
assert.Equal(t, "fr", frSite.Language.Lang)
assert.Len(t, frSite.RegularPages, 3, "should have 3 pages")
assert.Len(t, frSite.AllPages, 28, "should have 28 total pages (including translations and nodes)")
require.Equal(t, "fr", frSite.Language.Lang)
require.Len(t, frSite.RegularPages, 3, "should have 3 pages")
require.Len(t, frSite.AllPages, 28, "should have 28 total pages (including translations and nodes)")
for _, frenchPage := range frSite.RegularPages {
assert.Equal(t, "fr", frenchPage.Lang())
require.Equal(t, "fr", frenchPage.Lang())
}
// Check redirect to main language, French
@ -339,12 +331,12 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
require.True(t, strings.Contains(languageRedirect, "0; url=http://example.com/blog/fr"), languageRedirect)
// check home page content (including data files rendering)
assertFileContent(t, fs, "public/en/index.html", true, "Home Page 1", "Hello", "Hugo Rocks!")
assertFileContent(t, fs, "public/fr/index.html", true, "Home Page 1", "Bonjour", "Hugo Rocks!")
th.assertFileContent(t, fs, "public/en/index.html", true, "Home Page 1", "Hello", "Hugo Rocks!")
th.assertFileContent(t, fs, "public/fr/index.html", true, "Home Page 1", "Bonjour", "Hugo Rocks!")
// check single page content
assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
th.assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
th.assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
// Check node translations
homeEn := enSite.getPage(KindHome)
@ -397,9 +389,9 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
readDestination(t, fs, "public/en/tags/tag1/index.html")
// Check Blackfriday config
assert.True(t, strings.Contains(string(doc1fr.Content), "&laquo;"), string(doc1fr.Content))
assert.False(t, strings.Contains(string(doc1en.Content), "&laquo;"), string(doc1en.Content))
assert.True(t, strings.Contains(string(doc1en.Content), "&ldquo;"), string(doc1en.Content))
require.True(t, strings.Contains(string(doc1fr.Content), "&laquo;"), string(doc1fr.Content))
require.False(t, strings.Contains(string(doc1en.Content), "&laquo;"), string(doc1en.Content))
require.True(t, strings.Contains(string(doc1en.Content), "&ldquo;"), string(doc1en.Content))
// Check that the drafts etc. are not built/processed/rendered.
assertShouldNotBuild(t, sites)
@ -415,13 +407,14 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
}
func TestMultiSitesRebuild(t *testing.T) {
// t.Parallel() not supported, see https://github.com/fortytw2/leaktest/issues/4
defer leaktest.Check(t)()
testCommonResetState()
fs := hugofs.NewMem()
siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: true}
sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
fs := sites.Fs
cfg := BuildCfg{Watching: true}
th := testHelper{sites.Cfg}
err := sites.Build(cfg)
@ -442,12 +435,12 @@ func TestMultiSitesRebuild(t *testing.T) {
require.Len(t, frSite.RegularPages, 3)
// Verify translations
assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Hello")
assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Bonjour")
th.assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Hello")
th.assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Bonjour")
// check single page content
assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
th.assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
th.assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
for i, this := range []struct {
preFunc func(t *testing.T)
@ -585,8 +578,8 @@ func TestMultiSitesRebuild(t *testing.T) {
require.Len(t, enSite.RegularPages, 5)
require.Len(t, enSite.AllPages, 30)
require.Len(t, frSite.RegularPages, 4)
assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Modified Shortcode: Salut")
assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Modified Shortcode: Hello")
th.assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Modified Shortcode: Salut")
th.assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Modified Shortcode: Hello")
},
},
} {
@ -630,9 +623,8 @@ func assertShouldNotBuild(t *testing.T, sites *HugoSites) {
}
func TestAddNewLanguage(t *testing.T) {
testCommonResetState()
fs := hugofs.NewMem()
siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
t.Parallel()
siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: true}
sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
cfg := BuildCfg{}
@ -643,6 +635,8 @@ func TestAddNewLanguage(t *testing.T) {
t.Fatalf("Failed to build sites: %s", err)
}
fs := sites.Fs
newConfig := multiSiteTOMLConfigTemplate + `
[Languages.sv]
@ -657,7 +651,7 @@ title = "Svenska"
writeSource(t, fs, "multilangconfig.toml", newConfig)
// Watching does not work with in-memory fs, so we trigger a reload manually
require.NoError(t, viper.ReadInConfig())
require.NoError(t, sites.Cfg.(*helpers.Language).Cfg.(*viper.Viper).ReadInConfig())
err = sites.Build(BuildCfg{CreateSitesFromConfig: true})
if err != nil {
@ -694,11 +688,15 @@ title = "Svenska"
}
func TestChangeDefaultLanguage(t *testing.T) {
testCommonResetState()
viper.Set("defaultContentLanguageInSubdir", false)
fs := hugofs.NewMem()
sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}, multiSiteTOMLConfigTemplate)
t.Parallel()
mf := afero.NewMemMapFs()
sites := createMultiTestSites(t, testSiteConfig{Fs: mf, DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: false}, multiSiteTOMLConfigTemplate)
require.Equal(t, mf, sites.Fs.Source)
cfg := BuildCfg{}
th := testHelper{sites.Cfg}
err := sites.Build(cfg)
@ -706,16 +704,19 @@ func TestChangeDefaultLanguage(t *testing.T) {
t.Fatalf("Failed to build sites: %s", err)
}
assertFileContent(t, fs, "public/sect/doc1/index.html", true, "Single", "Bonjour")
assertFileContent(t, fs, "public/en/sect/doc2/index.html", true, "Single", "Hello")
fs := sites.Fs
newConfig := createConfig(t, testSiteConfig{DefaultContentLanguage: "en"}, multiSiteTOMLConfigTemplate)
th.assertFileContent(t, fs, "public/sect/doc1/index.html", false, "Single", "Bonjour")
th.assertFileContent(t, fs, "public/en/sect/doc2/index.html", false, "Single", "Hello")
newConfig := createConfig(t, testSiteConfig{Fs: mf, DefaultContentLanguage: "en", DefaultContentLanguageInSubdir: false}, multiSiteTOMLConfigTemplate)
// replace the config
writeSource(t, fs, "multilangconfig.toml", newConfig)
// Watching does not work with in-memory fs, so we trigger a reload manually
require.NoError(t, viper.ReadInConfig())
// This does not look pretty, so we should think of something else.
require.NoError(t, th.Cfg.(*helpers.Language).Cfg.(*viper.Viper).ReadInConfig())
err = sites.Build(BuildCfg{CreateSitesFromConfig: true})
if err != nil {
@ -723,19 +724,19 @@ func TestChangeDefaultLanguage(t *testing.T) {
}
// Default language is now en, so that should now be the "root" language
assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Bonjour")
assertFileContent(t, fs, "public/sect/doc2/index.html", true, "Single", "Hello")
th.assertFileContent(t, fs, "public/fr/sect/doc1/index.html", false, "Single", "Bonjour")
th.assertFileContent(t, fs, "public/sect/doc2/index.html", false, "Single", "Hello")
}
func TestTableOfContentsInShortcodes(t *testing.T) {
testCommonResetState()
fs := hugofs.NewMem()
t.Parallel()
mf := afero.NewMemMapFs()
writeSource(t, fs, "layouts/shortcodes/toc.html", tocShortcode)
writeSource(t, fs, "content/post/simple.en.md", tocPageSimple)
writeSource(t, fs, "content/post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings)
writeToFs(t, mf, "layouts/shortcodes/toc.html", tocShortcode)
writeToFs(t, mf, "content/post/simple.en.md", tocPageSimple)
writeToFs(t, mf, "content/post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings)
sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "en", Fs: fs}, multiSiteTOMLConfigTemplate)
sites := createMultiTestSites(t, testSiteConfig{Fs: mf, DefaultContentLanguage: "en", DefaultContentLanguageInSubdir: true}, multiSiteTOMLConfigTemplate)
cfg := BuildCfg{}
@ -745,8 +746,11 @@ func TestTableOfContentsInShortcodes(t *testing.T) {
t.Fatalf("Failed to build sites: %s", err)
}
assertFileContent(t, fs, "public/en/post/simple/index.html", true, tocPageSimpleExpected)
assertFileContent(t, fs, "public/en/post/withSCInHeading/index.html", true, tocPageWithShortcodesInHeadingsExpected)
th := testHelper{sites.Cfg}
fs := sites.Fs
th.assertFileContent(t, fs, "public/en/post/simple/index.html", true, tocPageSimpleExpected)
th.assertFileContent(t, fs, "public/en/post/withSCInHeading/index.html", true, tocPageWithShortcodesInHeadingsExpected)
}
var tocShortcode = `
@ -836,6 +840,7 @@ rssURI = "index.xml"
paginate = 1
defaultContentLanguage = "{{ .DefaultContentLanguage }}"
defaultContentLanguageInSubdir = {{ .DefaultContentLanguageInSubdir }}
[permalinks]
other = "/somewhere/else/:filename"
@ -886,7 +891,7 @@ paginatePath = "side"
lag = "lag"
`
var multiSiteYAMLConfig = `
var multiSiteYAMLConfigTemplate = `
defaultExtension: "html"
baseURL: "http://example.com/blog"
disableSitemap: false
@ -894,7 +899,8 @@ disableRSS: false
rssURI: "index.xml"
paginate: 1
defaultContentLanguage: "fr"
defaultContentLanguage: "{{ .DefaultContentLanguage }}"
defaultContentLanguageInSubdir: {{ .DefaultContentLanguageInSubdir }}
permalinks:
other: "/somewhere/else/:filename"
@ -945,7 +951,7 @@ Languages:
`
var multiSiteJSONConfig = `
var multiSiteJSONConfigTemplate = `
{
"defaultExtension": "html",
"baseURL": "http://example.com/blog",
@ -953,7 +959,8 @@ var multiSiteJSONConfig = `
"disableRSS": false,
"rssURI": "index.xml",
"paginate": 1,
"defaultContentLanguage": "fr",
"defaultContentLanguage": "{{ .DefaultContentLanguage }}",
"defaultContentLanguageInSubdir": true,
"permalinks": {
"other": "/somewhere/else/:filename"
},
@ -1026,25 +1033,26 @@ func createMultiTestSites(t *testing.T, siteConfig testSiteConfig, tomlConfigTem
func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, configTemplate, configSuffix string) *HugoSites {
depsCfg := deps.DepsCfg{Fs: siteConfig.Fs}
configContent := createConfig(t, siteConfig, configTemplate)
mf := siteConfig.Fs
// Add some layouts
if err := afero.WriteFile(depsCfg.Fs.Source,
if err := afero.WriteFile(mf,
filepath.Join("layouts", "_default/single.html"),
[]byte("Single: {{ .Title }}|{{ i18n \"hello\" }}|{{.Lang}}|{{ .Content }}"),
0755); err != nil {
t.Fatalf("Failed to write layout file: %s", err)
}
if err := afero.WriteFile(depsCfg.Fs.Source,
if err := afero.WriteFile(mf,
filepath.Join("layouts", "_default/list.html"),
[]byte("{{ $p := .Paginator }}List Page {{ $p.PageNumber }}: {{ .Title }}|{{ i18n \"hello\" }}|{{ .Permalink }}"),
0755); err != nil {
t.Fatalf("Failed to write layout file: %s", err)
}
if err := afero.WriteFile(depsCfg.Fs.Source,
if err := afero.WriteFile(mf,
filepath.Join("layouts", "index.html"),
[]byte("{{ $p := .Paginator }}Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}"),
0755); err != nil {
@ -1052,7 +1060,7 @@ func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, conf
}
// Add a shortcode
if err := afero.WriteFile(depsCfg.Fs.Source,
if err := afero.WriteFile(mf,
filepath.Join("layouts", "shortcodes", "shortcode.html"),
[]byte("Shortcode: {{ i18n \"hello\" }}"),
0755); err != nil {
@ -1060,7 +1068,7 @@ func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, conf
}
// Add some language files
if err := afero.WriteFile(depsCfg.Fs.Source,
if err := afero.WriteFile(mf,
filepath.Join("i18n", "en.yaml"),
[]byte(`
- id: hello
@ -1069,7 +1077,7 @@ func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, conf
0755); err != nil {
t.Fatalf("Failed to write language file: %s", err)
}
if err := afero.WriteFile(depsCfg.Fs.Source,
if err := afero.WriteFile(mf,
filepath.Join("i18n", "fr.yaml"),
[]byte(`
- id: hello
@ -1223,26 +1231,25 @@ lag:
}
configFile := "multilangconfig." + configSuffix
writeSource(t, depsCfg.Fs, configFile, configContent)
writeToFs(t, mf, configFile, configContent)
viper.SetFs(depsCfg.Fs.Source)
cfg, err := LoadConfig(mf, "", configFile)
require.NoError(t, err)
if err := LoadGlobalConfig("", configFile); err != nil {
t.Fatalf("Failed to load config: %s", err)
}
fs := hugofs.NewFrom(mf, cfg)
// Hugo support using ByteSource's directly (for testing),
// but to make it more real, we write them to the mem file system.
for _, s := range sources {
if err := afero.WriteFile(depsCfg.Fs.Source, filepath.Join("content", s.Name), s.Content, 0755); err != nil {
if err := afero.WriteFile(mf, filepath.Join("content", s.Name), s.Content, 0755); err != nil {
t.Fatalf("Failed to write file: %s", err)
}
}
// Add some data
writeSource(t, depsCfg.Fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
writeSource(t, fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
sites, err := NewHugoSitesFromConfiguration(depsCfg)
sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg}) //, Logger: newDebugLogger()})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@ -1252,11 +1259,19 @@ lag:
t.Fatalf("Got %d sites", len(sites.Sites))
}
if sites.Fs.Source != mf {
t.Fatal("FS mismatch")
}
return sites
}
func writeSource(t *testing.T, fs *hugofs.Fs, filename, content string) {
if err := afero.WriteFile(fs.Source, filepath.FromSlash(filename), []byte(content), 0755); err != nil {
writeToFs(t, fs.Source, filename, content)
}
func writeToFs(t *testing.T, fs afero.Fs, filename, content string) {
if err := afero.WriteFile(fs, filepath.FromSlash(filename), []byte(content), 0755); err != nil {
t.Fatalf("Failed to write file: %s", err)
}
}

View File

@ -1,42 +0,0 @@
// Copyright 2016 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
import (
"fmt"
"github.com/nicksnyder/go-i18n/i18n/bundle"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/tpl"
)
func (s *Site) loadI18n(sources []source.Input) error {
s.Log.DEBUG.Printf("Load I18n from %q", sources)
i18nBundle := bundle.New()
for _, currentSource := range sources {
for _, r := range currentSource.Files() {
err := i18nBundle.ParseTranslationFileBytes(r.LogicalName(), r.Bytes())
if err != nil {
return fmt.Errorf("Failed to load translations in file %q: %s", r.LogicalName(), err)
}
}
}
tpl.SetI18nTfuncs(i18nBundle)
return nil
}

View File

@ -20,14 +20,10 @@ import (
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"path/filepath"
toml "github.com/pelletier/go-toml"
"github.com/spf13/hugo/source"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -187,7 +183,7 @@ Front Matter with Menu with Identifier`, title, menu, identifier))
// Issue 817 - identifier should trump everything
func TestPageMenuWithIdentifier(t *testing.T) {
t.Parallel()
toml := []source.ByteSource{
{Name: "sect/doc1.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i1")},
{Name: "sect/doc2.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i2")},
@ -206,7 +202,6 @@ func TestPageMenuWithIdentifier(t *testing.T) {
}
func doTestPageMenuWithIdentifier(t *testing.T, menuPageSources []source.ByteSource) {
testCommonResetState()
s := setupMenuTests(t, menuPageSources)
@ -225,7 +220,7 @@ func doTestPageMenuWithIdentifier(t *testing.T, menuPageSources []source.ByteSou
// Issue 817 contd - name should be second identifier in
func TestPageMenuWithDuplicateName(t *testing.T) {
t.Parallel()
toml := []source.ByteSource{
{Name: "sect/doc1.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n1")},
{Name: "sect/doc2.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n2")},
@ -244,7 +239,6 @@ func TestPageMenuWithDuplicateName(t *testing.T) {
}
func doTestPageMenuWithDuplicateName(t *testing.T, menuPageSources []source.ByteSource) {
testCommonResetState()
s := setupMenuTests(t, menuPageSources)
@ -262,8 +256,7 @@ func doTestPageMenuWithDuplicateName(t *testing.T, menuPageSources []source.Byte
}
func TestPageMenu(t *testing.T) {
testCommonResetState()
t.Parallel()
s := setupMenuTests(t, menuPageSources)
if len(s.RegularPages) != 3 {
@ -312,8 +305,7 @@ func TestPageMenu(t *testing.T) {
}
func TestMenuURL(t *testing.T) {
testCommonResetState()
t.Parallel()
s := setupMenuTests(t, menuPageSources)
for i, this := range []struct {
@ -342,8 +334,7 @@ func TestMenuURL(t *testing.T) {
// Issue #1934
func TestYAMLMenuWithMultipleEntries(t *testing.T) {
testCommonResetState()
t.Parallel()
ps1 := []byte(`---
title: "Yaml 1"
weight: 5
@ -373,18 +364,15 @@ Yaml Front Matter with Menu Pages`)
// issue #719
func TestMenuWithUnicodeURLs(t *testing.T) {
t.Parallel()
for _, canonifyURLs := range []bool{true, false} {
doTestMenuWithUnicodeURLs(t, canonifyURLs)
}
}
func doTestMenuWithUnicodeURLs(t *testing.T, canonifyURLs bool) {
testCommonResetState()
viper.Set("canonifyURLs", canonifyURLs)
s := setupMenuTests(t, menuPageSources)
s := setupMenuTests(t, menuPageSources, "canonifyURLs", canonifyURLs)
unicodeRussian := findTestMenuEntryByID(s, "unicode", "unicode-russian")
@ -399,18 +387,17 @@ func doTestMenuWithUnicodeURLs(t *testing.T, canonifyURLs bool) {
// Issue #1114
func TestSectionPagesMenu(t *testing.T) {
t.Parallel()
doTestSectionPagesMenu(true, t)
doTestSectionPagesMenu(false, t)
}
func doTestSectionPagesMenu(canonifyURLs bool, t *testing.T) {
testCommonResetState()
viper.Set("sectionPagesMenu", "spm")
viper.Set("canonifyURLs", canonifyURLs)
s := setupMenuTests(t, menuPageSectionsSources)
s := setupMenuTests(t, menuPageSectionsSources,
"sectionPagesMenu", "spm",
"canonifyURLs", canonifyURLs,
)
require.Equal(t, 3, len(s.Sections))
@ -463,16 +450,15 @@ func doTestSectionPagesMenu(canonifyURLs bool, t *testing.T) {
}
func TestTaxonomyNodeMenu(t *testing.T) {
t.Parallel()
type taxRenderInfo struct {
key string
singular string
plural string
}
testCommonResetState()
viper.Set("canonifyURLs", true)
s := setupMenuTests(t, menuPageSources)
s := setupMenuTests(t, menuPageSources, "canonifyURLs", true)
for i, this := range []struct {
menu string
@ -512,8 +498,7 @@ func TestTaxonomyNodeMenu(t *testing.T) {
}
func TestMenuLimit(t *testing.T) {
testCommonResetState()
t.Parallel()
s := setupMenuTests(t, menuPageSources)
m := *s.Menus["main"]
@ -528,7 +513,7 @@ func TestMenuLimit(t *testing.T) {
}
func TestMenuSortByN(t *testing.T) {
t.Parallel()
for i, this := range []struct {
sortFunc func(p Menu) Menu
assertFunc func(p Menu) bool
@ -554,12 +539,11 @@ func TestMenuSortByN(t *testing.T) {
}
func TestHomeNodeMenu(t *testing.T) {
testCommonResetState()
viper.Set("canonifyURLs", true)
viper.Set("uglyURLs", false)
s := setupMenuTests(t, menuPageSources)
t.Parallel()
s := setupMenuTests(t, menuPageSources,
"canonifyURLs", true,
"uglyURLs", false,
)
home := s.getPage(KindHome)
homeMenuEntry := &MenuEntry{Name: home.Title, URL: home.URL()}
@ -596,12 +580,14 @@ func TestHomeNodeMenu(t *testing.T) {
}
func TestHopefullyUniqueID(t *testing.T) {
t.Parallel()
assert.Equal(t, "i", (&MenuEntry{Identifier: "i", URL: "u", Name: "n"}).hopefullyUniqueID())
assert.Equal(t, "u", (&MenuEntry{Identifier: "", URL: "u", Name: "n"}).hopefullyUniqueID())
assert.Equal(t, "n", (&MenuEntry{Identifier: "", URL: "", Name: "n"}).hopefullyUniqueID())
}
func TestAddMenuEntryChild(t *testing.T) {
t.Parallel()
root := &MenuEntry{Weight: 1}
root.addChild(&MenuEntry{Weight: 2})
root.addChild(&MenuEntry{Weight: 1})
@ -667,38 +653,28 @@ func findDescendantTestMenuEntry(parent *MenuEntry, id string, matcher func(me *
return found
}
func setupTestMenuState(t *testing.T) {
func setupMenuTests(t *testing.T, pageSources []source.ByteSource, configKeyValues ...interface{}) *Site {
var (
cfg, fs = newTestCfg()
)
menus, err := tomlToMap(confMenu1)
require.NoError(t, err)
if err != nil {
t.Fatalf("Unable to read menus: %v", err)
cfg.Set("menu", menus["menu"])
cfg.Set("baseURL", "http://foo.local/Zoo/")
for i := 0; i < len(configKeyValues); i += 2 {
cfg.Set(configKeyValues[i].(string), configKeyValues[i+1])
}
viper.Set("menu", menus["menu"])
viper.Set("baseURL", "http://foo.local/Zoo/")
}
func setupMenuTests(t *testing.T, pageSources []source.ByteSource) *Site {
setupTestMenuState(t)
fs := hugofs.NewMem()
for _, src := range pageSources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
}
func createTestSite(pageSources []source.ByteSource) *Site {
return &Site{
Source: &source.InMemorySource{ByteSource: pageSources},
Language: helpers.NewDefaultLanguage(),
}
return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
}

View File

@ -22,6 +22,7 @@ import (
"fmt"
"github.com/spf13/cast"
"github.com/spf13/hugo/config"
"github.com/spf13/hugo/helpers"
)
@ -44,7 +45,7 @@ func (ml *Multilingual) Language(lang string) *helpers.Language {
return ml.langMap[lang]
}
func newMultiLingualFromSites(sites ...*Site) (*Multilingual, error) {
func newMultiLingualFromSites(cfg config.Provider, sites ...*Site) (*Multilingual, error) {
languages := make(helpers.Languages, len(sites))
for i, s := range sites {
@ -54,12 +55,14 @@ func newMultiLingualFromSites(sites ...*Site) (*Multilingual, error) {
languages[i] = s.Language
}
return &Multilingual{Languages: languages, DefaultLang: helpers.NewDefaultLanguage()}, nil
defaultLang := cfg.GetString("defaultContentLanguage")
}
if defaultLang == "" {
defaultLang = "en"
}
return &Multilingual{Languages: languages, DefaultLang: helpers.NewLanguage(defaultLang, cfg)}, nil
func newMultiLingualDefaultLanguage() *Multilingual {
return newMultiLingualForLanguage(helpers.NewDefaultLanguage())
}
func newMultiLingualForLanguage(language *helpers.Language) *Multilingual {
@ -77,7 +80,7 @@ func (s *Site) multilingualEnabled() bool {
return s.owner.multilingual != nil && s.owner.multilingual.enabled()
}
func toSortedLanguages(l map[string]interface{}) (helpers.Languages, error) {
func toSortedLanguages(cfg config.Provider, l map[string]interface{}) (helpers.Languages, error) {
langs := make(helpers.Languages, len(l))
i := 0
@ -88,7 +91,7 @@ func toSortedLanguages(l map[string]interface{}) (helpers.Languages, error) {
return nil, fmt.Errorf("Language config is not a map: %T", langConf)
}
language := helpers.NewLanguage(lang)
language := helpers.NewLanguage(lang, cfg)
for loki, v := range langsMap {
switch loki {

View File

@ -21,9 +21,10 @@ import (
"time"
"github.com/spf13/afero"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
@ -35,6 +36,7 @@ import (
*/
func TestNodesAsPage(t *testing.T) {
t.Parallel()
for _, preserveTaxonomyNames := range []bool{false, true} {
for _, ugly := range []bool{true, false} {
doTestNodeAsPage(t, ugly, preserveTaxonomyNames)
@ -54,25 +56,24 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) {
*/
testCommonResetState()
var (
cfg, fs = newTestCfg()
th = testHelper{cfg}
)
viper.Set("uglyURLs", ugly)
viper.Set("preserveTaxonomyNames", preserveTaxonomyNames)
cfg.Set("uglyURLs", ugly)
cfg.Set("preserveTaxonomyNames", preserveTaxonomyNames)
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks")
viper.Set("rssURI", "customrss.xml")
cfg.Set("paginate", 1)
cfg.Set("title", "Hugo Rocks")
cfg.Set("rssURI", "customrss.xml")
depsCfg := newTestDepsConfig()
writeLayoutsForNodeAsPageTests(t, fs)
writeNodePagesForNodeAsPageTests(t, fs, "")
viper.SetFs(depsCfg.Fs.Source)
writeRegularPagesForNodeAsPageTests(t, fs)
writeLayoutsForNodeAsPageTests(t, depsCfg.Fs)
writeNodePagesForNodeAsPageTests(t, depsCfg.Fs, "")
writeRegularPagesForNodeAsPageTests(t, depsCfg.Fs)
sites, err := NewHugoSitesFromConfiguration(depsCfg)
sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
@ -80,7 +81,7 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) {
// date order: home, sect1, sect2, cat/hugo, cat/web, categories
assertFileContent(t, depsCfg.Fs, filepath.Join("public", "index.html"), false,
th.assertFileContent(t, fs, filepath.Join("public", "index.html"), false,
"Index Title: Home Sweet Home!",
"Home <strong>Content!</strong>",
"# Pages: 4",
@ -89,7 +90,7 @@ 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 <strong>Content!</strong>",
"Date: 2009-01-04",
"Lastmod: 2009-01-05",
)
assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect2"), false,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect2"), false,
"Section Title: Section", "Section2 <strong>Content!</strong>",
"Date: 2009-01-06",
"Lastmod: 2009-01-07",
)
// 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 <strong>Content!</strong>",
"Date: 2009-01-08",
"Lastmod: 2009-01-09",
)
assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "hugo-rocks"), false,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories", "hugo-rocks"), false,
"Taxonomy Title: Taxonomy Hugo Rocks",
)
@ -156,7 +157,7 @@ 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 <strong>Content!</strong>",
"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 <strong>Content!</strong>", "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", "<rss")
assertFileContent(t, depsCfg.Fs, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Section1 on Hugo Rocks", "<rss")
assertFileContent(t, depsCfg.Fs, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Section2 on Hugo Rocks", "<rss")
assertFileContent(t, depsCfg.Fs, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Taxonomy Hugo on Hugo Rocks", "<rss")
assertFileContent(t, depsCfg.Fs, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Taxonomy Web on Hugo Rocks", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "customrss.xml"), false, "Recent content in Home Sweet Home! on Hugo Rocks", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Section1 on Hugo Rocks", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Section2 on Hugo Rocks", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Taxonomy Hugo on Hugo Rocks", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Taxonomy Web on Hugo Rocks", "<rss")
}
func TestNodesWithNoContentFile(t *testing.T) {
t.Parallel()
for _, ugly := range []bool{false, true} {
doTestNodesWithNoContentFile(t, ugly)
}
}
func doTestNodesWithNoContentFile(t *testing.T, ugly bool) {
testCommonResetState()
viper.Set("uglyURLs", ugly)
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
viper.Set("rssURI", "customrss.xml")
var (
cfg, fs = newTestCfg()
th = testHelper{cfg}
)
fs := hugofs.NewMem()
cfg.Set("uglyURLs", ugly)
cfg.Set("paginate", 1)
cfg.Set("title", "Hugo Rocks!")
cfg.Set("rssURI", "customrss.xml")
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
@ -222,21 +226,21 @@ func doTestNodesWithNoContentFile(t *testing.T, ugly bool) {
require.Len(t, homePage.Pages, 4)
require.True(t, homePage.Path() == "")
assertFileContent(t, fs, filepath.Join("public", "index.html"), false,
th.assertFileContent(t, fs, filepath.Join("public", "index.html"), false,
"Index Title: Hugo Rocks!",
"Date: 2010-06-12",
"Lastmod: 2010-06-13",
)
// Taxonomy list
assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories", "hugo"), false,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories", "hugo"), false,
"Taxonomy Title: Hugo",
"Date: 2010-06-12",
"Lastmod: 2010-06-13",
)
// Taxonomy terms
assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories"), false,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories"), false,
"Taxonomy Terms Title: Categories",
)
@ -254,28 +258,29 @@ func doTestNodesWithNoContentFile(t *testing.T, ugly bool) {
}
// Sections
assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect1"), false,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect1"), false,
"Section Title: Sect1s",
"Date: 2010-06-12",
"Lastmod: 2010-06-13",
)
assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect2"), false,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect2"), false,
"Section Title: Sect2s",
"Date: 2008-07-06",
"Lastmod: 2008-07-09",
)
// RSS
assertFileContent(t, fs, filepath.Join("public", "customrss.xml"), false, "Hugo Rocks!", "<rss")
assertFileContent(t, fs, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Sect1s on Hugo Rocks!", "<rss")
assertFileContent(t, fs, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Sect2s on Hugo Rocks!", "<rss")
assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Hugo on Hugo Rocks!", "<rss")
assertFileContent(t, fs, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Web on Hugo Rocks!", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "customrss.xml"), false, "Hugo Rocks!", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Sect1s on Hugo Rocks!", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Sect2s on Hugo Rocks!", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Hugo on Hugo Rocks!", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Web on Hugo Rocks!", "<rss")
}
func TestNodesAsPageMultilingual(t *testing.T) {
t.Parallel()
for _, ugly := range []bool{false, true} {
doTestNodesAsPageMultilingual(t, ugly)
}
@ -283,15 +288,9 @@ func TestNodesAsPageMultilingual(t *testing.T) {
func doTestNodesAsPageMultilingual(t *testing.T, ugly bool) {
testCommonResetState()
mf := afero.NewMemMapFs()
fs := hugofs.NewMem()
viper.Set("uglyURLs", ugly)
viper.SetFs(fs.Source)
writeSource(t, fs, "config.toml",
writeToFs(t, mf, "config.toml",
`
paginage = 1
title = "Hugo Multilingual Rocks!"
@ -317,17 +316,22 @@ weight = 3
title = "Deutsche Hugo"
`)
cfg, err := LoadConfig(mf, "", "config.toml")
require.NoError(t, err)
cfg.Set("uglyURLs", ugly)
fs := hugofs.NewFrom(mf, cfg)
writeLayoutsForNodeAsPageTests(t, fs)
for _, lang := range []string{"nn", "en"} {
writeRegularPagesForNodeAsPageTestsWithLang(t, fs, lang)
}
if err := LoadGlobalConfig("", "config.toml"); err != nil {
t.Fatalf("Failed to load config: %s", err)
}
th := testHelper{cfg}
sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@ -372,63 +376,65 @@ title = "Deutsche Hugo"
require.Equal(t, expetedPermalink(ugly, "/en/sect1/"), enSect.Permalink())
assertFileContent(t, fs, filepath.Join("public", "nn", "index.html"), true,
th.assertFileContent(t, fs, filepath.Join("public", "nn", "index.html"), true,
"Index Title: Hugo på norsk")
assertFileContent(t, fs, filepath.Join("public", "en", "index.html"), true,
th.assertFileContent(t, fs, filepath.Join("public", "en", "index.html"), true,
"Index Title: Home Sweet Home!", "<strong>Content!</strong>")
assertFileContent(t, fs, filepath.Join("public", "de", "index.html"), true,
th.assertFileContent(t, fs, filepath.Join("public", "de", "index.html"), true,
"Index Title: Home Sweet Home!", "<strong>Content!</strong>")
// Taxonomy list
assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories", "hugo"), true,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories", "hugo"), true,
"Taxonomy Title: Hugo")
assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories", "hugo"), true,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories", "hugo"), true,
"Taxonomy Title: Taxonomy Hugo")
// Taxonomy terms
assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories"), true,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories"), true,
"Taxonomy Terms Title: Categories")
assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories"), true,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories"), true,
"Taxonomy Terms Title: Taxonomy Term Categories")
// Sections
assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1"), true,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1"), true,
"Section Title: Sect1s")
assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect2"), true,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect2"), true,
"Section Title: Sect2s")
assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1"), true,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1"), true,
"Section Title: Section1")
assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect2"), true,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect2"), true,
"Section Title: Section2")
// Regular pages
assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1", "regular1"), true,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1", "regular1"), true,
"Single Title: Page 01")
assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1", "regular2"), true,
th.assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1", "regular2"), true,
"Single Title: Page 02")
// RSS
assertFileContent(t, fs, filepath.Join("public", "nn", "customrss.xml"), true, "Hugo på norsk", "<rss")
assertFileContent(t, fs, filepath.Join("public", "nn", "sect1", "customrss.xml"), true, "Recent content in Sect1s on Hugo på norsk", "<rss")
assertFileContent(t, fs, filepath.Join("public", "nn", "sect2", "customrss.xml"), true, "Recent content in Sect2s on Hugo på norsk", "<rss")
assertFileContent(t, fs, filepath.Join("public", "nn", "categories", "hugo", "customrss.xml"), true, "Recent content in Hugo on Hugo på norsk", "<rss")
assertFileContent(t, fs, filepath.Join("public", "nn", "categories", "web", "customrss.xml"), true, "Recent content in Web on Hugo på norsk", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "nn", "customrss.xml"), true, "Hugo på norsk", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "nn", "sect1", "customrss.xml"), true, "Recent content in Sect1s on Hugo på norsk", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "nn", "sect2", "customrss.xml"), true, "Recent content in Sect2s on Hugo på norsk", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "nn", "categories", "hugo", "customrss.xml"), true, "Recent content in Hugo on Hugo på norsk", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "nn", "categories", "web", "customrss.xml"), true, "Recent content in Web on Hugo på norsk", "<rss")
assertFileContent(t, fs, filepath.Join("public", "en", "customrss.xml"), true, "Recent content in Home Sweet Home! on Hugo in English", "<rss")
assertFileContent(t, fs, filepath.Join("public", "en", "sect1", "customrss.xml"), true, "Recent content in Section1 on Hugo in English", "<rss")
assertFileContent(t, fs, filepath.Join("public", "en", "sect2", "customrss.xml"), true, "Recent content in Section2 on Hugo in English", "<rss")
assertFileContent(t, fs, filepath.Join("public", "en", "categories", "hugo", "customrss.xml"), true, "Recent content in Taxonomy Hugo on Hugo in English", "<rss")
assertFileContent(t, fs, filepath.Join("public", "en", "categories", "web", "customrss.xml"), true, "Recent content in Taxonomy Web on Hugo in English", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "en", "customrss.xml"), true, "Recent content in Home Sweet Home! on Hugo in English", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "en", "sect1", "customrss.xml"), true, "Recent content in Section1 on Hugo in English", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "en", "sect2", "customrss.xml"), true, "Recent content in Section2 on Hugo in English", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "en", "categories", "hugo", "customrss.xml"), true, "Recent content in Taxonomy Hugo on Hugo in English", "<rss")
th.assertFileContent(t, fs, filepath.Join("public", "en", "categories", "web", "customrss.xml"), true, "Recent content in Taxonomy Web on Hugo in English", "<rss")
}
func TestNodesWithTaxonomies(t *testing.T) {
testCommonResetState()
t.Parallel()
var (
cfg, fs = newTestCfg()
th = testHelper{cfg}
)
fs := hugofs.NewMem()
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
cfg.Set("paginate", 1)
cfg.Set("title", "Hugo Rocks!")
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
@ -442,24 +448,26 @@ categories: [
---
`)
h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
require.NoError(t, h.Build(BuildCfg{}))
assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy Title: Hugo", "# Pages: 5")
assertFileContent(t, fs, filepath.Join("public", "categories", "home", "index.html"), true, "Taxonomy Title: Home", "# Pages: 1")
th.assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy Title: Hugo", "# Pages: 5")
th.assertFileContent(t, fs, filepath.Join("public", "categories", "home", "index.html"), true, "Taxonomy Title: Home", "# Pages: 1")
}
func TestNodesWithMenu(t *testing.T) {
testCommonResetState()
t.Parallel()
var (
cfg, fs = newTestCfg()
th = testHelper{cfg}
)
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
fs := hugofs.NewMem()
cfg.Set("paginate", 1)
cfg.Set("title", "Hugo Rocks!")
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
@ -488,26 +496,28 @@ menu:
---
`)
h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
require.NoError(t, h.Build(BuildCfg{}))
assertFileContent(t, fs, filepath.Join("public", "index.html"), true, "Home With Menu", "Home Menu Item: Go Home!: /")
assertFileContent(t, fs, filepath.Join("public", "sect1", "index.html"), true, "Sect1 With Menu", "Section Menu Item: Go Sect1!: /sect1/")
assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy With Menu", "Taxonomy Menu Item: Go Tax Hugo!: /categories/hugo/")
th.assertFileContent(t, fs, filepath.Join("public", "index.html"), true, "Home With Menu", "Home Menu Item: Go Home!: /")
th.assertFileContent(t, fs, filepath.Join("public", "sect1", "index.html"), true, "Sect1 With Menu", "Section Menu Item: Go Sect1!: /sect1/")
th.assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy With Menu", "Taxonomy Menu Item: Go Tax Hugo!: /categories/hugo/")
}
func TestNodesWithAlias(t *testing.T) {
testCommonResetState()
t.Parallel()
var (
cfg, fs = newTestCfg()
th = testHelper{cfg}
)
fs := hugofs.NewMem()
viper.Set("paginate", 1)
viper.Set("baseURL", "http://base/")
viper.Set("title", "Hugo Rocks!")
cfg.Set("paginate", 1)
cfg.Set("baseURL", "http://base/")
cfg.Set("title", "Hugo Rocks!")
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
@ -519,24 +529,26 @@ aliases:
---
`)
h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
require.NoError(t, h.Build(BuildCfg{}))
assertFileContent(t, fs, filepath.Join("public", "index.html"), true, "Home With Alias")
assertFileContent(t, fs, filepath.Join("public", "my", "new", "home.html"), true, "content=\"0; url=http://base/")
th.assertFileContent(t, fs, filepath.Join("public", "index.html"), true, "Home With Alias")
th.assertFileContent(t, fs, filepath.Join("public", "my", "new", "home.html"), true, "content=\"0; url=http://base/")
}
func TestNodesWithSectionWithIndexPageOnly(t *testing.T) {
testCommonResetState()
t.Parallel()
var (
cfg, fs = newTestCfg()
th = testHelper{cfg}
)
fs := hugofs.NewMem()
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
cfg.Set("paginate", 1)
cfg.Set("title", "Hugo Rocks!")
writeLayoutsForNodeAsPageTests(t, fs)
@ -546,24 +558,26 @@ title: MySection
My Section Content
`)
h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
require.NoError(t, h.Build(BuildCfg{}))
assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
th.assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
}
func TestNodesWithURLs(t *testing.T) {
testCommonResetState()
t.Parallel()
var (
cfg, fs = newTestCfg()
th = testHelper{cfg}
)
fs := hugofs.NewMem()
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
viper.Set("baseURL", "http://bep.is/base/")
cfg.Set("paginate", 1)
cfg.Set("title", "Hugo Rocks!")
cfg.Set("baseURL", "http://bep.is/base/")
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
@ -575,13 +589,13 @@ url: foo.html
My Section Content
`)
h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
require.NoError(t, err)
require.NoError(t, h.Build(BuildCfg{}))
assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
th.assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
s := h.Sites[0]

View File

@ -39,7 +39,6 @@ import (
"github.com/spf13/cast"
bp "github.com/spf13/hugo/bufferpool"
"github.com/spf13/hugo/source"
"github.com/spf13/viper"
)
var (
@ -195,8 +194,11 @@ type Page struct {
scratch *Scratch
// It would be tempting to use the language set on the Site, but in they way we do
// multi-site processing, these values may differ during the initial page processing.
language *helpers.Language
lang string
lang string
}
// pageInit lazy initializes different parts of the page. It is extracted
@ -518,10 +520,11 @@ func (p *Page) renderContent(content []byte) []byte {
return p.Site.SourceRelativeLinkFile(ref, p)
}
}
return helpers.RenderBytes(&helpers.RenderingContext{
return p.s.ContentSpec.RenderBytes(&helpers.RenderingContext{
Content: content, RenderTOC: true, PageFmt: p.determineMarkupType(),
ConfigProvider: p.Language(),
DocumentID: p.UniqueID(), DocumentName: p.Path(),
Cfg: p.Language(),
DocumentID: p.UniqueID(), DocumentName: p.Path(),
Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn})
}
@ -532,7 +535,7 @@ func (p *Page) getRenderingConfig() *helpers.Blackfriday {
if p.Language() == nil {
panic(fmt.Sprintf("nil language for %s with source lang %s", p.BaseFileName(), p.lang))
}
p.renderingConfig = helpers.NewBlackfriday(p.Language())
p.renderingConfig = p.s.ContentSpec.NewBlackfriday()
if err := mapstructure.Decode(pageParam, p.renderingConfig); err != nil {
p.s.Log.FATAL.Printf("Failed to get rendering config for %s:\n%s", p.BaseFileName(), err.Error())
@ -544,15 +547,18 @@ func (p *Page) getRenderingConfig() *helpers.Blackfriday {
}
func (s *Site) newPage(filename string) *Page {
sp := source.NewSourceSpec(s.Cfg, s.Fs)
page := Page{
pageInit: &pageInit{},
Kind: kindFromFilename(filename),
contentType: "",
Source: Source{File: *source.NewFile(filename)},
Source: Source{File: *sp.NewFile(filename)},
Keywords: []string{}, Sitemap: Sitemap{Priority: -1},
Params: make(map[string]interface{}),
translations: make(Pages, 0),
sections: sectionsFromFilename(filename),
Site: &s.Info,
s: s,
}
s.Log.DEBUG.Println("Reading from", page.File.Path())
@ -799,7 +805,7 @@ func (p *Page) Extension() string {
if p.extension != "" {
return p.extension
}
return viper.GetString("defaultExtension")
return p.s.Cfg.GetString("defaultExtension")
}
// AllTranslations returns all translations, including the current Page.
@ -832,8 +838,8 @@ func (p *Page) LinkTitle() string {
}
func (p *Page) shouldBuild() bool {
return shouldBuild(viper.GetBool("buildFuture"), viper.GetBool("buildExpired"),
viper.GetBool("buildDrafts"), p.Draft, p.PublishDate, p.ExpiryDate)
return shouldBuild(p.s.Cfg.GetBool("buildFuture"), p.s.Cfg.GetBool("buildExpired"),
p.s.Cfg.GetBool("buildDrafts"), p.Draft, p.PublishDate, p.ExpiryDate)
}
func shouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool,
@ -886,7 +892,7 @@ func (p *Page) URL() string {
func (p *Page) RelPermalink() string {
link := p.getPermalink()
if viper.GetBool("canonifyURLs") {
if p.s.Cfg.GetBool("canonifyURLs") {
// replacements for relpermalink with baseURL on the form http://myhost.com/sub/ will fail later on
// have to return the URL relative from baseURL
relpath, err := helpers.GetRelativePath(link.String(), string(p.Site.BaseURL))
@ -1047,8 +1053,8 @@ func (p *Page) update(f interface{}) error {
p.Draft = !*published
}
if p.Date.IsZero() && viper.GetBool("useModTimeAsFallback") {
fi, err := p.s.Fs.Source.Stat(filepath.Join(helpers.AbsPathify(viper.GetString("contentDir")), p.File.Path()))
if p.Date.IsZero() && p.s.Cfg.GetBool("useModTimeAsFallback") {
fi, err := p.s.Fs.Source.Stat(filepath.Join(p.s.PathSpec.AbsPathify(p.s.Cfg.GetString("contentDir")), p.File.Path()))
if err == nil {
p.Date = fi.ModTime()
}
@ -1060,7 +1066,7 @@ func (p *Page) update(f interface{}) error {
if isCJKLanguage != nil {
p.isCJKLanguage = *isCJKLanguage
} else if viper.GetBool("hasCJKLanguage") {
} else if p.s.Cfg.GetBool("hasCJKLanguage") {
if cjk.Match(p.rawContent) {
p.isCJKLanguage = true
} else {
@ -1378,10 +1384,9 @@ func (p *Page) saveSourceAs(path string, safe bool) error {
func (p *Page) saveSource(by []byte, inpath string, safe bool) (err error) {
if !filepath.IsAbs(inpath) {
inpath = helpers.AbsPathify(inpath)
inpath = p.s.PathSpec.AbsPathify(inpath)
}
p.s.Log.INFO.Println("creating", inpath)
if safe {
err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source)
} else {
@ -1691,25 +1696,27 @@ func (p *Page) initLanguage() {
if p.language != nil {
return
}
pageLang := p.lang
ml := p.Site.multilingual
if ml == nil {
panic("Multilanguage not set")
}
if pageLang == "" {
if p.lang == "" {
p.lang = ml.DefaultLang.Lang
p.language = ml.DefaultLang
return
}
language := ml.Language(pageLang)
language := ml.Language(p.lang)
if language == nil {
// It can be a file named stefano.chiodino.md.
p.s.Log.WARN.Printf("Page language (if it is that) not found in multilang setup: %s.", pageLang)
p.s.Log.WARN.Printf("Page language (if it is that) not found in multilang setup: %s.", p.lang)
language = ml.DefaultLang
}
p.language = language
})
}
@ -1743,6 +1750,7 @@ func (p *Page) addLangFilepathPrefix(outfile string) string {
if outfile == "" {
outfile = helpers.FilePathSeparator
}
if !p.shouldAddLanguagePrefix() {
return outfile
}
@ -1795,7 +1803,4 @@ func (p *Page) setValuesForKind(s *Site) {
case KindTaxonomyTerm:
p.URLPath.URL = "/" + path.Join(p.sections...) + "/"
}
p.s = s
}

View File

@ -14,13 +14,15 @@
package hugolib
import (
"github.com/stretchr/testify/assert"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPageCache(t *testing.T) {
t.Parallel()
c1 := newPageCache()
changeFirst := func(p Pages) {
@ -37,8 +39,10 @@ func TestPageCache(t *testing.T) {
var testPageSets []Pages
s := newTestSite(t)
for i := 0; i < 50; i++ {
testPageSets = append(testPageSets, createSortTestPages(i+1))
testPageSets = append(testPageSets, createSortTestPages(s, i+1))
}
for j := 0; j < 100; j++ {

View File

@ -38,24 +38,26 @@ var pageGroupTestSources = []pageGroupTestObject{
}
func preparePageGroupTestPages(t *testing.T) Pages {
s := newTestSite(t)
var pages Pages
for _, s := range pageGroupTestSources {
p, err := pageTestSite.NewPage(filepath.FromSlash(s.path))
for _, src := range pageGroupTestSources {
p, err := s.NewPage(filepath.FromSlash(src.path))
if err != nil {
t.Fatalf("failed to prepare test page %s", s.path)
t.Fatalf("failed to prepare test page %s", src.path)
}
p.Weight = s.weight
p.Date = cast.ToTime(s.date)
p.PublishDate = cast.ToTime(s.date)
p.ExpiryDate = cast.ToTime(s.date)
p.Params["custom_param"] = s.param
p.Params["custom_date"] = cast.ToTime(s.date)
p.Weight = src.weight
p.Date = cast.ToTime(src.date)
p.PublishDate = cast.ToTime(src.date)
p.ExpiryDate = cast.ToTime(src.date)
p.Params["custom_param"] = src.param
p.Params["custom_date"] = cast.ToTime(src.date)
pages = append(pages, p)
}
return pages
}
func TestGroupByWithFieldNameArg(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: 1, Pages: Pages{pages[3], pages[4]}},
@ -73,6 +75,7 @@ func TestGroupByWithFieldNameArg(t *testing.T) {
}
func TestGroupByWithMethodNameArg(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "section1", Pages: Pages{pages[0], pages[1], pages[2]}},
@ -89,6 +92,7 @@ func TestGroupByWithMethodNameArg(t *testing.T) {
}
func TestGroupByWithSectionArg(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "section1", Pages: Pages{pages[0], pages[1], pages[2]}},
@ -105,6 +109,7 @@ func TestGroupByWithSectionArg(t *testing.T) {
}
func TestGroupByInReverseOrder(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: 3, Pages: Pages{pages[0], pages[1]}},
@ -122,6 +127,7 @@ func TestGroupByInReverseOrder(t *testing.T) {
}
func TestGroupByCalledWithEmptyPages(t *testing.T) {
t.Parallel()
var pages Pages
groups, err := pages.GroupBy("Weight")
if err != nil {
@ -133,6 +139,7 @@ func TestGroupByCalledWithEmptyPages(t *testing.T) {
}
func TestGroupByCalledWithUnavailableKey(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
_, err := pages.GroupBy("UnavailableKey")
if err == nil {
@ -157,6 +164,7 @@ func (page *Page) dummyPageMethodReturnTwoValueForTest() (string, string) {
}
func TestGroupByCalledWithInvalidMethod(t *testing.T) {
t.Parallel()
var err error
pages := preparePageGroupTestPages(t)
@ -182,6 +190,7 @@ func TestGroupByCalledWithInvalidMethod(t *testing.T) {
}
func TestReverse(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
groups1, err := pages.GroupBy("Weight", "desc")
@ -201,6 +210,7 @@ func TestReverse(t *testing.T) {
}
func TestGroupByParam(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "bar", Pages: Pages{pages[1], pages[3]}},
@ -218,6 +228,7 @@ func TestGroupByParam(t *testing.T) {
}
func TestGroupByParamInReverseOrder(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "foo", Pages: Pages{pages[0], pages[2]}},
@ -237,7 +248,8 @@ func TestGroupByParamInReverseOrder(t *testing.T) {
func TestGroupByParamCalledWithCapitalLetterString(t *testing.T) {
testStr := "TestString"
f := "/section1/test_capital.md"
p, err := pageTestSite.NewPage(filepath.FromSlash(f))
s := newTestSite(t)
p, err := s.NewPage(filepath.FromSlash(f))
if err != nil {
t.Fatalf("failed to prepare test page %s", f)
}
@ -254,6 +266,7 @@ func TestGroupByParamCalledWithCapitalLetterString(t *testing.T) {
}
func TestGroupByParamCalledWithSomeUnavailableParams(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
delete(pages[1].Params, "custom_param")
delete(pages[3].Params, "custom_param")
@ -273,6 +286,7 @@ func TestGroupByParamCalledWithSomeUnavailableParams(t *testing.T) {
}
func TestGroupByParamCalledWithEmptyPages(t *testing.T) {
t.Parallel()
var pages Pages
groups, err := pages.GroupByParam("custom_param")
if err != nil {
@ -284,6 +298,7 @@ func TestGroupByParamCalledWithEmptyPages(t *testing.T) {
}
func TestGroupByParamCalledWithUnavailableParam(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
_, err := pages.GroupByParam("unavailable_param")
if err == nil {
@ -292,6 +307,7 @@ func TestGroupByParamCalledWithUnavailableParam(t *testing.T) {
}
func TestGroupByDate(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}},
@ -309,6 +325,7 @@ func TestGroupByDate(t *testing.T) {
}
func TestGroupByDateInReverseOrder(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-01", Pages: Pages{pages[1]}},
@ -326,6 +343,7 @@ func TestGroupByDateInReverseOrder(t *testing.T) {
}
func TestGroupByPublishDate(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}},
@ -343,6 +361,7 @@ func TestGroupByPublishDate(t *testing.T) {
}
func TestGroupByPublishDateInReverseOrder(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-01", Pages: Pages{pages[1]}},
@ -360,6 +379,7 @@ func TestGroupByPublishDateInReverseOrder(t *testing.T) {
}
func TestGroupByPublishDateWithEmptyPages(t *testing.T) {
t.Parallel()
var pages Pages
groups, err := pages.GroupByPublishDate("2006-01")
if err != nil {
@ -371,6 +391,7 @@ func TestGroupByPublishDateWithEmptyPages(t *testing.T) {
}
func TestGroupByExpiryDate(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}},
@ -388,6 +409,7 @@ func TestGroupByExpiryDate(t *testing.T) {
}
func TestGroupByParamDate(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}},
@ -405,6 +427,7 @@ func TestGroupByParamDate(t *testing.T) {
}
func TestGroupByParamDateInReverseOrder(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-01", Pages: Pages{pages[1]}},
@ -422,6 +445,7 @@ func TestGroupByParamDateInReverseOrder(t *testing.T) {
}
func TestGroupByParamDateWithEmptyPages(t *testing.T) {
t.Parallel()
var pages Pages
groups, err := pages.GroupByParamDate("custom_date", "2006-01")
if err != nil {

View File

@ -68,7 +68,7 @@ var defaultPageSort = func(p1, p2 *Page) bool {
}
var languagePageSort = func(p1, p2 *Page) bool {
if p1.language.Weight == p2.language.Weight {
if p1.Language().Weight == p2.Language().Weight {
if p1.Date.Unix() == p2.Date.Unix() {
if p1.LinkTitle() == p2.LinkTitle() {
return (p1.FullFilePath() < p2.FullFilePath())
@ -78,15 +78,15 @@ var languagePageSort = func(p1, p2 *Page) bool {
return p1.Date.Unix() > p2.Date.Unix()
}
if p2.language.Weight == 0 {
if p2.Language().Weight == 0 {
return true
}
if p1.language.Weight == 0 {
if p1.Language().Weight == 0 {
return false
}
return p1.language.Weight < p2.language.Weight
return p1.Language().Weight < p2.Language().Weight
}
func (ps *pageSorter) Len() int { return len(ps.pages) }

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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)

View File

@ -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 = "<h1>Do Be Do Be Do</h1>"
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()))
}
}

View File

@ -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.")
}

View File

@ -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)

View File

@ -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 {

View File

@ -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 }}
</body></html>`)
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
assertFileContent(t, fs, filepath.Join("public", "阅读", "testing", "2", "index.html"), false, "2: /%E9%98%85%E8%AF%BB/testing/2/")
th := testHelper{s.Cfg}
th.assertFileContent(t, fs, filepath.Join("public", "阅读", "testing", "2", "index.html"), false, "2: /%E9%98%85%E8%AF%BB/testing/2/")
}
func doTestPaginate(t *testing.T, useViper bool) {
pagerSize := 5
var (
s *Site
err error
)
if useViper {
viper.Set("paginate", pagerSize)
s = newTestSite(t, "paginate", pagerSize)
} else {
viper.Set("paginate", -1)
s = newTestSite(t, "paginate", -1)
}
pages := createTestPages(6)
s, err := NewSiteDefaultLang()
require.NoError(t, err)
pages := createTestPages(s, 6)
n1 := s.newHomePage()
n2 := s.newHomePage()
@ -346,10 +352,10 @@ 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

View File

@ -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)
}

View File

@ -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")

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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, "<?xml", "rss version", "RSSTest")
th.assertFileContent(t, fs, filepath.Join("public", rssURI), true, "<?xml", "rss version", "RSSTest")
// Section RSS
assertFileContent(t, fs, filepath.Join("public", "sect", rssURI), true, "<?xml", "rss version", "Sects on RSSTest")
th.assertFileContent(t, fs, filepath.Join("public", "sect", rssURI), true, "<?xml", "rss version", "Sects on RSSTest")
// Taxonomy RSS
assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", rssURI), true, "<?xml", "rss version", "Hugo on RSSTest")
th.assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", rssURI), true, "<?xml", "rss version", "Hugo on RSSTest")
}

View File

@ -14,13 +14,15 @@
package hugolib
import (
"github.com/stretchr/testify/assert"
"reflect"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestScratchAdd(t *testing.T) {
t.Parallel()
scratch := newScratch()
scratch.Add("int1", 10)
scratch.Add("int1", 20)
@ -50,6 +52,7 @@ func TestScratchAdd(t *testing.T) {
}
func TestScratchAddSlice(t *testing.T) {
t.Parallel()
scratch := newScratch()
_, err := scratch.Add("intSlice", []int{1, 2})
@ -78,6 +81,7 @@ func TestScratchAddSlice(t *testing.T) {
}
func TestScratchSet(t *testing.T) {
t.Parallel()
scratch := newScratch()
scratch.Set("key", "val")
assert.Equal(t, "val", scratch.Get("key"))
@ -119,6 +123,7 @@ func TestScratchInParallel(t *testing.T) {
}
func TestScratchGet(t *testing.T) {
t.Parallel()
scratch := newScratch()
nothing := scratch.Get("nothing")
if nothing != nil {
@ -127,6 +132,7 @@ func TestScratchGet(t *testing.T) {
}
func TestScratchSetInMap(t *testing.T) {
t.Parallel()
scratch := newScratch()
scratch.SetInMap("key", "lux", "Lux")
scratch.SetInMap("key", "abc", "Abc")
@ -137,6 +143,7 @@ func TestScratchSetInMap(t *testing.T) {
}
func TestScratchGetSortedMapValues(t *testing.T) {
t.Parallel()
scratch := newScratch()
nothing := scratch.GetSortedMapValues("nothing")
if nothing != nil {

View File

@ -149,31 +149,6 @@ func (sc shortcode) String() string {
return fmt.Sprintf("%s(%q, %t){%s}", sc.name, params, sc.doMarkup, sc.inner)
}
// HandleShortcodes does all in one go: extract, render and replace
// only used for testing
func HandleShortcodes(stringToParse string, page *Page) (string, error) {
tmpContent, tmpShortcodes, err := extractAndRenderShortcodes(stringToParse, page)
if err != nil {
return "", err
}
if len(tmpShortcodes) > 0 {
shortcodes, err := executeShortcodeFuncMap(tmpShortcodes)
if err != nil {
return "", err
}
tmpContentWithTokensReplaced, err := replaceShortcodeTokens([]byte(tmpContent), shortcodePlaceholderPrefix, shortcodes)
if err != nil {
return "", fmt.Errorf("Failed to replace shortcode tokens in %s:\n%s", page.BaseFileName(), err.Error())
}
return string(tmpContentWithTokensReplaced), nil
}
return tmpContent, nil
}
var isInnerShortcodeCache = struct {
sync.RWMutex
m map[string]bool
@ -239,12 +214,12 @@ 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

View File

@ -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
}{
{"{{</*/", true},
} {
output, err := HandleShortcodes(this.data, p)
if this.expectErr && err == nil {
t.Errorf("[%d] should have errored", i)
}
if !this.expectErr && err != nil {
t.Errorf("[%d] should not have errored: %s", i, err)
}
if !this.expectErr && err == nil && len(output) == 0 {
t.Errorf("[%d] empty result", i)
}
}
}
func TestNonSC(t *testing.T) {
t.Parallel()
// notice the syntax diff from 0.12, now comment delims must be added
CheckShortCodeMatch(t, "{{%/* movie 47238zzb */%}}", "{{% movie 47238zzb %}}", nil)
}
// Issue #929
func TestHyphenatedSC(t *testing.T) {
t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
return nil
@ -137,6 +110,7 @@ func TestHyphenatedSC(t *testing.T) {
// Issue #1753
func TestNoTrailingNewline(t *testing.T) {
t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("a.html", `{{ .Get 0 }}`)
return nil
@ -146,6 +120,7 @@ func TestNoTrailingNewline(t *testing.T) {
}
func TestPositionalParamSC(t *testing.T) {
t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
return nil
@ -159,6 +134,7 @@ func TestPositionalParamSC(t *testing.T) {
}
func TestPositionalParamIndexOutOfBounds(t *testing.T) {
t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 1 }}`)
return nil
@ -169,6 +145,7 @@ func TestPositionalParamIndexOutOfBounds(t *testing.T) {
// some repro issues for panics in Go Fuzz testing
func TestNamedParamSC(t *testing.T) {
t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
return nil
@ -183,6 +160,7 @@ func TestNamedParamSC(t *testing.T) {
// Issue #2294
func TestNestedNamedMissingParam(t *testing.T) {
t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("acc.html", `<div class="acc">{{ .Inner }}</div>`)
tem.AddInternalShortcode("div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
@ -195,6 +173,7 @@ func TestNestedNamedMissingParam(t *testing.T) {
}
func TestIsNamedParamsSC(t *testing.T) {
t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("byposition.html", `<div id="{{ .Get 0 }}">`)
tem.AddInternalShortcode("byname.html", `<div id="{{ .Get "id" }}">`)
@ -210,6 +189,7 @@ func TestIsNamedParamsSC(t *testing.T) {
}
func TestInnerSC(t *testing.T) {
t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
@ -220,6 +200,7 @@ func TestInnerSC(t *testing.T) {
}
func TestInnerSCWithMarkdown(t *testing.T) {
t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
@ -233,6 +214,7 @@ func TestInnerSCWithMarkdown(t *testing.T) {
}
func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
@ -256,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<figure class=\"bananas orange\">\n \n <img src=\"/found/here\" />\n \n \n</figure>\n", nil)
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" caption="This is a caption" %}}`, "\n<figure class=\"bananas orange\">\n \n <img src=\"/found/here\" alt=\"This is a caption\" />\n \n \n <figcaption>\n <p>\n This is a caption\n \n \n \n </p> \n </figcaption>\n \n</figure>\n", nil)
}
func TestNestedSC(t *testing.T) {
t.Parallel()
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
tem.AddInternalShortcode("scn2.html", `<div>SC2</div>`)
@ -273,6 +257,7 @@ func 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<figure class=\"bananas orange\">\n \n <img src=\"/found/here\" alt=\"apple\" width=\"100px\" />\n \n \n</figure>\n", nil)
}
func TestHighlight(t *testing.T) {
testCommonResetState()
if !helpers.HasPygments() {
t.Skip("Skip test as Pygments is not installed")
}
viper.Set("pygmentsStyle", "bw")
viper.Set("pygmentsUseClasses", false)
code := `
{{< highlight java >}}
void do();
{{< /highlight >}}`
p, _ := pageFromString(simplePage, "simple.md")
output, err := HandleShortcodes(code, p)
if err != nil {
t.Fatal("Handle shortcode error", err)
}
matched, err := regexp.MatchString("(?s)^\n<div class=\"highlight\" style=\"background: #ffffff\"><pre style=\"line-height: 125%\">.*?void</span> do().*?</pre></div>\n$", output)
if err != nil {
t.Fatal("Regexp error", err)
}
if !matched {
t.Errorf("Hightlight mismatch, got (escaped to see invisible chars)\n%+q", output)
}
}
const testScPlaceholderRegexp = "HAHAHUGOSHORTCODE-\\d+HBHB"
func TestExtractShortcodes(t *testing.T) {
t.Parallel()
for i, this := range []struct {
name string
input string
@ -455,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

View File

@ -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) {

View File

@ -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

View File

@ -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)

View File

@ -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
}
}

View File

@ -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"), "<root>RSS</root>")
writeSource(t, fs, filepath.Join("layouts", "sitemap.xml"), "<root>SITEMAP</root>")
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
var expectedPagePath string
if uglyURLs {
@ -356,6 +358,7 @@ func 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("<!doctype html><html><body>more content</body></html>")},
@ -447,13 +448,13 @@ func TestSkipRender(t *testing.T) {
{Name: filepath.FromSlash("doc9.html"), Content: []byte("<html><body>doc9: {{< myshortcode >}}</body></html>")},
}
viper.Set("defaultExtension", "html")
viper.Set("verbose", true)
viper.Set("canonifyURLs", true)
viper.Set("uglyURLs", true)
viper.Set("baseURL", "http://auth/bub")
cfg, fs := newTestCfg()
fs := hugofs.NewMem()
cfg.Set("defaultExtension", "html")
cfg.Set("verbose", true)
cfg.Set("canonifyURLs", true)
cfg.Set("uglyURLs", true)
cfg.Set("baseURL", "http://auth/bub")
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
@ -465,7 +466,7 @@ func TestSkipRender(t *testing.T) {
writeSource(t, fs, filepath.Join("layouts", "head_abs"), "<head><script src=\"/script.js\"></script></head>")
writeSource(t, fs, filepath.Join("layouts", "shortcodes", "myshortcode.html"), "SHORT")
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
tests := []struct {
doc string
@ -497,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("<!doctype html><html><head></head><body><a href=\"#frag1\">link</a></body></html>")},
{Name: filepath.FromSlash("blue/doc2.html"), Content: []byte("---\nf: t\n---\n<!doctype html><html><body>more content</body></html>")},
}
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

View File

@ -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 {

View File

@ -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 = `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@ -36,19 +34,18 @@ const sitemapTemplate = `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/
</urlset>`
func TestSitemapOutput(t *testing.T) {
t.Parallel()
for _, internal := range []bool{false, true} {
doTestSitemapOutput(t, internal)
}
}
func doTestSitemapOutput(t *testing.T, internal bool) {
testCommonResetState()
viper.Set("baseURL", "http://auth/bub/")
cfg, fs := newTestCfg()
cfg.Set("baseURL", "http://auth/bub/")
fs := hugofs.NewMem()
depsCfg := deps.DepsCfg{Fs: fs}
depsCfg := deps.DepsCfg{Fs: fs, Cfg: cfg}
if !internal {
depsCfg.WithTemplate = func(templ tplapi.Template) error {
@ -59,8 +56,9 @@ func doTestSitemapOutput(t *testing.T, internal bool) {
writeSourcesToSource(t, "content", fs, weightedSources...)
s := buildSingleSite(t, depsCfg, BuildCfg{})
th := testHelper{s.Cfg}
assertFileContent(t, s.Fs, "public/sitemap.xml", true,
th.assertFileContent(t, s.Fs, "public/sitemap.xml", true,
// Regular page
" <loc>http://auth/bub/sect/doc1/</loc>",
// Home page
@ -76,6 +74,7 @@ func 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",

View File

@ -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() {

View File

@ -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",

View File

@ -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. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
@ -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)

View File

@ -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]

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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}
}

View File

@ -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)

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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{}{

View File

@ -21,6 +21,7 @@ import (
)
func TestTruncate(t *testing.T) {
t.Parallel()
var err error
cases := []struct {
v1 interface{}

View File

@ -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("<p>")
var markdownTrimSuffix = []byte("</p>\n")
// markdownify renders a given string from Markdown to HTML.
func markdownify(in interface{}) (template.HTML, error) {
func (t *templateFuncster) markdownify(in interface{}) (template.HTML, error) {
text, err := cast.ToStringE(in)
if err != nil {
return "", err
}
language := viper.Get("currentContentLanguage").(*helpers.Language)
m := helpers.RenderBytes(&helpers.RenderingContext{
ConfigProvider: language,
Content: []byte(text), PageFmt: "markdown"})
m := t.ContentSpec.RenderBytes(&helpers.RenderingContext{
Cfg: t.Cfg,
Content: []byte(text), PageFmt: "markdown"})
m = bytes.TrimPrefix(m, markdownTrimPrefix)
m = bytes.TrimSuffix(m, markdownTrimSuffix)
return template.HTML(m), nil
@ -2143,7 +2140,7 @@ 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

View File

@ -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 <strong>World!</strong>")},
{[]byte("Hello Bytes **World!**"), template.HTML("Hello Bytes <strong>World!</strong>")},
} {
result, err := markdownify(this.in)
result, err := f.markdownify(this.in)
if err != nil {
t.Fatalf("[%d] unexpected error in markdownify: %s", i, err)
}
@ -1975,12 +2003,13 @@ 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)
}

View File

@ -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++ {

View File

@ -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)

View File

@ -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)

View File

@ -16,24 +16,23 @@ package transform
import (
"bytes"
"fmt"
"github.com/spf13/viper"
)
func LiveReloadInject(ct contentTransformer) {
endBodyTag := "</body>"
match := []byte(endBodyTag)
port := viper.Get("port")
replaceTemplate := `<script data-no-instant>document.write('<script src="/livereload.js?port=%d&mindelay=10"></' + 'script>')</script>%s`
replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag))
newcontent := bytes.Replace(ct.Content(), match, replace, 1)
if len(newcontent) == len(ct.Content()) {
endBodyTag = "</BODY>"
replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag))
func LiveReloadInject(port int) func(ct contentTransformer) {
return func(ct contentTransformer) {
endBodyTag := "</body>"
match := []byte(endBodyTag)
newcontent = bytes.Replace(ct.Content(), match, replace, 1)
}
replaceTemplate := `<script data-no-instant>document.write('<script src="/livereload.js?port=%d&mindelay=10"></' + 'script>')</script>%s`
replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag))
ct.Write(newcontent)
newcontent := bytes.Replace(ct.Content(), match, replace, 1)
if len(newcontent) == len(ct.Content()) {
endBodyTag = "</BODY>"
replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag))
match := []byte(endBodyTag)
newcontent = bytes.Replace(ct.Content(), match, replace, 1)
}
ct.Write(newcontent)
}
}

View File

@ -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(`<script data-no-instant>document.write('<script src="/livereload.js?port=1313&mindelay=10"></' + 'script>')</script>%s`, bodyEndTag)