all: Refactor to nonglobal file systems

Updates #2701
Fixes #2951
This commit is contained in:
Bjørn Erik Pedersen 2017-01-10 10:55:03 +01:00
parent 0ada405912
commit c71e1b106e
71 changed files with 2219 additions and 1731 deletions

View File

@ -49,10 +49,13 @@ func init() {
func benchmark(cmd *cobra.Command, args []string) error {
cfg, err := InitializeConfig(benchmarkCmd)
if err != nil {
return err
}
c := commandeer{cfg}
var memProf *os.File
if memProfileFile != "" {
memProf, err = os.Create(memProfileFile)
@ -79,7 +82,7 @@ func benchmark(cmd *cobra.Command, args []string) error {
t := time.Now()
for i := 0; i < benchmarkTimes; i++ {
if err = resetAndBuildSites(cfg, false); err != nil {
if err = c.resetAndBuildSites(false); err != nil {
return err
}
}

View File

@ -51,9 +51,9 @@ for rendering in Hugo.`,
if !strings.HasSuffix(gendocdir, helpers.FilePathSeparator) {
gendocdir += helpers.FilePathSeparator
}
if found, _ := helpers.Exists(gendocdir, hugofs.Os()); !found {
if found, _ := helpers.Exists(gendocdir, hugofs.Os); !found {
jww.FEEDBACK.Println("Directory", gendocdir, "does not exist, creating...")
hugofs.Os().MkdirAll(gendocdir, 0777)
hugofs.Os.MkdirAll(gendocdir, 0777)
}
now := time.Now().Format(time.RFC3339)
prepender := func(filename string) string {

View File

@ -41,9 +41,9 @@ in the "man" directory under the current directory.`,
if !strings.HasSuffix(genmandir, helpers.FilePathSeparator) {
genmandir += helpers.FilePathSeparator
}
if found, _ := helpers.Exists(genmandir, hugofs.Os()); !found {
if found, _ := helpers.Exists(genmandir, hugofs.Os); !found {
jww.FEEDBACK.Println("Directory", genmandir, "does not exist, creating...")
hugofs.Os().MkdirAll(genmandir, 0777)
hugofs.Os.MkdirAll(genmandir, 0777)
}
cmd.Root().DisableAutoGenTag = true

View File

@ -40,6 +40,7 @@ import (
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/fsync"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugolib"
"github.com/spf13/hugo/livereload"
@ -50,6 +51,10 @@ 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.
@ -119,12 +124,14 @@ Complete documentation is available at http://gohugo.io/.`,
return err
}
c := commandeer{cfg}
if buildWatch {
viper.Set("disableLiveReload", true)
watchConfig(cfg)
c.watchConfig()
}
return build(cfg)
return c.build()
},
}
@ -268,9 +275,9 @@ func init() {
}
// InitializeConfig initializes a config file with sensible default configuration flags.
func InitializeConfig(subCmdVs ...*cobra.Command) (hugolib.DepsCfg, error) {
func InitializeConfig(subCmdVs ...*cobra.Command) (deps.DepsCfg, error) {
var cfg hugolib.DepsCfg
var cfg deps.DepsCfg
if err := hugolib.LoadGlobalConfig(source, cfgFile); err != nil {
return cfg, err
@ -323,34 +330,34 @@ func InitializeConfig(subCmdVs ...*cobra.Command) (hugolib.DepsCfg, error) {
viper.Set("cacheDir", cacheDir)
}
// Init file systems. This may be changed at a later point.
cfg.Fs = hugofs.NewDefault()
cacheDir = viper.GetString("cacheDir")
if cacheDir != "" {
if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] {
cacheDir = cacheDir + helpers.FilePathSeparator
}
isDir, err := helpers.DirExists(cacheDir, hugofs.Source())
isDir, err := helpers.DirExists(cacheDir, cfg.Fs.Source)
utils.CheckErr(err)
if !isDir {
mkdir(cacheDir)
}
viper.Set("cacheDir", cacheDir)
} else {
viper.Set("cacheDir", helpers.GetTempDir("hugo_cache", hugofs.Source()))
viper.Set("cacheDir", helpers.GetTempDir("hugo_cache", cfg.Fs.Source))
}
jww.INFO.Println("Using config file:", viper.ConfigFileUsed())
// Init file systems. This may be changed at a later point.
hugofs.InitDefaultFs()
themeDir := helpers.GetThemeDir()
if themeDir != "" {
if _, err := hugofs.Source().Stat(themeDir); os.IsNotExist(err) {
if _, err := cfg.Fs.Source.Stat(themeDir); os.IsNotExist(err) {
return cfg, newSystemError("Unable to find theme Directory:", themeDir)
}
}
themeVersionMismatch, minVersion := isThemeVsHugoVersionMismatch()
themeVersionMismatch, minVersion := isThemeVsHugoVersionMismatch(cfg.Fs.Source)
if themeVersionMismatch {
jww.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
@ -447,12 +454,12 @@ func flagChanged(flags *flag.FlagSet, key string) bool {
return flag.Changed
}
func watchConfig(cfg hugolib.DepsCfg) {
func (c commandeer) watchConfig() {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
jww.FEEDBACK.Println("Config file changed:", e.Name)
// Force a full rebuild
utils.CheckErr(recreateAndBuildSites(cfg, true))
utils.CheckErr(c.recreateAndBuildSites(true))
if !viper.GetBool("disableLiveReload") {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
livereload.ForceRefresh()
@ -460,39 +467,40 @@ func watchConfig(cfg hugolib.DepsCfg) {
})
}
func build(cfg hugolib.DepsCfg, watches ...bool) error {
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 {
hugofs.SetDestination(new(afero.MemMapFs))
c.Fs.Destination = new(afero.MemMapFs)
// Rendering to memoryFS, publish to Root regardless of publishDir.
viper.Set("publishDir", "/")
}
if err := copyStatic(); err != nil {
if err := c.copyStatic(); err != nil {
return fmt.Errorf("Error copying static files to %s: %s", helpers.AbsPathify(viper.GetString("publishDir")), err)
}
watch := false
if len(watches) > 0 && watches[0] {
watch = true
}
if err := buildSites(cfg, buildWatch || watch); err != nil {
if err := c.buildSites(buildWatch || watch); err != nil {
return fmt.Errorf("Error building site: %s", err)
}
if buildWatch {
jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("contentDir")))
jww.FEEDBACK.Println("Press Ctrl+C to stop")
utils.CheckErr(newWatcher(cfg, 0))
utils.CheckErr(c.newWatcher(0))
}
return nil
}
func getStaticSourceFs() afero.Fs {
source := hugofs.Source()
themeDir, err := helpers.GetThemeStaticDirPath()
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
useTheme := true
@ -532,12 +540,12 @@ func getStaticSourceFs() afero.Fs {
jww.INFO.Println("using a UnionFS for static directory comprised of:")
jww.INFO.Println("Base:", themeDir)
jww.INFO.Println("Overlay:", staticDir)
base := afero.NewReadOnlyFs(afero.NewBasePathFs(hugofs.Source(), themeDir))
overlay := afero.NewReadOnlyFs(afero.NewBasePathFs(hugofs.Source(), staticDir))
base := afero.NewReadOnlyFs(afero.NewBasePathFs(source, themeDir))
overlay := afero.NewReadOnlyFs(afero.NewBasePathFs(source, staticDir))
return afero.NewCopyOnWriteFs(base, overlay)
}
func copyStatic() error {
func (c commandeer) copyStatic() error {
publishDir := helpers.AbsPathify(viper.GetString("publishDir")) + helpers.FilePathSeparator
// If root, remove the second '/'
@ -546,7 +554,7 @@ func copyStatic() error {
}
// Includes both theme/static & /static
staticSourceFs := getStaticSourceFs()
staticSourceFs := c.getStaticSourceFs()
if staticSourceFs == nil {
jww.WARN.Println("No static directories found to sync")
@ -557,7 +565,7 @@ func copyStatic() error {
syncer.NoTimes = viper.GetBool("noTimes")
syncer.NoChmod = viper.GetBool("noChmod")
syncer.SrcFs = staticSourceFs
syncer.DestFs = hugofs.Destination()
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")
@ -572,7 +580,7 @@ func copyStatic() error {
}
// getDirList provides NewWatcher() with a list of directories to watch for changes.
func getDirList() []string {
func (c commandeer) getDirList() []string {
var a []string
dataDir := helpers.AbsPathify(viper.GetString("dataDir"))
i18nDir := helpers.AbsPathify(viper.GetString("i18nDir"))
@ -621,7 +629,7 @@ func getDirList() []string {
jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err)
return nil
}
linkfi, err := hugofs.Source().Stat(link)
linkfi, err := c.Fs.Source.Stat(link)
if err != nil {
jww.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
return nil
@ -642,25 +650,25 @@ func getDirList() []string {
return nil
}
helpers.SymbolicWalk(hugofs.Source(), dataDir, walker)
helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("contentDir")), walker)
helpers.SymbolicWalk(hugofs.Source(), i18nDir, walker)
helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("layoutDir")), walker)
helpers.SymbolicWalk(c.Fs.Source, dataDir, walker)
helpers.SymbolicWalk(c.Fs.Source, helpers.AbsPathify(viper.GetString("contentDir")), walker)
helpers.SymbolicWalk(c.Fs.Source, i18nDir, walker)
helpers.SymbolicWalk(c.Fs.Source, helpers.AbsPathify(viper.GetString("layoutDir")), walker)
helpers.SymbolicWalk(hugofs.Source(), staticDir, walker)
helpers.SymbolicWalk(c.Fs.Source, staticDir, walker)
if helpers.ThemeSet() {
helpers.SymbolicWalk(hugofs.Source(), filepath.Join(themesDir, "layouts"), walker)
helpers.SymbolicWalk(hugofs.Source(), filepath.Join(themesDir, "static"), walker)
helpers.SymbolicWalk(hugofs.Source(), filepath.Join(themesDir, "i18n"), walker)
helpers.SymbolicWalk(hugofs.Source(), filepath.Join(themesDir, "data"), walker)
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)
helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "data"), walker)
}
return a
}
func recreateAndBuildSites(cfg hugolib.DepsCfg, watching bool) (err error) {
if err := initSites(cfg); err != nil {
func (c commandeer) recreateAndBuildSites(watching bool) (err error) {
if err := c.initSites(); err != nil {
return err
}
if !quiet {
@ -669,9 +677,9 @@ func recreateAndBuildSites(cfg hugolib.DepsCfg, watching bool) (err error) {
return Hugo.Build(hugolib.BuildCfg{CreateSitesFromConfig: true, Watching: watching, PrintStats: !quiet})
}
func resetAndBuildSites(cfg hugolib.DepsCfg, watching bool) (err error) {
if err := initSites(cfg); err != nil {
return err
func (c commandeer) resetAndBuildSites(watching bool) (err error) {
if err = c.initSites(); err != nil {
return
}
if !quiet {
jww.FEEDBACK.Println("Started building sites ...")
@ -679,12 +687,12 @@ func resetAndBuildSites(cfg hugolib.DepsCfg, watching bool) (err error) {
return Hugo.Build(hugolib.BuildCfg{ResetState: true, Watching: watching, PrintStats: !quiet})
}
func initSites(cfg hugolib.DepsCfg) error {
func (c commandeer) initSites() error {
if Hugo != nil {
return nil
}
h, err := hugolib.NewHugoSitesFromConfiguration(cfg)
h, err := hugolib.NewHugoSitesFromConfiguration(c.DepsCfg)
if err != nil {
return err
@ -694,8 +702,8 @@ func initSites(cfg hugolib.DepsCfg) error {
return nil
}
func buildSites(cfg hugolib.DepsCfg, watching bool) (err error) {
if err := initSites(cfg); err != nil {
func (c commandeer) buildSites(watching bool) (err error) {
if err := c.initSites(); err != nil {
return err
}
if !quiet {
@ -704,19 +712,21 @@ func buildSites(cfg hugolib.DepsCfg, watching bool) (err error) {
return Hugo.Build(hugolib.BuildCfg{Watching: watching, PrintStats: !quiet})
}
func rebuildSites(cfg hugolib.DepsCfg, events []fsnotify.Event) error {
if err := initSites(cfg); err != nil {
func (c commandeer) rebuildSites(events []fsnotify.Event) error {
if err := c.initSites(); err != nil {
return err
}
return Hugo.Build(hugolib.BuildCfg{PrintStats: !quiet, Watching: true}, events...)
}
// newWatcher creates a new watcher to watch filesystem events.
func newWatcher(cfg hugolib.DepsCfg, 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
@ -728,7 +738,7 @@ func newWatcher(cfg hugolib.DepsCfg, port int) error {
wg.Add(1)
for _, d := range getDirList() {
for _, d := range c.getDirList() {
if d != "" {
_ = watcher.Add(d)
}
@ -793,12 +803,12 @@ func newWatcher(cfg hugolib.DepsCfg, port int) error {
// recursively add new directories to watch list
// When mkdir -p is used, only the top directory triggers an event (at least on OSX)
if ev.Op&fsnotify.Create == fsnotify.Create {
if s, err := hugofs.Source().Stat(ev.Name); err == nil && s.Mode().IsDir() {
helpers.SymbolicWalk(hugofs.Source(), ev.Name, walkAdder)
if s, err := c.Fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() {
helpers.SymbolicWalk(c.Fs.Source, ev.Name, walkAdder)
}
}
isstatic := strings.HasPrefix(ev.Name, helpers.GetStaticDirPath()) || (len(helpers.GetThemesDirPath()) > 0 && strings.HasPrefix(ev.Name, helpers.GetThemesDirPath()))
isstatic := strings.HasPrefix(ev.Name, helpers.GetStaticDirPath()) || (len(pathSpec.GetThemesDirPath()) > 0 && strings.HasPrefix(ev.Name, pathSpec.GetThemesDirPath()))
if isstatic {
staticEvents = append(staticEvents, ev)
@ -821,12 +831,12 @@ func newWatcher(cfg hugolib.DepsCfg, port int) error {
if viper.GetBool("forceSyncStatic") {
jww.FEEDBACK.Printf("Syncing all static files\n")
err := copyStatic()
err := c.copyStatic()
if err != nil {
utils.StopOnErr(err, fmt.Sprintf("Error copying static files to %s", publishDir))
}
} else {
staticSourceFs := getStaticSourceFs()
staticSourceFs := c.getStaticSourceFs()
if staticSourceFs == nil {
jww.WARN.Println("No static directories found to sync")
@ -837,7 +847,7 @@ func newWatcher(cfg hugolib.DepsCfg, port int) error {
syncer.NoTimes = viper.GetBool("noTimes")
syncer.NoChmod = viper.GetBool("noChmod")
syncer.SrcFs = staticSourceFs
syncer.DestFs = hugofs.Destination()
syncer.DestFs = c.Fs.Destination
// prevent spamming the log on changes
logger := helpers.NewDistinctFeedbackLogger()
@ -862,7 +872,7 @@ func newWatcher(cfg hugolib.DepsCfg, port int) error {
fromPath := ev.Name
// If we are here we already know the event took place in a static dir
relPath, err := helpers.MakeStaticPathRelative(fromPath)
relPath, err := pathSpec.MakeStaticPathRelative(fromPath)
if err != nil {
jww.ERROR.Println(err)
continue
@ -882,7 +892,7 @@ func newWatcher(cfg hugolib.DepsCfg, port int) error {
// If file doesn't exist in any static dir, remove it
toRemove := filepath.Join(publishDir, relPath)
logger.Println("File no longer exists in static dir, removing", toRemove)
hugofs.Destination().RemoveAll(toRemove)
c.Fs.Destination.RemoveAll(toRemove)
} else if err == nil {
// If file still exists, sync it
logger.Println("Syncing", relPath, "to", publishDir)
@ -910,7 +920,7 @@ func newWatcher(cfg hugolib.DepsCfg, port int) error {
// force refresh when more than one file
if len(staticEvents) > 0 {
for _, ev := range staticEvents {
path, _ := helpers.MakeStaticPathRelative(ev.Name)
path, _ := pathSpec.MakeStaticPathRelative(ev.Name)
livereload.RefreshPath(path)
}
@ -925,7 +935,7 @@ func newWatcher(cfg hugolib.DepsCfg, port int) error {
const layout = "2006-01-02 15:04 -0700"
jww.FEEDBACK.Println(time.Now().Format(layout))
rebuildSites(cfg, dynamicEvents)
c.rebuildSites(dynamicEvents)
if !buildWatch && !viper.GetBool("disableLiveReload") {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
@ -947,7 +957,7 @@ func newWatcher(cfg hugolib.DepsCfg, port int) error {
http.HandleFunc("/livereload", livereload.Handler)
}
go serve(port)
go c.serve(port)
}
wg.Wait()
@ -956,14 +966,13 @@ func newWatcher(cfg hugolib.DepsCfg, port int) error {
// isThemeVsHugoVersionMismatch returns whether the current Hugo version is
// less than the theme's min_version.
func isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinVersion string) {
func isThemeVsHugoVersionMismatch(fs afero.Fs) (mismatch bool, requiredMinVersion string) {
if !helpers.ThemeSet() {
return
}
themeDir := helpers.GetThemeDir()
fs := hugofs.Source()
path := filepath.Join(themeDir, "theme.toml")
exists, err := helpers.Exists(path, fs)

View File

@ -25,6 +25,7 @@ import (
"strings"
"time"
"github.com/spf13/afero"
"github.com/spf13/cast"
"github.com/spf13/cobra"
"github.com/spf13/hugo/helpers"
@ -122,7 +123,7 @@ func importFromJekyll(cmd *cobra.Command, args []string) error {
return convertJekyllPost(site, path, relPath, targetDir, draft)
}
err = helpers.SymbolicWalk(hugofs.Os(), jekyllRoot, callback)
err = helpers.SymbolicWalk(hugofs.Os, jekyllRoot, callback)
if err != nil {
return err
@ -137,7 +138,13 @@ func importFromJekyll(cmd *cobra.Command, args []string) error {
// TODO: Consider calling doNewSite() instead?
func createSiteFromJekyll(jekyllRoot, targetDir string, force bool) (*hugolib.Site, error) {
fs := hugofs.Source()
s, err := hugolib.NewSiteDefaultLang()
if err != nil {
return nil, err
}
fs := s.Fs.Source
if exists, _ := helpers.Exists(targetDir, fs); exists {
if isDir, _ := helpers.IsDir(targetDir, fs); !isDir {
return nil, errors.New("Target path \"" + targetDir + "\" already exists but not a directory")
@ -150,7 +157,7 @@ func createSiteFromJekyll(jekyllRoot, targetDir string, force bool) (*hugolib.Si
}
}
jekyllConfig := loadJekyllConfig(jekyllRoot)
jekyllConfig := loadJekyllConfig(fs, jekyllRoot)
// Crude test to make sure at least one of _drafts/ and _posts/ exists
// and is not empty.
@ -177,16 +184,14 @@ func createSiteFromJekyll(jekyllRoot, targetDir string, force bool) (*hugolib.Si
mkdir(targetDir, "data")
mkdir(targetDir, "themes")
createConfigFromJekyll(targetDir, "yaml", jekyllConfig)
createConfigFromJekyll(fs, targetDir, "yaml", jekyllConfig)
copyJekyllFilesAndFolders(jekyllRoot, filepath.Join(targetDir, "static"))
site := hugolib.NewSiteDefaultLang()
return site, nil
return s, nil
}
func loadJekyllConfig(jekyllRoot string) map[string]interface{} {
fs := hugofs.Source()
func loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]interface{} {
path := filepath.Join(jekyllRoot, "_config.yml")
exists, err := helpers.Exists(path, fs)
@ -218,7 +223,7 @@ func loadJekyllConfig(jekyllRoot string) map[string]interface{} {
return c.(map[string]interface{})
}
func createConfigFromJekyll(inpath string, kind string, jekyllConfig map[string]interface{}) (err error) {
func createConfigFromJekyll(fs afero.Fs, inpath string, kind string, jekyllConfig map[string]interface{}) (err error) {
title := "My New Hugo Site"
baseURL := "http://example.org/"
@ -251,7 +256,7 @@ func createConfigFromJekyll(inpath string, kind string, jekyllConfig map[string]
return err
}
err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), hugofs.Source())
err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), fs)
if err != nil {
return
}

View File

@ -16,15 +16,18 @@ package commands
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/hugo/create"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/hugolib"
"github.com/spf13/hugo/parser"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
@ -84,7 +87,9 @@ as you see fit.`,
// NewContent adds new content to a Hugo site.
func NewContent(cmd *cobra.Command, args []string) error {
if _, err := InitializeConfig(); err != nil {
cfg, err := InitializeConfig()
if err != nil {
return err
}
@ -110,10 +115,16 @@ func NewContent(cmd *cobra.Command, args []string) error {
kind = contentType
}
return create.NewContent(hugofs.Source(), kind, createpath)
s, err := hugolib.NewSite(cfg)
if err != nil {
return newSystemError(err)
}
return create.NewContent(s, kind, createpath)
}
func doNewSite(basepath string, force bool) error {
func doNewSite(fs *hugofs.Fs, basepath string, force bool) error {
dirs := []string{
filepath.Join(basepath, "layouts"),
filepath.Join(basepath, "content"),
@ -123,12 +134,12 @@ func doNewSite(basepath string, force bool) error {
filepath.Join(basepath, "themes"),
}
if exists, _ := helpers.Exists(basepath, hugofs.Source()); exists {
if isDir, _ := helpers.IsDir(basepath, hugofs.Source()); !isDir {
if exists, _ := helpers.Exists(basepath, fs.Source); exists {
if isDir, _ := helpers.IsDir(basepath, fs.Source); !isDir {
return errors.New(basepath + " already exists but not a directory")
}
isEmpty, _ := helpers.IsEmpty(basepath, hugofs.Source())
isEmpty, _ := helpers.IsEmpty(basepath, fs.Source)
switch {
case !isEmpty && !force:
@ -137,7 +148,7 @@ func doNewSite(basepath string, force bool) error {
case !isEmpty && force:
all := append(dirs, filepath.Join(basepath, "config."+configFormat))
for _, path := range all {
if exists, _ := helpers.Exists(path, hugofs.Source()); exists {
if exists, _ := helpers.Exists(path, fs.Source); exists {
return errors.New(path + " already exists")
}
}
@ -145,10 +156,12 @@ func doNewSite(basepath string, force bool) error {
}
for _, dir := range dirs {
hugofs.Source().MkdirAll(dir, 0777)
if err := fs.Source.MkdirAll(dir, 0777); err != nil {
return fmt.Errorf("Failed to create dir: %s", err)
}
}
createConfig(basepath, configFormat)
createConfig(fs, basepath, configFormat)
jww.FEEDBACK.Printf("Congratulations! Your new Hugo site is created in %s.\n\n", basepath)
jww.FEEDBACK.Println(nextStepsText())
@ -190,12 +203,14 @@ func NewSite(cmd *cobra.Command, args []string) error {
forceNew, _ := cmd.Flags().GetBool("force")
return doNewSite(createpath, forceNew)
return doNewSite(hugofs.NewDefault(), createpath, forceNew)
}
// NewTheme creates a new Hugo theme.
func NewTheme(cmd *cobra.Command, args []string) error {
if _, err := InitializeConfig(); err != nil {
cfg, err := InitializeConfig()
if err != nil {
return err
}
@ -207,26 +222,26 @@ func NewTheme(cmd *cobra.Command, args []string) error {
createpath := helpers.AbsPathify(filepath.Join(viper.GetString("themesDir"), args[0]))
jww.INFO.Println("creating theme at", createpath)
if x, _ := helpers.Exists(createpath, hugofs.Source()); x {
if x, _ := helpers.Exists(createpath, cfg.Fs.Source); x {
return newUserError(createpath, "already exists")
}
mkdir(createpath, "layouts", "_default")
mkdir(createpath, "layouts", "partials")
touchFile(createpath, "layouts", "index.html")
touchFile(createpath, "layouts", "404.html")
touchFile(createpath, "layouts", "_default", "list.html")
touchFile(createpath, "layouts", "_default", "single.html")
touchFile(cfg.Fs.Source, createpath, "layouts", "index.html")
touchFile(cfg.Fs.Source, createpath, "layouts", "404.html")
touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "list.html")
touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "single.html")
touchFile(createpath, "layouts", "partials", "header.html")
touchFile(createpath, "layouts", "partials", "footer.html")
touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "header.html")
touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "footer.html")
mkdir(createpath, "archetypes")
archDefault := []byte("+++\n+++\n")
err := helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), hugofs.Source())
err = helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), cfg.Fs.Source)
if err != nil {
return err
}
@ -256,12 +271,12 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
`)
err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE.md"), bytes.NewReader(by), hugofs.Source())
err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE.md"), bytes.NewReader(by), cfg.Fs.Source)
if err != nil {
return err
}
createThemeMD(createpath)
createThemeMD(cfg.Fs, createpath)
return nil
}
@ -275,16 +290,16 @@ func mkdir(x ...string) {
}
}
func touchFile(x ...string) {
func touchFile(fs afero.Fs, x ...string) {
inpath := filepath.Join(x...)
mkdir(filepath.Dir(inpath))
err := helpers.WriteToDisk(inpath, bytes.NewReader([]byte{}), hugofs.Source())
err := helpers.WriteToDisk(inpath, bytes.NewReader([]byte{}), fs)
if err != nil {
jww.FATAL.Fatalln(err)
}
}
func createThemeMD(inpath string) (err error) {
func createThemeMD(fs *hugofs.Fs, inpath string) (err error) {
by := []byte(`# theme.toml template for a Hugo theme
# See https://github.com/spf13/hugoThemes#themetoml for an example
@ -309,7 +324,7 @@ min_version = 0.18
repo = ""
`)
err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), hugofs.Source())
err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), fs.Source)
if err != nil {
return
}
@ -320,7 +335,7 @@ min_version = 0.18
func newContentPathSection(path string) (string, string) {
// Forward slashes is used in all examples. Convert if needed.
// Issue #1133
createpath := strings.Replace(path, "/", helpers.FilePathSeparator, -1)
createpath := filepath.FromSlash(path)
var section string
// assume the first directory is the section (kind)
if strings.Contains(createpath[1:], helpers.FilePathSeparator) {
@ -330,7 +345,7 @@ func newContentPathSection(path string) (string, string) {
return createpath, section
}
func createConfig(inpath string, kind string) (err error) {
func createConfig(fs *hugofs.Fs, inpath string, kind string) (err error) {
in := map[string]interface{}{
"baseURL": "http://example.org/",
"title": "My New Hugo Site",
@ -343,7 +358,7 @@ func createConfig(inpath string, kind string) (err error) {
return err
}
err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), hugofs.Source())
err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), fs.Source)
if err != nil {
return
}

View File

@ -14,12 +14,12 @@
package commands
import (
"os"
"path/filepath"
"testing"
"github.com/spf13/hugo/hugofs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Issue #1133
@ -29,7 +29,8 @@ func TestNewContentPathSectionWithForwardSlashes(t *testing.T) {
assert.Equal(t, "post", s)
}
func checkNewSiteInited(basepath string, t *testing.T) {
func checkNewSiteInited(fs *hugofs.Fs, basepath string, t *testing.T) {
paths := []string{
filepath.Join(basepath, "layouts"),
filepath.Join(basepath, "content"),
@ -40,63 +41,70 @@ func checkNewSiteInited(basepath string, t *testing.T) {
}
for _, path := range paths {
_, err := hugofs.Source().Stat(path)
assert.Nil(t, err)
_, err := fs.Source.Stat(path)
require.NoError(t, err)
}
}
func TestDoNewSite(t *testing.T) {
basepath := filepath.Join(os.TempDir(), "blog")
hugofs.InitMemFs()
err := doNewSite(basepath, false)
assert.Nil(t, err)
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
checkNewSiteInited(basepath, t)
require.NoError(t, doNewSite(fs, basepath, false))
checkNewSiteInited(fs, basepath, t)
}
func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) {
basepath := filepath.Join(os.TempDir(), "blog")
hugofs.InitMemFs()
hugofs.Source().MkdirAll(basepath, 777)
err := doNewSite(basepath, false)
assert.Nil(t, err)
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
require.NoError(t, doNewSite(fs, basepath, false))
}
func TestDoNewSite_error_base_exists(t *testing.T) {
basepath := filepath.Join(os.TempDir(), "blog")
hugofs.InitMemFs()
hugofs.Source().MkdirAll(basepath, 777)
hugofs.Source().Create(filepath.Join(basepath, "foo"))
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
_, err := fs.Source.Create(filepath.Join(basepath, "foo"))
require.NoError(t, err)
// Since the directory already exists and isn't empty, expect an error
err := doNewSite(basepath, false)
assert.NotNil(t, err)
require.Error(t, doNewSite(fs, basepath, false))
}
func TestDoNewSite_force_empty_dir(t *testing.T) {
basepath := filepath.Join(os.TempDir(), "blog")
hugofs.InitMemFs()
hugofs.Source().MkdirAll(basepath, 777)
err := doNewSite(basepath, true)
assert.Nil(t, err)
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
checkNewSiteInited(basepath, t)
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
require.NoError(t, doNewSite(fs, basepath, true))
checkNewSiteInited(fs, basepath, t)
}
func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) {
basepath := filepath.Join(os.TempDir(), "blog")
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
contentPath := filepath.Join(basepath, "content")
hugofs.InitMemFs()
hugofs.Source().MkdirAll(contentPath, 777)
err := doNewSite(basepath, true)
assert.NotNil(t, err)
require.NoError(t, fs.Source.MkdirAll(contentPath, 777))
require.Error(t, doNewSite(fs, basepath, true))
}
func TestDoNewSite_error_force_config_inside_exists(t *testing.T) {
basepath := filepath.Join(os.TempDir(), "blog")
basepath := filepath.Join("base", "blog")
fs := hugofs.NewMem()
configPath := filepath.Join(basepath, "config.toml")
hugofs.InitMemFs()
hugofs.Source().MkdirAll(basepath, 777)
hugofs.Source().Create(configPath)
err := doNewSite(basepath, true)
assert.NotNil(t, err)
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
_, err := fs.Source.Create(configPath)
require.NoError(t, err)
require.Error(t, doNewSite(fs, basepath, true))
}

View File

@ -29,7 +29,6 @@ import (
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
)
@ -109,6 +108,8 @@ func server(cmd *cobra.Command, args []string) error {
return err
}
c := commandeer{cfg}
if flagChanged(cmd.Flags(), "disableLiveReload") {
viper.Set("disableLiveReload", disableLiveReload)
}
@ -119,7 +120,7 @@ func server(cmd *cobra.Command, args []string) error {
if viper.GetBool("watch") {
serverWatch = true
watchConfig(cfg)
c.watchConfig()
}
l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(serverPort)))
@ -157,18 +158,18 @@ func server(cmd *cobra.Command, args []string) error {
// Hugo writes the output to memory instead of the disk
if !renderToDisk {
hugofs.SetDestination(new(afero.MemMapFs))
cfg.Fs.Destination = new(afero.MemMapFs)
// Rendering to memoryFS, publish to Root regardless of publishDir.
viper.Set("publishDir", "/")
}
if err := build(cfg, serverWatch); err != nil {
if err := c.build(serverWatch); err != nil {
return err
}
// Watch runs its own server as part of the routine
if serverWatch {
watchDirs := getDirList()
watchDirs := c.getDirList()
baseWatchDir := viper.GetString("workingDir")
for i, dir := range watchDirs {
watchDirs[i], _ = helpers.GetRelativePath(dir, baseWatchDir)
@ -177,26 +178,26 @@ func server(cmd *cobra.Command, args []string) error {
rootWatchDirs := strings.Join(helpers.UniqueStrings(helpers.ExtractRootPaths(watchDirs)), ",")
jww.FEEDBACK.Printf("Watching for changes in %s%s{%s}\n", baseWatchDir, helpers.FilePathSeparator, rootWatchDirs)
err := newWatcher(cfg, serverPort)
err := c.newWatcher(serverPort)
if err != nil {
return err
}
}
serve(serverPort)
c.serve(serverPort)
return nil
}
func serve(port int) {
func (c commandeer) serve(port int) {
if renderToDisk {
jww.FEEDBACK.Println("Serving pages from " + helpers.AbsPathify(viper.GetString("publishDir")))
} else {
jww.FEEDBACK.Println("Serving pages from memory")
}
httpFs := afero.NewHttpFs(hugofs.Destination())
httpFs := afero.NewHttpFs(c.Fs.Destination)
fs := filesOnlyFs{httpFs.Dir(helpers.AbsPathify(viper.GetString("publishDir")))}
fileserver := http.FileServer(fs)

View File

@ -20,7 +20,6 @@ import (
"time"
"github.com/spf13/cobra"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/parser"
)
@ -37,7 +36,9 @@ If the content's draft status is 'False', nothing is done.`,
// to false and setting its publish date to now. If the specified content is
// not a draft, it will log an error.
func Undraft(cmd *cobra.Command, args []string) error {
if _, err := InitializeConfig(); err != nil {
cfg, err := InitializeConfig()
if err != nil {
return err
}
@ -47,7 +48,7 @@ func Undraft(cmd *cobra.Command, args []string) error {
location := args[0]
// open the file
f, err := hugofs.Source().Open(location)
f, err := cfg.Fs.Source.Open(location)
if err != nil {
return err
}
@ -64,7 +65,7 @@ func Undraft(cmd *cobra.Command, args []string) error {
return newSystemErrorF("an error occurred while undrafting %q: %s", location, err)
}
f, err = hugofs.Source().OpenFile(location, os.O_WRONLY|os.O_TRUNC, 0644)
f, err = cfg.Fs.Source.OpenFile(location, os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return newSystemErrorF("%q not be undrafted due to error opening file to save changes: %q\n", location, err)
}

View File

@ -34,15 +34,15 @@ import (
// NewContent creates a new content file in the content directory based upon the
// given kind, which is used to lookup an archetype.
func NewContent(fs afero.Fs, kind, name string) (err error) {
func NewContent(s *hugolib.Site, kind, name string) (err error) {
jww.INFO.Println("attempting to create ", name, "of", kind)
location := FindArchetype(fs, kind)
location := FindArchetype(s.Fs.Source, kind)
var by []byte
if location != "" {
by, err = afero.ReadFile(fs, location)
by, err = afero.ReadFile(s.Fs.Source, location)
if err != nil {
jww.ERROR.Println(err)
}
@ -62,9 +62,7 @@ func NewContent(fs afero.Fs, kind, name string) (err error) {
return err
}
site := hugolib.NewSiteDefaultLang()
page, err := site.NewPage(name)
page, err := s.NewPage(name)
if err != nil {
return err
}

View File

@ -19,23 +19,22 @@ import (
"strings"
"testing"
"github.com/spf13/hugo/hugolib"
"fmt"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/afero"
"github.com/spf13/hugo/create"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
func TestNewContent(t *testing.T) {
initViper()
err := initFs()
if err != nil {
t.Fatalf("initialization error: %s", err)
}
cases := []struct {
kind string
path string
@ -48,15 +47,15 @@ func TestNewContent(t *testing.T) {
{"product", "product/sample-4.md", []string{`title = "sample 4"`}}, // empty archetype front matter
}
for i, c := range cases {
err = create.NewContent(hugofs.Source(), c.kind, c.path)
if err != nil {
t.Errorf("[%d] NewContent: %s", i, err)
}
for _, c := range cases {
s, err := hugolib.NewEnglishSite()
require.NoError(t, err)
require.NoError(t, initFs(s.Fs))
require.NoError(t, create.NewContent(s, c.kind, c.path))
fname := filepath.Join("content", filepath.FromSlash(c.path))
content := readFileFromFs(t, hugofs.Source(), fname)
content := readFileFromFs(t, s.Fs.Source, fname)
for i, v := range c.expected {
found := strings.Contains(content, v)
if !found {
@ -72,11 +71,11 @@ func initViper() {
viper.Set("archetypeDir", "archetypes")
viper.Set("contentDir", "content")
viper.Set("themesDir", "themes")
viper.Set("layoutDir", "layouts")
viper.Set("theme", "sample")
}
func initFs() error {
hugofs.InitMemFs()
func initFs(fs *hugofs.Fs) error {
perm := os.FileMode(0755)
var err error
@ -87,7 +86,7 @@ func initFs() error {
filepath.Join("themes", "sample", "archetypes"),
}
for _, dir := range dirs {
err = hugofs.Source().Mkdir(dir, perm)
err = fs.Source.Mkdir(dir, perm)
if err != nil {
return err
}
@ -111,7 +110,7 @@ func initFs() error {
content: "+++\ndate =\"\"\ntitle = \"Empty Date Arch title\"\ntest = \"test1\"\n+++\n",
},
} {
f, err := hugofs.Source().Create(v.path)
f, err := fs.Source.Create(v.path)
if err != nil {
return err
}

115
deps/deps.go vendored Normal file
View File

@ -0,0 +1,115 @@
package deps
import (
"io/ioutil"
"log"
"os"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/tplapi"
jww "github.com/spf13/jwalterweatherman"
)
// Deps holds dependencies used by many.
// There will be normally be only one instance of deps in play
// at a given time, i.e. one per Site built.
type Deps struct {
// The logger to use.
Log *jww.Notepad `json:"-"`
// The templates to use.
Tmpl tplapi.Template `json:"-"`
// The file systems to use.
Fs *hugofs.Fs `json:"-"`
// The PathSpec to use
*helpers.PathSpec `json:"-"`
templateProvider TemplateProvider
WithTemplate func(templ tplapi.Template) error
// TODO(bep) globals next in line: Viper
}
// Used to create and refresh, and clone the template.
type TemplateProvider interface {
Update(deps *Deps) error
Clone(deps *Deps) error
}
func (d *Deps) LoadTemplates() error {
if err := d.templateProvider.Update(d); err != nil {
return err
}
d.Tmpl.PrintErrors()
return nil
}
func New(cfg DepsCfg) *Deps {
var (
logger = cfg.Logger
fs = cfg.Fs
)
if cfg.TemplateProvider == nil {
panic("Must have a TemplateProvider")
}
if cfg.Language == nil {
panic("Must have a Language")
}
if logger == nil {
logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
}
if fs == nil {
// Default to the most used file systems.
fs = hugofs.NewMem()
}
d := &Deps{
Fs: fs,
Log: logger,
templateProvider: cfg.TemplateProvider,
WithTemplate: cfg.WithTemplate,
PathSpec: helpers.NewPathSpec(fs, cfg.Language),
}
return d
}
// ForLanguage creates a copy of the Deps with the language dependent
// parts switched out.
func (d Deps) ForLanguage(l *helpers.Language) (*Deps, error) {
d.PathSpec = helpers.NewPathSpec(d.Fs, l)
if err := d.templateProvider.Clone(&d); err != nil {
return nil, err
}
return &d, nil
}
// DepsCfg contains configuration options that can be used to configure Hugo
// on a global level, i.e. logging etc.
// Nil values will be given default values.
type DepsCfg struct {
// The Logger to use.
Logger *jww.Notepad
// The file systems to use
Fs *hugofs.Fs
// The language to use.
Language *helpers.Language
// Template handling.
TemplateProvider TemplateProvider
WithTemplate func(templ tplapi.Template) error
}

View File

@ -29,7 +29,6 @@ import (
// TODO(bep) Get rid of these.
var (
currentConfigProvider ConfigProvider
currentPathSpec *PathSpec
)
// ConfigProvider provides the configuration settings for Hugo.
@ -52,24 +51,13 @@ func Config() ConfigProvider {
return viper.Get("currentContentLanguage").(ConfigProvider)
}
// CurrentPathSpec returns the current PathSpec.
// If it is not set, a new will be created based in the currently active Hugo config.
func CurrentPathSpec() *PathSpec {
if currentPathSpec != nil {
return currentPathSpec
}
// Some tests rely on this. We will fix that, eventually.
return NewPathSpecFromConfig(Config())
}
// InitConfigProviderForCurrentContentLanguage does what it says.
func InitConfigProviderForCurrentContentLanguage() {
currentConfigProvider = viper.Get("CurrentContentLanguage").(ConfigProvider)
currentPathSpec = NewPathSpecFromConfig(currentConfigProvider)
}
// ResetConfigProvider is used in tests.
func ResetConfigProvider() {
currentConfigProvider = nil
currentPathSpec = nil
}

View File

@ -23,8 +23,6 @@ import (
"strings"
"unicode"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/afero"
"github.com/spf13/viper"
"golang.org/x/text/transform"
@ -196,29 +194,29 @@ func GetRelativeThemeDir() string {
// GetThemeStaticDirPath returns the theme's static dir path if theme is set.
// If theme is set and the static dir doesn't exist, an error is returned.
func GetThemeStaticDirPath() (string, error) {
return getThemeDirPath("static")
func (p *PathSpec) GetThemeStaticDirPath() (string, error) {
return p.getThemeDirPath("static")
}
// GetThemeDataDirPath returns the theme's data dir path if theme is set.
// If theme is set and the data dir doesn't exist, an error is returned.
func GetThemeDataDirPath() (string, error) {
return getThemeDirPath("data")
func (p *PathSpec) GetThemeDataDirPath() (string, error) {
return p.getThemeDirPath("data")
}
// GetThemeI18nDirPath returns the theme's i18n dir path if theme is set.
// If theme is set and the i18n dir doesn't exist, an error is returned.
func GetThemeI18nDirPath() (string, error) {
return getThemeDirPath("i18n")
func (p *PathSpec) GetThemeI18nDirPath() (string, error) {
return p.getThemeDirPath("i18n")
}
func getThemeDirPath(path string) (string, error) {
func (p *PathSpec) getThemeDirPath(path string) (string, error) {
if !ThemeSet() {
return "", ErrThemeUndefined
}
themeDir := filepath.Join(GetThemeDir(), path)
if _, err := hugofs.Source().Stat(themeDir); os.IsNotExist(err) {
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)
}
@ -228,17 +226,17 @@ func getThemeDirPath(path string) (string, error) {
// GetThemesDirPath gets the static files directory of the current theme, if there is one.
// Ignores underlying errors.
// TODO(bep) Candidate for deprecation?
func GetThemesDirPath() string {
dir, _ := getThemeDirPath("static")
func (p *PathSpec) GetThemesDirPath() string {
dir, _ := p.getThemeDirPath("static")
return dir
}
// MakeStaticPathRelative makes a relative path to the static files directory.
// It does so by taking either the project's static path or the theme's static
// path into consideration.
func MakeStaticPathRelative(inPath string) (string, error) {
func (p *PathSpec) MakeStaticPathRelative(inPath string) (string, error) {
staticDir := GetStaticDirPath()
themeStaticDir := GetThemesDirPath()
themeStaticDir := p.GetThemesDirPath()
return makePathRelative(inPath, staticDir, themeStaticDir)
}

View File

@ -30,6 +30,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/spf13/afero"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
)
@ -64,7 +65,8 @@ func TestMakePath(t *testing.T) {
for _, test := range tests {
viper.Set("removePathAccents", test.removeAccents)
p := NewPathSpecFromConfig(viper.GetViper())
p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
output := p.MakePath(test.input)
if output != test.expected {
t.Errorf("Expected %#v, got %#v\n", test.expected, output)
@ -77,7 +79,7 @@ func TestMakePathSanitized(t *testing.T) {
defer viper.Reset()
initCommonTestConfig()
p := NewPathSpecFromConfig(viper.GetViper())
p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
tests := []struct {
input string
@ -105,7 +107,7 @@ func TestMakePathSanitizedDisablePathToLower(t *testing.T) {
initCommonTestConfig()
viper.Set("disablePathToLower", true)
p := NewPathSpecFromConfig(viper.GetViper())
p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
tests := []struct {
input string

View File

@ -13,6 +13,12 @@
package helpers
import (
"fmt"
"github.com/spf13/hugo/hugofs"
)
// PathSpec holds methods that decides how paths in URLs and files in Hugo should look like.
type PathSpec struct {
disablePathToLower bool
@ -33,11 +39,27 @@ type PathSpec struct {
defaultContentLanguageInSubdir bool
defaultContentLanguage string
multilingual bool
// The file systems to use
fs *hugofs.Fs
}
// NewPathSpecFromConfig creats a new PathSpec from the given ConfigProvider.
func NewPathSpecFromConfig(config ConfigProvider) *PathSpec {
func (p PathSpec) String() string {
return fmt.Sprintf("PathSpec, language %q, prefix %q, multilingual: %T", p.currentContentLanguage.Lang, p.getLanguagePrefix(), p.multilingual)
}
// NewPathSpec creats a new PathSpec from the given filesystems and ConfigProvider.
func NewPathSpec(fs *hugofs.Fs, config ConfigProvider) *PathSpec {
currCl, ok := config.Get("currentContentLanguage").(*Language)
if !ok {
// TODO(bep) globals
currCl = NewLanguage("en")
}
return &PathSpec{
fs: fs,
disablePathToLower: config.GetBool("disablePathToLower"),
removePathAccents: config.GetBool("removePathAccents"),
uglyURLs: config.GetBool("uglyURLs"),
@ -45,7 +67,7 @@ func NewPathSpecFromConfig(config ConfigProvider) *PathSpec {
multilingual: config.GetBool("multilingual"),
defaultContentLanguageInSubdir: config.GetBool("defaultContentLanguageInSubdir"),
defaultContentLanguage: config.GetString("defaultContentLanguage"),
currentContentLanguage: config.Get("currentContentLanguage").(*Language),
currentContentLanguage: currCl,
paginatePath: config.GetString("paginatePath"),
}
}

View File

@ -16,6 +16,8 @@ package helpers
import (
"testing"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
@ -31,15 +33,15 @@ func TestNewPathSpecFromConfig(t *testing.T) {
viper.Set("canonifyURLs", true)
viper.Set("paginatePath", "side")
pathSpec := NewPathSpecFromConfig(viper.GetViper())
p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
require.True(t, pathSpec.canonifyURLs)
require.True(t, pathSpec.defaultContentLanguageInSubdir)
require.True(t, pathSpec.disablePathToLower)
require.True(t, pathSpec.multilingual)
require.True(t, pathSpec.removePathAccents)
require.True(t, pathSpec.uglyURLs)
require.Equal(t, "no", pathSpec.defaultContentLanguage)
require.Equal(t, "no", pathSpec.currentContentLanguage.Lang)
require.Equal(t, "side", pathSpec.paginatePath)
require.True(t, p.canonifyURLs)
require.True(t, p.defaultContentLanguageInSubdir)
require.True(t, p.disablePathToLower)
require.True(t, p.multilingual)
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, "side", p.paginatePath)
}

View File

@ -60,7 +60,7 @@ func Highlight(code, lang, optsStr string) string {
io.WriteString(hash, lang)
io.WriteString(hash, options)
fs := hugofs.Os()
fs := hugofs.Os
ignoreCache := viper.GetBool("ignoreCache")
cacheDir := viper.GetString("cacheDir")

View File

@ -18,6 +18,7 @@ import (
"strings"
"testing"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -26,7 +27,7 @@ import (
func TestURLize(t *testing.T) {
initCommonTestConfig()
p := NewPathSpecFromConfig(viper.GetViper())
p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
tests := []struct {
input string
@ -85,9 +86,11 @@ 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)
p := NewPathSpecFromConfig(viper.GetViper())
output := p.AbsURL(test.input, addLanguage)
expected := test.expected
if multilingual && addLanguage {
@ -164,7 +167,7 @@ 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 := NewPathSpecFromConfig(viper.GetViper())
p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
output := p.RelURL(test.input, addLanguage)
@ -247,9 +250,10 @@ func TestURLPrep(t *testing.T) {
{false, "/section/name.html", "/section/name/"},
{true, "/section/name/index.html", "/section/name.html"},
}
for i, d := range data {
viper.Set("uglyURLs", d.ugly)
p := NewPathSpecFromConfig(viper.GetViper())
p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
output := p.URLPrep(d.input)
if d.output != output {

View File

@ -19,76 +19,54 @@ import (
"github.com/spf13/viper"
)
var (
sourceFs afero.Fs
destinationFs afero.Fs
osFs afero.Fs = &afero.OsFs{}
workingDirFs *afero.BasePathFs
)
// Os points to an Os Afero file system.
var Os = &afero.OsFs{}
// Source returns Hugo's source file system.
func Source() afero.Fs {
return sourceFs
type Fs struct {
// Source is Hugo's source file system.
Source afero.Fs
// Destination is Hugo's destionation file system.
Destination afero.Fs
// Os is an OS file system.
Os afero.Fs
// WorkingDir is a read-only file system
// restricted to the project working dir.
WorkingDir *afero.BasePathFs
}
// SetSource sets Hugo's source file system
// and re-initializes dependent file systems.
func SetSource(fs afero.Fs) {
sourceFs = fs
initSourceDependencies()
}
// Destination returns Hugo's destionation file system.
func Destination() afero.Fs {
return destinationFs
}
// SetDestination sets Hugo's destionation file system
func SetDestination(fs afero.Fs) {
destinationFs = fs
}
// Os returns an OS file system.
func Os() afero.Fs {
return osFs
}
// WorkingDir returns a read-only file system
// restricted to the project working dir.
func WorkingDir() *afero.BasePathFs {
return workingDirFs
}
// InitDefaultFs initializes with the OS file system
// NewDefault creates a new Fs with the OS file system
// as source and destination file systems.
func InitDefaultFs() {
InitFs(&afero.OsFs{})
func NewDefault() *Fs {
fs := &afero.OsFs{}
return newFs(fs)
}
// InitMemFs initializes with a MemMapFs as source and destination file systems.
// NewDefault creates a new Fs with the MemMapFs
// as source and destination file systems.
// Useful for testing.
func InitMemFs() {
InitFs(&afero.MemMapFs{})
func NewMem() *Fs {
fs := &afero.MemMapFs{}
return newFs(fs)
}
// InitFs initializes with the given file system
// as source and destination file systems.
func InitFs(fs afero.Fs) {
sourceFs = fs
destinationFs = fs
initSourceDependencies()
func newFs(base afero.Fs) *Fs {
return &Fs{
Source: base,
Destination: base,
Os: &afero.OsFs{},
WorkingDir: getWorkingDirFs(base),
}
}
func initSourceDependencies() {
func getWorkingDirFs(base afero.Fs) *afero.BasePathFs {
workingDir := viper.GetString("workingDir")
if workingDir != "" {
workingDirFs = afero.NewBasePathFs(afero.NewReadOnlyFs(sourceFs), workingDir).(*afero.BasePathFs)
return afero.NewBasePathFs(afero.NewReadOnlyFs(base), workingDir).(*afero.BasePathFs)
}
}
func init() {
InitDefaultFs()
return nil
}

View File

@ -21,51 +21,35 @@ import (
"github.com/stretchr/testify/assert"
)
func TestInitDefault(t *testing.T) {
func TestNewDefault(t *testing.T) {
viper.Reset()
defer viper.Reset()
InitDefaultFs()
f := NewDefault()
assert.NotNil(t, Source())
assert.IsType(t, new(afero.OsFs), Source())
assert.NotNil(t, Destination())
assert.IsType(t, new(afero.OsFs), Destination())
assert.NotNil(t, Os())
assert.IsType(t, new(afero.OsFs), Os())
assert.Nil(t, WorkingDir())
assert.NotNil(t, f.Source)
assert.IsType(t, new(afero.OsFs), f.Source)
assert.NotNil(t, f.Destination)
assert.IsType(t, new(afero.OsFs), f.Destination)
assert.NotNil(t, f.Os)
assert.IsType(t, new(afero.OsFs), f.Os)
assert.Nil(t, f.WorkingDir)
assert.IsType(t, new(afero.OsFs), Os)
}
func TestInitMemFs(t *testing.T) {
func TestNewMem(t *testing.T) {
viper.Reset()
defer viper.Reset()
InitMemFs()
f := NewMem()
assert.NotNil(t, Source())
assert.IsType(t, new(afero.MemMapFs), Source())
assert.NotNil(t, Destination())
assert.IsType(t, new(afero.MemMapFs), Destination())
assert.IsType(t, new(afero.OsFs), Os())
assert.Nil(t, WorkingDir())
}
func TestSetSource(t *testing.T) {
InitMemFs()
SetSource(new(afero.OsFs))
assert.NotNil(t, Source())
assert.IsType(t, new(afero.OsFs), Source())
}
func TestSetDestination(t *testing.T) {
InitMemFs()
SetDestination(new(afero.OsFs))
assert.NotNil(t, Destination())
assert.IsType(t, new(afero.OsFs), Destination())
assert.NotNil(t, f.Source)
assert.IsType(t, new(afero.MemMapFs), f.Source)
assert.NotNil(t, f.Destination)
assert.IsType(t, new(afero.MemMapFs), f.Destination)
assert.IsType(t, new(afero.OsFs), f.Os)
assert.Nil(t, f.WorkingDir)
}
func TestWorkingDir(t *testing.T) {
@ -74,8 +58,8 @@ func TestWorkingDir(t *testing.T) {
viper.Set("workingDir", "/a/b/")
InitMemFs()
f := NewMem()
assert.NotNil(t, WorkingDir())
assert.IsType(t, new(afero.BasePathFs), WorkingDir())
assert.NotNil(t, f.WorkingDir)
assert.IsType(t, new(afero.BasePathFs), f.WorkingDir)
}

View File

@ -16,6 +16,10 @@ package hugolib
import (
"path/filepath"
"testing"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
"github.com/stretchr/testify/require"
)
const pageWithAlias = `---
@ -30,31 +34,37 @@ const aliasTemplate = "<html><body>ALIASTEMPLATE</body></html>"
func TestAlias(t *testing.T) {
testCommonResetState()
writeSource(t, filepath.Join("content", "page.md"), pageWithAlias)
writeSource(t, filepath.Join("layouts", "_default", "single.html"), basicTemplate)
if err := buildAndRenderSite(NewSiteDefaultLang()); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
fs := hugofs.NewMem()
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{})
// the real page
assertFileContent(t, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
// the alias redirector
assertFileContent(t, filepath.Join("public", "foo", "bar", "index.html"), false, "<meta http-equiv=\"refresh\" content=\"0; ")
assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "<meta http-equiv=\"refresh\" content=\"0; ")
}
func TestAliasTemplate(t *testing.T) {
testCommonResetState()
writeSource(t, filepath.Join("content", "page.md"), pageWithAlias)
writeSource(t, filepath.Join("layouts", "_default", "single.html"), basicTemplate)
writeSource(t, filepath.Join("layouts", "alias.html"), aliasTemplate)
if err := buildAndRenderSite(NewSiteDefaultLang()); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
fs := hugofs.NewMem()
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})
require.NoError(t, err)
require.NoError(t, sites.Build(BuildCfg{}))
// the real page
assertFileContent(t, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
// the alias redirector
assertFileContent(t, filepath.Join("public", "foo", "bar", "index.html"), false, "ALIASTEMPLATE")
assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "ALIASTEMPLATE")
}

View File

@ -18,6 +18,11 @@ import (
"path/filepath"
"strings"
"testing"
"github.com/spf13/viper"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
)
var (
@ -106,22 +111,22 @@ ColorS:
`
)
func caseMixingTestsWriteCommonSources(t *testing.T) {
writeSource(t, filepath.Join("content", "sect1", "page1.md"), caseMixingPage1)
writeSource(t, filepath.Join("content", "sect2", "page2.md"), caseMixingPage2)
writeSource(t, filepath.Join("content", "sect1", "page1.en.md"), caseMixingPage1En)
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)
writeSource(t, "layouts/shortcodes/shortcode.html", `
writeSource(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, "layouts/partials/partial.html", `
writeSource(t, fs, "layouts/partials/partial.html", `
Partial Page: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}
Partial Site: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
`)
writeSource(t, "config.toml", caseMixingSiteConfigTOML)
writeSource(t, fs, "config.toml", caseMixingSiteConfigTOML)
}
@ -139,13 +144,21 @@ func TestCaseInsensitiveConfigurationVariations(t *testing.T) {
// page frontmatter: regular fields, blackfriday config, param with nested map
testCommonResetState()
caseMixingTestsWriteCommonSources(t)
writeSource(t, filepath.Join("layouts", "_default", "baseof.html"), `
depsCfg := newTestDepsConfig()
viper.SetFs(depsCfg.Fs.Source)
caseMixingTestsWriteCommonSources(t, depsCfg.Fs)
if err := LoadGlobalConfig("", "config.toml"); err != nil {
t.Fatalf("Failed to load config: %s", err)
}
writeSource(t, depsCfg.Fs, filepath.Join("layouts", "_default", "baseof.html"), `
Block Page Colors: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}
{{ block "main" . }}default{{end}}`)
writeSource(t, filepath.Join("layouts", "sect2", "single.html"), `
writeSource(t, depsCfg.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 }}
@ -154,7 +167,7 @@ Site Colors: {{ .Site.Params.COlOR }}|{{ .Site.Params.COLORS.YELLOW }}
{{ end }}
`)
writeSource(t, filepath.Join("layouts", "_default", "single.html"), `
writeSource(t, depsCfg.Fs, filepath.Join("layouts", "_default", "single.html"), `
Page Title: {{ .Title }}
Site Title: {{ .Site.Title }}
Site Lang Mood: {{ .Site.Language.Params.MOoD }}
@ -164,11 +177,7 @@ Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
{{ partial "partial.html" . }}
`)
if err := LoadGlobalConfig("", "config.toml"); err != nil {
t.Fatalf("Failed to load config: %s", err)
}
sites, err := NewHugoSitesFromConfiguration(DepsCfg{})
sites, err := NewHugoSitesFromConfiguration(depsCfg)
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@ -180,7 +189,7 @@ Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
t.Fatalf("Failed to build sites: %s", err)
}
assertFileContent(t, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
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",
@ -193,7 +202,7 @@ Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
"&laquo;Hi&raquo;", // angled quotes
)
assertFileContent(t, filepath.Join("public", "en", "sect1", "page1", "index.html"), true,
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",
@ -202,7 +211,7 @@ Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
"&ldquo;Hi&rdquo;",
)
assertFileContent(t, filepath.Join("public", "nn", "sect2", "page2", "index.html"), true,
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",
@ -244,7 +253,15 @@ func TestCaseInsensitiveConfigurationForAllTemplateEngines(t *testing.T) {
func doTestCaseInsensitiveConfigurationForTemplateEngine(t *testing.T, suffix string, templateFixer func(s string) string) {
testCommonResetState()
caseMixingTestsWriteCommonSources(t)
fs := hugofs.NewMem()
viper.SetFs(fs.Source)
caseMixingTestsWriteCommonSources(t, fs)
if err := LoadGlobalConfig("", "config.toml"); err != nil {
t.Fatalf("Failed to load config: %s", err)
}
t.Log("Testing", suffix)
@ -261,13 +278,9 @@ p
t.Log(templ)
writeSource(t, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
writeSource(t, fs, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
if err := LoadGlobalConfig("", "config.toml"); err != nil {
t.Fatalf("Failed to load config: %s", err)
}
sites, err := NewHugoSitesFromConfiguration(DepsCfg{})
sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@ -279,7 +292,7 @@ p
t.Fatalf("Failed to build sites: %s", err)
}
assertFileContent(t, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
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

@ -18,6 +18,7 @@ import (
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -30,7 +31,10 @@ func TestLoadGlobalConfig(t *testing.T) {
PaginatePath = "side"
`
writeSource(t, "hugo.toml", configContent)
fs := hugofs.NewMem()
viper.SetFs(fs.Source)
writeSource(t, fs, "hugo.toml", configContent)
require.NoError(t, LoadGlobalConfig("", "hugo.toml"))
assert.Equal(t, "side", helpers.Config().GetString("paginatePath"))

View File

@ -89,19 +89,16 @@ func TestDataDirUnknownFormat(t *testing.T) {
sources := []source.ByteSource{
{Name: filepath.FromSlash("test.roml"), Content: []byte("boo")},
}
s := NewSiteDefaultLang()
err := s.loadData([]source.Input{&source.InMemorySource{ByteSource: sources}})
if err != nil {
t.Fatalf("Should not return an error")
}
s, err := NewSiteDefaultLang()
require.NoError(t, err)
require.NoError(t, s.loadData([]source.Input{&source.InMemorySource{ByteSource: sources}}))
}
func doTestDataDir(t *testing.T, expected interface{}, sources []source.Input) {
s := NewSiteDefaultLang()
err := s.loadData(sources)
if err != nil {
t.Fatalf("Error loading data: %s", err)
}
s, err := NewSiteDefaultLang()
require.NoError(t, err)
require.NoError(t, s.loadData(sources))
if !reflect.DeepEqual(expected, s.Data) {
t.Errorf("Expected structure\n%#v got\n%#v", expected, s.Data)
}
@ -109,21 +106,22 @@ func doTestDataDir(t *testing.T, expected interface{}, sources []source.Input) {
func TestDataFromShortcode(t *testing.T) {
testCommonResetState()
writeSource(t, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
writeSource(t, "layouts/_default/single.html", `
cfg := newTestDepsConfig()
writeSource(t, cfg.Fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
writeSource(t, cfg.Fs, "layouts/_default/single.html", `
* Slogan from template: {{ .Site.Data.hugo.slogan }}
* {{ .Content }}`)
writeSource(t, "layouts/shortcodes/d.html", `{{ .Page.Site.Data.hugo.slogan }}`)
writeSource(t, "content/c.md", `---
writeSource(t, cfg.Fs, "layouts/shortcodes/d.html", `{{ .Page.Site.Data.hugo.slogan }}`)
writeSource(t, cfg.Fs, "content/c.md", `---
---
Slogan from shortcode: {{< d >}}
`)
h, err := newHugoSitesDefaultLanguage()
require.NoError(t, err)
require.NoError(t, h.Build(BuildCfg{}))
buildSingleSite(t, cfg, BuildCfg{})
content := readSource(t, "public/c/index.html")
content := readSource(t, cfg.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)

View File

@ -27,9 +27,11 @@ import (
"log"
"path/filepath"
"github.com/spf13/hugo/tpl"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/tplapi"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
@ -65,17 +67,17 @@ func doTestShortcodeCrossrefs(t *testing.T, relative bool) {
path := filepath.FromSlash("blog/post.md")
in := fmt.Sprintf(`{{< %s "%s" >}}`, refShortcode, path)
writeSource(t, "content/"+path, simplePageWithURL+": "+in)
fs := hugofs.NewMem()
writeSource(t, fs, "content/"+path, simplePageWithURL+": "+in)
expected := fmt.Sprintf(`%s/simple/url/`, expectedBase)
sites, err := newHugoSitesDefaultLanguage()
require.NoError(t, err)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
require.NoError(t, sites.Build(BuildCfg{}))
require.Len(t, sites.Sites[0].RegularPages, 1)
require.Len(t, s.RegularPages, 1)
output := string(sites.Sites[0].RegularPages[0].Content)
output := string(s.RegularPages[0].Content)
if !strings.Contains(output, expected) {
t.Errorf("Got\n%q\nExpected\n%q", output, expected)
@ -308,7 +310,7 @@ func TestShortcodeTweet(t *testing.T) {
},
}
p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error {
p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error {
templ.Funcs(tweetFuncMap)
return nil
})
@ -361,7 +363,7 @@ func TestShortcodeInstagram(t *testing.T) {
},
}
p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error {
p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error {
templ.Funcs(instagramFuncMap)
return nil
})

View File

@ -20,7 +20,6 @@ import (
"github.com/bep/gitmap"
"github.com/spf13/hugo/helpers"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
)
@ -36,7 +35,7 @@ func (h *HugoSites) assembleGitInfo() {
gitRepo, err := gitmap.Map(workingDir, "")
if err != nil {
jww.ERROR.Printf("Got error reading Git log: %s", err)
h.Log.ERROR.Printf("Got error reading Git log: %s", err)
return
}
@ -60,7 +59,7 @@ func (h *HugoSites) assembleGitInfo() {
filename := path.Join(filepath.ToSlash(contentRoot), contentDir, filepath.ToSlash(p.Path()))
g, ok := gitMap[filename]
if !ok {
jww.ERROR.Printf("Failed to find GitInfo for %q", filename)
h.Log.ERROR.Printf("Failed to find GitInfo for %q", filename)
return
}

View File

@ -65,7 +65,6 @@ type htmlHandler struct {
func (h htmlHandler) Extensions() []string { return []string{"html", "htm"} }
// TODO(bep) globals use p.s.t
func (h htmlHandler) PageConvert(p *Page) HandledResult {
if p.rendered {
panic(fmt.Sprintf("Page %q already rendered, does not need conversion", p.BaseFileName()))

View File

@ -17,44 +17,36 @@ import (
"path/filepath"
"testing"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/target"
"github.com/spf13/viper"
)
func TestDefaultHandler(t *testing.T) {
testCommonResetState()
hugofs.InitMemFs()
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>")},
{Name: filepath.FromSlash("sect/doc3.md"), Content: []byte("# doc3\n*some* content")},
{Name: filepath.FromSlash("sect/doc4.md"), Content: []byte("---\ntitle: doc4\n---\n# doc4\n*some content*")},
{Name: filepath.FromSlash("sect/doc3/img1.png"), Content: []byte("‰PNG  <20><><EFBFBD> IHDR<44><52><EFBFBD><01><><EFBFBD><08><><EFBFBD><EFBFBD>:~U<E280BA><55><EFBFBD> IDATWcø<0F><01>ZMoñ<6F><C3B1><EFBFBD><EFBFBD>IEND®B`")},
{Name: filepath.FromSlash("sect/img2.gif"), Content: []byte("GIF89a<01><01><EFBFBD><E282AC>ÿÿÿ<C3BF><C3BF><EFBFBD>,<2C><><EFBFBD><EFBFBD><01><01><>D<01>;")},
{Name: filepath.FromSlash("sect/img2.spf"), Content: []byte("****FAKE-FILETYPE****")},
{Name: filepath.FromSlash("doc7.html"), Content: []byte("<html><body>doc7 content</body></html>")},
{Name: filepath.FromSlash("sect/doc8.html"), Content: []byte("---\nmarkup: md\n---\n# title\nsome *content*")},
}
viper.Set("defaultExtension", "html")
viper.Set("verbose", true)
viper.Set("uglyURLs", true)
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: true, PublishDir: "public"}},
Language: helpers.NewLanguage("en"),
}
fs := hugofs.NewMem()
if err := buildAndRenderSite(s,
"_default/single.html", "{{.Content}}",
"head", "<head><script src=\"script.js\"></script></head>",
"head_abs", "<head><script src=\"/script.js\"></script></head>"); err != nil {
t.Fatalf("Failed to render site: %s", err)
}
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>")
writeSource(t, fs, filepath.FromSlash("content/sect/doc3.md"), "# doc3\n*some* content")
writeSource(t, fs, filepath.FromSlash("content/sect/doc4.md"), "---\ntitle: doc4\n---\n# doc4\n*some content*")
writeSource(t, fs, filepath.FromSlash("content/sect/doc3/img1.png"), "‰PNG  <20><><EFBFBD> IHDR<44><52><EFBFBD><01><><EFBFBD><08><><EFBFBD><EFBFBD>:~U<E280BA><55><EFBFBD> IDATWcø<0F><01>ZMoñ<6F><C3B1><EFBFBD><EFBFBD>IEND®B`")
writeSource(t, fs, filepath.FromSlash("content/sect/img2.gif"), "GIF89a<01><01><EFBFBD><E282AC>ÿÿÿ<C3BF><C3BF><EFBFBD>,<2C><><EFBFBD><EFBFBD><01><01><>D<01>;")
writeSource(t, fs, filepath.FromSlash("content/sect/img2.spf"), "****FAKE-FILETYPE****")
writeSource(t, fs, filepath.FromSlash("content/doc7.html"), "<html><body>doc7 content</body></html>")
writeSource(t, fs, filepath.FromSlash("content/sect/doc8.html"), "---\nmarkup: md\n---\n# title\nsome *content*")
writeSource(t, fs, filepath.FromSlash("layouts/_default/single.html"), "{{.Content}}")
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{})
tests := []struct {
doc string
@ -71,7 +63,7 @@ func TestDefaultHandler(t *testing.T) {
}
for _, test := range tests {
file, err := hugofs.Destination().Open(test.doc)
file, err := fs.Destination.Open(test.doc)
if err != nil {
t.Fatalf("Did not find %s in target.", test.doc)
}

View File

@ -14,20 +14,19 @@
package hugolib
import (
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"sync"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/viper"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/tpl"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/hugo/tplapi"
)
// HugoSites represents the sites to build. Each site represents a language.
@ -38,74 +37,77 @@ type HugoSites struct {
multilingual *Multilingual
*deps
}
// deps holds dependencies used by many.
// TODO(bep) globals a better name.
// There will be normally be only one instance of deps in play
// at a given time.
type deps struct {
// The logger to use.
log *jww.Notepad
tmpl *tpl.GoHTMLTemplate
// TODO(bep) next in line: Viper, hugofs
}
func (d *deps) refreshTemplates(withTemplate ...func(templ tpl.Template) error) {
d.tmpl = tpl.New(d.log, withTemplate...)
d.tmpl.PrintErrors() // TODO(bep) globals error handling
}
func newDeps(cfg DepsCfg) *deps {
logger := cfg.Logger
if logger == nil {
// TODO(bep) globals default log level
//logger = jww.NewNotepad(jww.LevelError, jww.LevelWarn, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
}
return &deps{
log: logger,
tmpl: tpl.New(logger, cfg.WithTemplate...),
}
*deps.Deps
}
// NewHugoSites creates a new collection of sites given the input sites, building
// a language configuration based on those.
func newHugoSites(cfg DepsCfg, sites ...*Site) (*HugoSites, error) {
func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
if cfg.Language != nil {
return nil, errors.New("Cannot provide Language in Cfg when sites are provided")
}
langConfig, err := newMultiLingualFromSites(sites...)
if err != nil {
return nil, err
}
var d *deps
if sites[0].deps != nil {
d = sites[0].deps
} else {
d = newDeps(cfg)
}
h := &HugoSites{
deps: d,
multilingual: langConfig,
Sites: sites}
for _, s := range sites {
s.owner = h
s.deps = h.deps
}
applyDepsIfNeeded(cfg, sites...)
h.Deps = sites[0].Deps
return h, nil
}
func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
if cfg.TemplateProvider == nil {
cfg.TemplateProvider = tpl.DefaultTemplateProvider
}
var (
d *deps.Deps
err error
)
for _, s := range sites {
if s.Deps != nil {
continue
}
if d == nil {
cfg.Language = s.Language
cfg.WithTemplate = s.withSiteTemplates(cfg.WithTemplate)
d = deps.New(cfg)
if err := d.LoadTemplates(); err != nil {
return err
}
} else {
d, err = d.ForLanguage(s.Language)
if err != nil {
return err
}
}
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 DepsCfg) (*HugoSites, error) {
func NewHugoSitesFromConfiguration(cfg deps.DepsCfg) (*HugoSites, error) {
sites, err := createSitesFromConfig(cfg)
if err != nil {
return nil, err
@ -113,17 +115,42 @@ func NewHugoSitesFromConfiguration(cfg DepsCfg) (*HugoSites, error) {
return newHugoSites(cfg, sites...)
}
func createSitesFromConfig(cfg DepsCfg) ([]*Site, error) {
deps := newDeps(cfg)
return createSitesFromDeps(deps)
func (s *Site) withSiteTemplates(withTemplates ...func(templ tplapi.Template) error) func(templ tplapi.Template) error {
return func(templ tplapi.Template) error {
templ.LoadTemplates(s.absLayoutDir())
if s.hasTheme() {
templ.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme")
}
for _, wt := range withTemplates {
if wt == nil {
continue
}
if err := wt(templ); err != nil {
return err
}
}
return nil
}
}
func createSitesFromDeps(deps *deps) ([]*Site, error) {
var sites []*Site
func createSitesFromConfig(cfg deps.DepsCfg) ([]*Site, error) {
var (
sites []*Site
)
multilingual := viper.GetStringMap("languages")
if len(multilingual) == 0 {
sites = append(sites, newSite(helpers.NewDefaultLanguage(), deps))
l := helpers.NewDefaultLanguage()
cfg.Language = l
s, err := newSite(cfg)
if err != nil {
return nil, err
}
sites = append(sites, s)
}
if len(multilingual) > 0 {
@ -136,9 +163,17 @@ func createSitesFromDeps(deps *deps) ([]*Site, error) {
}
for _, lang := range languages {
sites = append(sites, newSite(lang, deps))
}
var s *Site
var err error
cfg.Language = lang
s, err = newSite(cfg)
if err != nil {
return nil, err
}
sites = append(sites, s)
}
}
return sites, nil
@ -155,7 +190,8 @@ func (h *HugoSites) reset() {
func (h *HugoSites) createSitesFromConfig() error {
sites, err := createSitesFromDeps(h.deps)
depsCfg := deps.DepsCfg{Fs: h.Fs}
sites, err := createSitesFromConfig(depsCfg)
if err != nil {
return err
@ -173,6 +209,12 @@ func (h *HugoSites) createSitesFromConfig() error {
s.owner = h
}
if err := applyDepsIfNeeded(depsCfg, sites...); err != nil {
return err
}
h.Deps = sites[0].Deps
h.multilingual = langConfig
return nil
@ -199,24 +241,10 @@ type BuildCfg struct {
CreateSitesFromConfig bool
// Skip rendering. Useful for testing.
SkipRender bool
// Use this to add templates to use for rendering.
// Useful for testing.
withTemplate func(templ tpl.Template) error
// Use this to indicate what changed (for rebuilds).
whatChanged *whatChanged
}
// DepsCfg contains configuration options that can be used to configure Hugo
// on a global level, i.e. logging etc.
// Nil values will be given default values.
type DepsCfg struct {
// The Logger to use.
Logger *jww.Notepad
WithTemplate []func(templ tpl.Template) error
}
func (h *HugoSites) renderCrossSitesArtifacts() error {
if !h.multilingual.enabled() {
@ -293,7 +321,7 @@ func (h *HugoSites) createMissingPages() error {
foundTaxonomyTermsPage := false
for key := range tax {
if s.Info.preserveTaxonomyNames {
key = s.Info.pathSpec.MakePathSanitized(key)
key = s.PathSpec.MakePathSanitized(key)
}
for _, p := range taxonomyPages {
if p.sections[0] == plural && p.sections[1] == key {
@ -454,8 +482,8 @@ func (s *Site) preparePagesForRender(cfg *BuildCfg) {
}
var err error
if workContentCopy, err = handleShortcodes(p, s.owner.tmpl, workContentCopy); err != nil {
jww.ERROR.Printf("Failed to handle shortcodes for page %s: %s", p.BaseFileName(), err)
if workContentCopy, err = handleShortcodes(p, s.Tmpl, workContentCopy); err != nil {
s.Log.ERROR.Printf("Failed to handle shortcodes for page %s: %s", p.BaseFileName(), err)
}
if p.Markup != "html" {
@ -464,7 +492,7 @@ func (s *Site) preparePagesForRender(cfg *BuildCfg) {
summaryContent, err := p.setUserDefinedSummaryIfProvided(workContentCopy)
if err != nil {
jww.ERROR.Printf("Failed to set user defined summary for page %q: %s", p.Path(), err)
s.Log.ERROR.Printf("Failed to set user defined summary for page %q: %s", p.Path(), err)
} else if summaryContent != nil {
workContentCopy = summaryContent.content
}
@ -501,9 +529,9 @@ func (h *HugoSites) Pages() Pages {
return h.Sites[0].AllPages
}
func handleShortcodes(p *Page, t tpl.Template, rawContentCopy []byte) ([]byte, error) {
func handleShortcodes(p *Page, t tplapi.Template, rawContentCopy []byte) ([]byte, error) {
if len(p.contentShortCodes) > 0 {
jww.DEBUG.Printf("Replace %d shortcodes in %q", len(p.contentShortCodes), p.BaseFileName())
p.s.Log.DEBUG.Printf("Replace %d shortcodes in %q", len(p.contentShortCodes), p.BaseFileName())
shortcodes, err := executeShortcodeFuncMap(p.contentShortCodes)
if err != nil {
@ -513,7 +541,7 @@ func handleShortcodes(p *Page, t tpl.Template, rawContentCopy []byte) ([]byte, e
rawContentCopy, err = replaceShortcodeTokens(rawContentCopy, shortcodePlaceholderPrefix, shortcodes)
if err != nil {
jww.FATAL.Printf("Failed to replace shortcode tokens in %s:\n%s", p.BaseFileName(), err.Error())
p.s.Log.FATAL.Printf("Failed to replace shortcode tokens in %s:\n%s", p.BaseFileName(), err.Error())
}
}
@ -550,51 +578,15 @@ func (h *HugoSites) findAllPagesByKindNotIn(kind string) Pages {
return h.findPagesByKindNotIn(kind, h.Sites[0].AllPages)
}
// Convenience func used in tests to build a single site/language excluding render phase.
func buildSiteSkipRender(s *Site, additionalTemplates ...string) error {
return doBuildSite(s, false, additionalTemplates...)
}
// Convenience func used in tests to build a single site/language including render phase.
func buildAndRenderSite(s *Site, additionalTemplates ...string) error {
return doBuildSite(s, true, additionalTemplates...)
}
// Convenience func used in tests to build a single site/language.
func doBuildSite(s *Site, render bool, additionalTemplates ...string) error {
if s.PageCollections == nil {
s.PageCollections = newPageCollections()
}
sites, err := newHugoSites(DepsCfg{}, s)
if err != nil {
return err
}
addTemplates := func(templ tpl.Template) error {
for i := 0; i < len(additionalTemplates); i += 2 {
err := templ.AddTemplate(additionalTemplates[i], additionalTemplates[i+1])
if err != nil {
return err
}
}
return nil
}
config := BuildCfg{SkipRender: !render, withTemplate: addTemplates}
return sites.Build(config)
}
// Convenience func used in tests.
func newHugoSitesFromSourceAndLanguages(input []source.ByteSource, languages helpers.Languages) (*HugoSites, error) {
func newHugoSitesFromSourceAndLanguages(input []source.ByteSource, languages helpers.Languages, cfg deps.DepsCfg) (*HugoSites, error) {
if len(languages) == 0 {
panic("Must provide at least one language")
}
cfg := DepsCfg{}
first := &Site{
Source: &source.InMemorySource{ByteSource: input},
Language: languages[0],
Source: &source.InMemorySource{ByteSource: input},
}
if len(languages) == 1 {
return newHugoSites(cfg, first)
@ -611,6 +603,6 @@ func newHugoSitesFromSourceAndLanguages(input []source.ByteSource, languages hel
}
// Convenience func used in tests.
func newHugoSitesDefaultLanguage() (*HugoSites, error) {
return newHugoSitesFromSourceAndLanguages(nil, helpers.Languages{helpers.NewDefaultLanguage()})
func newHugoSitesDefaultLanguage(cfg deps.DepsCfg) (*HugoSites, error) {
return newHugoSitesFromSourceAndLanguages(nil, helpers.Languages{helpers.NewDefaultLanguage()}, cfg)
}

View File

@ -59,7 +59,7 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
}
if config.PrintStats {
h.log.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds()))
h.Log.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds()))
}
return nil

View File

@ -14,6 +14,7 @@ import (
"github.com/fortytw2/leaktest"
"github.com/fsnotify/fsnotify"
"github.com/spf13/afero"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
@ -25,6 +26,7 @@ import (
type testSiteConfig struct {
DefaultContentLanguage string
Fs *hugofs.Fs
}
func init() {
@ -32,22 +34,19 @@ func init() {
}
func testCommonResetState() {
hugofs.InitMemFs()
viper.Reset()
viper.SetFs(hugofs.Source())
// 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)
if err := hugofs.Source().Mkdir("content", 0755); err != nil {
panic("Content folder creation failed.")
}
}
func TestMultiSitesMainLangInRoot(t *testing.T) {
// 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) {
for _, b := range []bool{true, false} {
doTestMultiSitesMainLangInRoot(t, b)
@ -57,7 +56,8 @@ func TestMultiSitesMainLangInRoot(t *testing.T) {
func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) {
testCommonResetState()
viper.Set("defaultContentLanguageInSubdir", defaultInSubDir)
siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}
fs := hugofs.NewMem()
siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
@ -80,7 +80,8 @@ func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) {
require.Equal(t, "", frSite.Info.LanguagePrefix)
}
require.Equal(t, "/blog/en/foo", enSite.Info.pathSpec.RelURL("foo", true))
fmt.Println(">>>", enSite.PathSpec)
require.Equal(t, "/blog/en/foo", enSite.PathSpec.RelURL("foo", true))
doc1en := enSite.RegularPages[0]
doc1fr := frSite.RegularPages[0]
@ -96,64 +97,64 @@ func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) {
require.Equal(t, replaceDefaultContentLanguageValue("http://example.com/blog/fr/sect/doc1/", defaultInSubDir), frPerm)
require.Equal(t, replaceDefaultContentLanguageValue("/blog/fr/sect/doc1/", defaultInSubDir), frRelPerm)
assertFileContent(t, "public/fr/sect/doc1/index.html", defaultInSubDir, "Single", "Bonjour")
assertFileContent(t, "public/en/sect/doc1-slug/index.html", defaultInSubDir, "Single", "Hello")
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")
// Check home
if defaultInSubDir {
// should have a redirect on top level.
assertFileContent(t, "public/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`)
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, "public/fr/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`)
assertFileContent(t, fs, "public/fr/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`)
}
assertFileContent(t, "public/fr/index.html", defaultInSubDir, "Home", "Bonjour")
assertFileContent(t, "public/en/index.html", defaultInSubDir, "Home", "Hello")
assertFileContent(t, fs, "public/fr/index.html", defaultInSubDir, "Home", "Bonjour")
assertFileContent(t, fs, "public/en/index.html", defaultInSubDir, "Home", "Hello")
// Check list pages
assertFileContent(t, "public/fr/sect/index.html", defaultInSubDir, "List", "Bonjour")
assertFileContent(t, "public/en/sect/index.html", defaultInSubDir, "List", "Hello")
assertFileContent(t, "public/fr/plaques/frtag1/index.html", defaultInSubDir, "List", "Bonjour")
assertFileContent(t, "public/en/tags/tag1/index.html", defaultInSubDir, "List", "Hello")
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")
// 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, "public/sitemap.xml", true,
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, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/fr/</loc>")
assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/fr/</loc>")
} else {
assertFileContent(t, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/</loc>")
assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/</loc>")
}
assertFileContent(t, "public/en/sitemap.xml", true, "<loc>http://example.com/blog/en/</loc>")
assertFileContent(t, fs, "public/en/sitemap.xml", true, "<loc>http://example.com/blog/en/</loc>")
// Check rss
assertFileContent(t, "public/fr/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/index.xml"`)
assertFileContent(t, "public/en/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/index.xml"`)
assertFileContent(t, "public/fr/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/sect/index.xml"`)
assertFileContent(t, "public/en/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/sect/index.xml"`)
assertFileContent(t, "public/fr/plaques/frtag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/plaques/frtag1/index.xml"`)
assertFileContent(t, "public/en/tags/tag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`)
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"`)
// Check paginators
assertFileContent(t, "public/fr/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/"`)
assertFileContent(t, "public/en/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/"`)
assertFileContent(t, "public/fr/page/2/index.html", defaultInSubDir, "Home Page 2", "Bonjour", "http://example.com/blog/fr/")
assertFileContent(t, "public/en/page/2/index.html", defaultInSubDir, "Home Page 2", "Hello", "http://example.com/blog/en/")
assertFileContent(t, "public/fr/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/sect/"`)
assertFileContent(t, "public/en/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/sect/"`)
assertFileContent(t, "public/fr/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/sect/")
assertFileContent(t, "public/en/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/sect/")
assertFileContent(t, "public/fr/plaques/frtag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/plaques/frtag1/"`)
assertFileContent(t, "public/en/tags/tag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`)
assertFileContent(t, "public/fr/plaques/frtag1/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/plaques/frtag1/")
assertFileContent(t, "public/en/tags/tag1/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/")
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/")
// nn (Nynorsk) and nb (Bokmål) have custom pagePath: side ("page" in Norwegian)
assertFileContent(t, "public/nn/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nn/"`)
assertFileContent(t, "public/nb/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nb/"`)
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/"`)
}
func replaceDefaultContentLanguageValue(value string, defaultInSubDir bool) string {
@ -166,18 +167,18 @@ func replaceDefaultContentLanguageValue(value string, defaultInSubDir bool) stri
}
func assertFileContent(t *testing.T, filename string, defaultInSubDir bool, matches ...string) {
func assertFileContent(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
content := readDestination(t, filename)
content := readDestination(t, fs, filename)
for _, match := range matches {
match = 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, filename string, defaultInSubDir bool, matches ...string) {
func assertFileContentRegexp(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
content := readDestination(t, filename)
content := readDestination(t, fs, filename)
for _, match := range matches {
match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
r := regexp.MustCompile(match)
@ -190,7 +191,12 @@ func TestMultiSitesWithTwoLanguages(t *testing.T) {
viper.Set("defaultContentLanguage", "nn")
writeSource(t, "config.toml", `
fs := hugofs.NewMem()
depsCfg := deps.DepsCfg{Fs: fs}
viper.SetFs(depsCfg.Fs.Source)
writeSource(t, depsCfg.Fs, "config.toml", `
[languages]
[languages.nn]
languageName = "Nynorsk"
@ -208,15 +214,17 @@ weight = 2
t.Fatalf("Failed to load config: %s", err)
}
// Add some data
writeSource(t, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
sites, err := NewHugoSitesFromConfiguration(DepsCfg{})
sites, err := NewHugoSitesFromConfiguration(depsCfg)
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
}
writeSource(t, fs, filepath.Join("content", "foo.md"), "foo")
// Add some data
writeSource(t, fs, filepath.Join("data", "hugo.toml"), "slogan = \"Hugo Rocks!\"")
require.NoError(t, sites.Build(BuildCfg{}))
require.Len(t, sites.Sites, 2)
@ -245,7 +253,8 @@ func TestMultiSitesBuild(t *testing.T) {
func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
defer leaktest.Check(t)()
testCommonResetState()
siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}
fs := hugofs.NewMem()
siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
sites := createMultiTestSitesForConfig(t, siteConfig, configTemplate, configSuffix)
err := sites.Build(BuildCfg{})
@ -286,7 +295,7 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
assert.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, "public/superbob/index.html", true, "doc3|Hello|en")
assertFileContent(t, fs, "public/superbob/index.html", true, "doc3|Hello|en")
assert.Equal(t, doc2.Next, doc3, "doc3 should follow doc2, in .Next")
doc1fr := doc1en.Translations()[0]
@ -326,16 +335,16 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
}
// Check redirect to main language, French
languageRedirect := readDestination(t, "public/index.html")
languageRedirect := readDestination(t, fs, "public/index.html")
require.True(t, strings.Contains(languageRedirect, "0; url=http://example.com/blog/fr"), languageRedirect)
// check home page content (including data files rendering)
assertFileContent(t, "public/en/index.html", true, "Home Page 1", "Hello", "Hugo Rocks!")
assertFileContent(t, "public/fr/index.html", true, "Home Page 1", "Bonjour", "Hugo Rocks!")
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!")
// check single page content
assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
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")
// Check node translations
homeEn := enSite.getPage(KindHome)
@ -369,11 +378,11 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
require.Equal(t, "nb", taxTermNn.Translations()[0].Lang())
// Check sitemap(s)
sitemapIndex := readDestination(t, "public/sitemap.xml")
sitemapIndex := readDestination(t, fs, "public/sitemap.xml")
require.True(t, strings.Contains(sitemapIndex, "<loc>http://example.com/blog/en/sitemap.xml</loc>"), sitemapIndex)
require.True(t, strings.Contains(sitemapIndex, "<loc>http://example.com/blog/fr/sitemap.xml</loc>"), sitemapIndex)
sitemapEn := readDestination(t, "public/en/sitemap.xml")
sitemapFr := readDestination(t, "public/fr/sitemap.xml")
sitemapEn := readDestination(t, fs, "public/en/sitemap.xml")
sitemapFr := readDestination(t, fs, "public/fr/sitemap.xml")
require.True(t, strings.Contains(sitemapEn, "http://example.com/blog/en/sect/doc2/"), sitemapEn)
require.True(t, strings.Contains(sitemapFr, "http://example.com/blog/fr/sect/doc1/"), sitemapFr)
@ -384,8 +393,8 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
require.Len(t, frTags, 2, fmt.Sprintf("Tags in fr: %v", frTags))
require.NotNil(t, enTags["tag1"])
require.NotNil(t, frTags["frtag1"])
readDestination(t, "public/fr/plaques/frtag1/index.html")
readDestination(t, "public/en/tags/tag1/index.html")
readDestination(t, fs, "public/fr/plaques/frtag1/index.html")
readDestination(t, fs, "public/en/tags/tag1/index.html")
// Check Blackfriday config
assert.True(t, strings.Contains(string(doc1fr.Content), "&laquo;"), string(doc1fr.Content))
@ -409,7 +418,8 @@ func TestMultiSitesRebuild(t *testing.T) {
defer leaktest.Check(t)()
testCommonResetState()
siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}
fs := hugofs.NewMem()
siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
cfg := BuildCfg{Watching: true}
@ -419,7 +429,7 @@ func TestMultiSitesRebuild(t *testing.T) {
t.Fatalf("Failed to build sites: %s", err)
}
_, err = hugofs.Destination().Open("public/en/sect/doc2/index.html")
_, err = fs.Destination.Open("public/en/sect/doc2/index.html")
if err != nil {
t.Fatalf("Unable to locate file")
@ -432,12 +442,12 @@ func TestMultiSitesRebuild(t *testing.T) {
require.Len(t, frSite.RegularPages, 3)
// Verify translations
assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Hello")
assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Bonjour")
assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Hello")
assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Bonjour")
// check single page content
assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
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")
for i, this := range []struct {
preFunc func(t *testing.T)
@ -468,9 +478,9 @@ func TestMultiSitesRebuild(t *testing.T) {
},
{
func(t *testing.T) {
writeNewContentFile(t, "new_en_1", "2016-07-31", "content/new1.en.md", -5)
writeNewContentFile(t, "new_en_2", "1989-07-30", "content/new2.en.md", -10)
writeNewContentFile(t, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10)
writeNewContentFile(t, fs, "new_en_1", "2016-07-31", "content/new1.en.md", -5)
writeNewContentFile(t, fs, "new_en_2", "1989-07-30", "content/new2.en.md", -10)
writeNewContentFile(t, fs, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10)
},
[]fsnotify.Event{
{Name: "content/new1.en.md", Op: fsnotify.Create},
@ -485,21 +495,21 @@ func TestMultiSitesRebuild(t *testing.T) {
require.Equal(t, "new_en_2", enSite.RegularPages[0].Title)
require.Equal(t, "new_en_1", enSite.RegularPages[1].Title)
rendered := readDestination(t, "public/en/new1/index.html")
rendered := readDestination(t, fs, "public/en/new1/index.html")
require.True(t, strings.Contains(rendered, "new_en_1"), rendered)
},
},
{
func(t *testing.T) {
p := "content/sect/doc1.en.md"
doc1 := readSource(t, p)
doc1 := readSource(t, fs, p)
doc1 += "CHANGED"
writeSource(t, p, doc1)
writeSource(t, fs, p, doc1)
},
[]fsnotify.Event{{Name: "content/sect/doc1.en.md", Op: fsnotify.Write}},
func(t *testing.T) {
require.Len(t, enSite.RegularPages, 5)
doc1 := readDestination(t, "public/en/sect/doc1-slug/index.html")
doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html")
require.True(t, strings.Contains(doc1, "CHANGED"), doc1)
},
@ -507,7 +517,7 @@ func TestMultiSitesRebuild(t *testing.T) {
// Rename a file
{
func(t *testing.T) {
if err := hugofs.Source().Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil {
if err := fs.Source.Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil {
t.Fatalf("Rename failed: %s", err)
}
},
@ -518,23 +528,23 @@ func TestMultiSitesRebuild(t *testing.T) {
func(t *testing.T) {
require.Len(t, enSite.RegularPages, 5, "Rename")
require.Equal(t, "new_en_1", enSite.RegularPages[1].Title)
rendered := readDestination(t, "public/en/new1renamed/index.html")
rendered := readDestination(t, fs, "public/en/new1renamed/index.html")
require.True(t, strings.Contains(rendered, "new_en_1"), rendered)
}},
{
// Change a template
func(t *testing.T) {
template := "layouts/_default/single.html"
templateContent := readSource(t, template)
templateContent := readSource(t, fs, template)
templateContent += "{{ print \"Template Changed\"}}"
writeSource(t, template, templateContent)
writeSource(t, fs, template, templateContent)
},
[]fsnotify.Event{{Name: "layouts/_default/single.html", Op: fsnotify.Write}},
func(t *testing.T) {
require.Len(t, enSite.RegularPages, 5)
require.Len(t, enSite.AllPages, 30)
require.Len(t, frSite.RegularPages, 4)
doc1 := readDestination(t, "public/en/sect/doc1-slug/index.html")
doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html")
require.True(t, strings.Contains(doc1, "Template Changed"), doc1)
},
},
@ -542,18 +552,18 @@ func TestMultiSitesRebuild(t *testing.T) {
// Change a language file
func(t *testing.T) {
languageFile := "i18n/fr.yaml"
langContent := readSource(t, languageFile)
langContent := readSource(t, fs, languageFile)
langContent = strings.Replace(langContent, "Bonjour", "Salut", 1)
writeSource(t, languageFile, langContent)
writeSource(t, fs, languageFile, langContent)
},
[]fsnotify.Event{{Name: "i18n/fr.yaml", Op: fsnotify.Write}},
func(t *testing.T) {
require.Len(t, enSite.RegularPages, 5)
require.Len(t, enSite.AllPages, 30)
require.Len(t, frSite.RegularPages, 4)
docEn := readDestination(t, "public/en/sect/doc1-slug/index.html")
docEn := readDestination(t, fs, "public/en/sect/doc1-slug/index.html")
require.True(t, strings.Contains(docEn, "Hello"), "No Hello")
docFr := readDestination(t, "public/fr/sect/doc1/index.html")
docFr := readDestination(t, fs, "public/fr/sect/doc1/index.html")
require.True(t, strings.Contains(docFr, "Salut"), "No Salut")
homeEn := enSite.getPage(KindHome)
@ -566,7 +576,7 @@ func TestMultiSitesRebuild(t *testing.T) {
// Change a shortcode
{
func(t *testing.T) {
writeSource(t, "layouts/shortcodes/shortcode.html", "Modified Shortcode: {{ i18n \"hello\" }}")
writeSource(t, fs, "layouts/shortcodes/shortcode.html", "Modified Shortcode: {{ i18n \"hello\" }}")
},
[]fsnotify.Event{
{Name: "layouts/shortcodes/shortcode.html", Op: fsnotify.Write},
@ -575,8 +585,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, "public/fr/sect/doc1/index.html", true, "Single", "Modified Shortcode: Salut")
assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Single", "Modified Shortcode: Hello")
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")
},
},
} {
@ -615,13 +625,14 @@ func assertShouldNotBuild(t *testing.T, sites *HugoSites) {
filename = strings.Replace(filename, ".html", "/index.html", 1)
}
require.Equal(t, p.shouldBuild(), destinationExists(filename), filename)
require.Equal(t, p.shouldBuild(), destinationExists(sites.Fs, filename), filename)
}
}
func TestAddNewLanguage(t *testing.T) {
testCommonResetState()
siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}
fs := hugofs.NewMem()
siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
cfg := BuildCfg{}
@ -641,9 +652,9 @@ title = "Svenska"
newConfig = createConfig(t, siteConfig, newConfig)
writeNewContentFile(t, "Swedish Contentfile", "2016-01-01", "content/sect/doc1.sv.md", 10)
writeNewContentFile(t, fs, "Swedish Contentfile", "2016-01-01", "content/sect/doc1.sv.md", 10)
// replace the config
writeSource(t, "multilangconfig.toml", newConfig)
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())
@ -685,8 +696,8 @@ title = "Svenska"
func TestChangeDefaultLanguage(t *testing.T) {
testCommonResetState()
viper.Set("defaultContentLanguageInSubdir", false)
sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "fr"}, multiSiteTOMLConfigTemplate)
fs := hugofs.NewMem()
sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}, multiSiteTOMLConfigTemplate)
cfg := BuildCfg{}
err := sites.Build(cfg)
@ -695,13 +706,13 @@ func TestChangeDefaultLanguage(t *testing.T) {
t.Fatalf("Failed to build sites: %s", err)
}
assertFileContent(t, "public/sect/doc1/index.html", true, "Single", "Bonjour")
assertFileContent(t, "public/en/sect/doc2/index.html", true, "Single", "Hello")
assertFileContent(t, fs, "public/sect/doc1/index.html", true, "Single", "Bonjour")
assertFileContent(t, fs, "public/en/sect/doc2/index.html", true, "Single", "Hello")
newConfig := createConfig(t, testSiteConfig{DefaultContentLanguage: "en"}, multiSiteTOMLConfigTemplate)
// replace the config
writeSource(t, "multilangconfig.toml", newConfig)
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())
@ -712,18 +723,19 @@ func TestChangeDefaultLanguage(t *testing.T) {
}
// Default language is now en, so that should now be the "root" language
assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Bonjour")
assertFileContent(t, "public/sect/doc2/index.html", true, "Single", "Hello")
assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Bonjour")
assertFileContent(t, fs, "public/sect/doc2/index.html", true, "Single", "Hello")
}
func TestTableOfContentsInShortcodes(t *testing.T) {
testCommonResetState()
fs := hugofs.NewMem()
sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "en"}, multiSiteTOMLConfigTemplate)
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)
writeSource(t, "layouts/shortcodes/toc.html", tocShortcode)
writeSource(t, "content/post/simple.en.md", tocPageSimple)
writeSource(t, "content/post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings)
sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "en", Fs: fs}, multiSiteTOMLConfigTemplate)
cfg := BuildCfg{}
@ -733,8 +745,8 @@ func TestTableOfContentsInShortcodes(t *testing.T) {
t.Fatalf("Failed to build sites: %s", err)
}
assertFileContent(t, "public/en/post/simple/index.html", true, tocPageSimpleExpected)
assertFileContent(t, "public/en/post/withSCInHeading/index.html", true, tocPageWithShortcodesInHeadingsExpected)
assertFileContent(t, fs, "public/en/post/simple/index.html", true, tocPageSimpleExpected)
assertFileContent(t, fs, "public/en/post/withSCInHeading/index.html", true, tocPageWithShortcodesInHeadingsExpected)
}
var tocShortcode = `
@ -1014,24 +1026,25 @@ 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)
// Add some layouts
if err := afero.WriteFile(hugofs.Source(),
if err := afero.WriteFile(depsCfg.Fs.Source,
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(hugofs.Source(),
if err := afero.WriteFile(depsCfg.Fs.Source,
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(hugofs.Source(),
if err := afero.WriteFile(depsCfg.Fs.Source,
filepath.Join("layouts", "index.html"),
[]byte("{{ $p := .Paginator }}Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}"),
0755); err != nil {
@ -1039,7 +1052,7 @@ func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, conf
}
// Add a shortcode
if err := afero.WriteFile(hugofs.Source(),
if err := afero.WriteFile(depsCfg.Fs.Source,
filepath.Join("layouts", "shortcodes", "shortcode.html"),
[]byte("Shortcode: {{ i18n \"hello\" }}"),
0755); err != nil {
@ -1047,7 +1060,7 @@ func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, conf
}
// Add some language files
if err := afero.WriteFile(hugofs.Source(),
if err := afero.WriteFile(depsCfg.Fs.Source,
filepath.Join("i18n", "en.yaml"),
[]byte(`
- id: hello
@ -1056,7 +1069,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(hugofs.Source(),
if err := afero.WriteFile(depsCfg.Fs.Source,
filepath.Join("i18n", "fr.yaml"),
[]byte(`
- id: hello
@ -1210,7 +1223,10 @@ lag:
}
configFile := "multilangconfig." + configSuffix
writeSource(t, configFile, configContent)
writeSource(t, depsCfg.Fs, configFile, configContent)
viper.SetFs(depsCfg.Fs.Source)
if err := LoadGlobalConfig("", configFile); err != nil {
t.Fatalf("Failed to load config: %s", err)
}
@ -1218,15 +1234,15 @@ lag:
// 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(hugofs.Source(), filepath.Join("content", s.Name), s.Content, 0755); err != nil {
if err := afero.WriteFile(depsCfg.Fs.Source, filepath.Join("content", s.Name), s.Content, 0755); err != nil {
t.Fatalf("Failed to write file: %s", err)
}
}
// Add some data
writeSource(t, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
writeSource(t, depsCfg.Fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
sites, err := NewHugoSitesFromConfiguration(DepsCfg{})
sites, err := NewHugoSitesFromConfiguration(depsCfg)
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@ -1239,26 +1255,26 @@ lag:
return sites
}
func writeSource(t *testing.T, filename, content string) {
if err := afero.WriteFile(hugofs.Source(), filepath.FromSlash(filename), []byte(content), 0755); err != nil {
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 {
t.Fatalf("Failed to write file: %s", err)
}
}
func readDestination(t *testing.T, filename string) string {
return readFileFromFs(t, hugofs.Destination(), filename)
func readDestination(t *testing.T, fs *hugofs.Fs, filename string) string {
return readFileFromFs(t, fs.Destination, filename)
}
func destinationExists(filename string) bool {
b, err := helpers.Exists(filename, hugofs.Destination())
func destinationExists(fs *hugofs.Fs, filename string) bool {
b, err := helpers.Exists(filename, fs.Destination)
if err != nil {
panic(err)
}
return b
}
func readSource(t *testing.T, filename string) string {
return readFileFromFs(t, hugofs.Source(), filename)
func readSource(t *testing.T, fs *hugofs.Fs, filename string) string {
return readFileFromFs(t, fs.Source, filename)
}
func readFileFromFs(t *testing.T, fs afero.Fs, filename string) string {
@ -1291,9 +1307,9 @@ func newTestPage(title, date string, weight int) string {
return fmt.Sprintf(testPageTemplate, title, date, weight, title)
}
func writeNewContentFile(t *testing.T, title, date, filename string, weight int) {
func writeNewContentFile(t *testing.T, fs *hugofs.Fs, title, date, filename string, weight int) {
content := newTestPage(title, date, weight)
writeSource(t, filename, content)
writeSource(t, fs, filename, content)
}
func createConfig(t *testing.T, config testSiteConfig, configTemplate string) string {

View File

@ -19,11 +19,10 @@ import (
"github.com/nicksnyder/go-i18n/i18n/bundle"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/tpl"
jww "github.com/spf13/jwalterweatherman"
)
func loadI18n(sources []source.Input) error {
jww.DEBUG.Printf("Load I18n from %q", sources)
func (s *Site) loadI18n(sources []source.Input) error {
s.Log.DEBUG.Printf("Load I18n from %q", sources)
i18nBundle := bundle.New()

View File

@ -18,12 +18,14 @@ import (
"strings"
"testing"
"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/hugofs"
"github.com/spf13/hugo/source"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
@ -677,31 +679,29 @@ func setupTestMenuState(t *testing.T) {
}
func setupMenuTests(t *testing.T, pageSources []source.ByteSource) *Site {
s := createTestSite(pageSources)
setupTestMenuState(t)
testSiteSetup(s, t)
return s
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 {
hugofs.InitMemFs()
return &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: pageSources},
Language: helpers.NewDefaultLanguage(),
}
}
func testSiteSetup(s *Site, t *testing.T) {
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Sites build failed: %s", err)
}
}
func tomlToMap(s string) (map[string]interface{}, error) {
tree, err := toml.Load(s)

View File

@ -18,8 +18,11 @@ import (
"path/filepath"
"strings"
"testing"
"time"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
@ -56,24 +59,28 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) {
viper.Set("uglyURLs", ugly)
viper.Set("preserveTaxonomyNames", preserveTaxonomyNames)
writeLayoutsForNodeAsPageTests(t)
writeNodePagesForNodeAsPageTests("", t)
writeRegularPagesForNodeAsPageTests(t)
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks")
viper.Set("rssURI", "customrss.xml")
s := NewSiteDefaultLang()
depsCfg := newTestDepsConfig()
if err := buildAndRenderSite(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
viper.SetFs(depsCfg.Fs.Source)
writeLayoutsForNodeAsPageTests(t, depsCfg.Fs)
writeNodePagesForNodeAsPageTests(t, depsCfg.Fs, "")
writeRegularPagesForNodeAsPageTests(t, depsCfg.Fs)
sites, err := NewHugoSitesFromConfiguration(depsCfg)
require.NoError(t, err)
require.NoError(t, sites.Build(BuildCfg{}))
// date order: home, sect1, sect2, cat/hugo, cat/web, categories
assertFileContent(t, filepath.Join("public", "index.html"), false,
assertFileContent(t, depsCfg.Fs, filepath.Join("public", "index.html"), false,
"Index Title: Home Sweet Home!",
"Home <strong>Content!</strong>",
"# Pages: 4",
@ -82,10 +89,9 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) {
"GetPage: Section1 ",
)
assertFileContent(t, expectedFilePath(ugly, "public", "sect1", "regular1"), false, "Single Title: Page 01", "Content Page 01")
assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect1", "regular1"), false, "Single Title: Page 01", "Content Page 01")
h := s.owner
nodes := h.findAllPagesByKindNotIn(KindPage)
nodes := sites.findAllPagesByKindNotIn(KindPage)
require.Len(t, nodes, 7)
@ -99,7 +105,7 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) {
section2 := nodes[4]
require.Equal(t, "Section2", section2.Title)
pages := h.findAllPagesByKind(KindPage)
pages := sites.findAllPagesByKind(KindPage)
require.Len(t, pages, 4)
first := pages[0]
@ -109,46 +115,48 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) {
require.True(t, first.IsPage())
// Check Home paginator
assertFileContent(t, expectedFilePath(ugly, "public", "page", "2"), false,
assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "page", "2"), false,
"Pag: Page 02")
// Check Sections
assertFileContent(t, expectedFilePath(ugly, "public", "sect1"), false,
assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect1"), false,
"Section Title: Section", "Section1 <strong>Content!</strong>",
"Date: 2009-01-04",
"Lastmod: 2009-01-05",
)
assertFileContent(t, expectedFilePath(ugly, "public", "sect2"), false,
assertFileContent(t, depsCfg.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, expectedFilePath(ugly, "public", "sect1", "page", "2"), false,
assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect1", "page", "2"), false,
"Pag: Page 02")
sections := h.findAllPagesByKind(KindSection)
sections := sites.findAllPagesByKind(KindSection)
require.Len(t, sections, 2)
// Check taxonomy lists
assertFileContent(t, expectedFilePath(ugly, "public", "categories", "hugo"), false,
assertFileContent(t, depsCfg.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, expectedFilePath(ugly, "public", "categories", "hugo-rocks"), false,
assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "hugo-rocks"), false,
"Taxonomy Title: Taxonomy Hugo Rocks",
)
s := sites.Sites[0]
web := s.getPage(KindTaxonomy, "categories", "web")
require.NotNil(t, web)
require.Len(t, web.Data["Pages"].(Pages), 4)
assertFileContent(t, expectedFilePath(ugly, "public", "categories", "web"), false,
assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "web"), false,
"Taxonomy Title: Taxonomy Web",
"Taxonomy Web <strong>Content!</strong>",
"Date: 2009-01-10",
@ -156,12 +164,12 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) {
)
// Check taxonomy list paginator
assertFileContent(t, expectedFilePath(ugly, "public", "categories", "hugo", "page", "2"), false,
assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "hugo", "page", "2"), false,
"Taxonomy Title: Taxonomy Hugo",
"Pag: Page 02")
// Check taxonomy terms
assertFileContent(t, expectedFilePath(ugly, "public", "categories"), false,
assertFileContent(t, depsCfg.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",
@ -170,11 +178,11 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) {
// There are no pages to paginate over in the taxonomy terms.
// RSS
assertFileContent(t, filepath.Join("public", "customrss.xml"), false, "Recent content in Home Sweet Home! on Hugo Rocks", "<rss")
assertFileContent(t, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Section1 on Hugo Rocks", "<rss")
assertFileContent(t, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Section2 on Hugo Rocks", "<rss")
assertFileContent(t, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Taxonomy Hugo on Hugo Rocks", "<rss")
assertFileContent(t, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Taxonomy Web on Hugo Rocks", "<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")
}
@ -187,19 +195,23 @@ func TestNodesWithNoContentFile(t *testing.T) {
func doTestNodesWithNoContentFile(t *testing.T, ugly bool) {
testCommonResetState()
writeLayoutsForNodeAsPageTests(t)
writeRegularPagesForNodeAsPageTests(t)
viper.Set("uglyURLs", ugly)
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
viper.Set("rssURI", "customrss.xml")
s := NewSiteDefaultLang()
fs := hugofs.NewMem()
if err := buildAndRenderSite(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
require.NoError(t, err)
require.NoError(t, sites.Build(BuildCfg{}))
s := sites.Sites[0]
// Home page
homePages := s.findPagesByKind(KindHome)
@ -210,21 +222,21 @@ func doTestNodesWithNoContentFile(t *testing.T, ugly bool) {
require.Len(t, homePage.Pages, 4)
require.True(t, homePage.Path() == "")
assertFileContent(t, filepath.Join("public", "index.html"), false,
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, expectedFilePath(ugly, "public", "categories", "hugo"), false,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories", "hugo"), false,
"Taxonomy Title: Hugo",
"Date: 2010-06-12",
"Lastmod: 2010-06-13",
)
// Taxonomy terms
assertFileContent(t, expectedFilePath(ugly, "public", "categories"), false,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories"), false,
"Taxonomy Terms Title: Categories",
)
@ -232,9 +244,9 @@ func doTestNodesWithNoContentFile(t *testing.T, ugly bool) {
for _, p := range pages {
var want string
if ugly {
want = "/" + p.Site.pathSpec.URLize(p.Title) + ".html"
want = "/" + p.s.PathSpec.URLize(p.Title) + ".html"
} else {
want = "/" + p.Site.pathSpec.URLize(p.Title) + "/"
want = "/" + p.s.PathSpec.URLize(p.Title) + "/"
}
if p.URL() != want {
t.Errorf("Taxonomy term URL mismatch: want %q, got %q", want, p.URL())
@ -242,29 +254,29 @@ func doTestNodesWithNoContentFile(t *testing.T, ugly bool) {
}
// Sections
assertFileContent(t, expectedFilePath(ugly, "public", "sect1"), false,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect1"), false,
"Section Title: Sect1s",
"Date: 2010-06-12",
"Lastmod: 2010-06-13",
)
assertFileContent(t, expectedFilePath(ugly, "public", "sect2"), false,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect2"), false,
"Section Title: Sect2s",
"Date: 2008-07-06",
"Lastmod: 2008-07-09",
)
// RSS
assertFileContent(t, filepath.Join("public", "customrss.xml"), false, "Hugo Rocks!", "<rss")
assertFileContent(t, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Sect1s on Hugo Rocks!", "<rss")
assertFileContent(t, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Sect2s on Hugo Rocks!", "<rss")
assertFileContent(t, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Hugo on Hugo Rocks!", "<rss")
assertFileContent(t, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Web on Hugo Rocks!", "<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")
}
func TestNodesAsPageMultilingual(t *testing.T) {
for _, ugly := range []bool{true, false} {
for _, ugly := range []bool{false, true} {
doTestNodesAsPageMultilingual(t, ugly)
}
}
@ -273,11 +285,13 @@ func doTestNodesAsPageMultilingual(t *testing.T, ugly bool) {
testCommonResetState()
fs := hugofs.NewMem()
viper.Set("uglyURLs", ugly)
writeLayoutsForNodeAsPageTests(t)
viper.SetFs(fs.Source)
writeSource(t, "config.toml",
writeSource(t, fs, "config.toml",
`
paginage = 1
title = "Hugo Multilingual Rocks!"
@ -303,19 +317,17 @@ weight = 3
title = "Deutsche Hugo"
`)
for _, lang := range []string{"nn", "en"} {
writeRegularPagesForNodeAsPageTestsWithLang(t, lang)
}
writeLayoutsForNodeAsPageTests(t, fs)
// Only write node pages for the English and Deutsch
writeNodePagesForNodeAsPageTests("en", t)
writeNodePagesForNodeAsPageTests("de", t)
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)
}
sites, err := NewHugoSitesFromConfiguration(DepsCfg{})
sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
@ -325,6 +337,10 @@ title = "Deutsche Hugo"
t.Fatalf("Got %d sites", len(sites.Sites))
}
// Only write node pages for the English and Deutsch
writeNodePagesForNodeAsPageTests(t, fs, "en")
writeNodePagesForNodeAsPageTests(t, fs, "de")
err = sites.Build(BuildCfg{})
if err != nil {
@ -356,92 +372,99 @@ title = "Deutsche Hugo"
require.Equal(t, expetedPermalink(ugly, "/en/sect1/"), enSect.Permalink())
assertFileContent(t, filepath.Join("public", "nn", "index.html"), true,
assertFileContent(t, fs, filepath.Join("public", "nn", "index.html"), true,
"Index Title: Hugo på norsk")
assertFileContent(t, filepath.Join("public", "en", "index.html"), true,
assertFileContent(t, fs, filepath.Join("public", "en", "index.html"), true,
"Index Title: Home Sweet Home!", "<strong>Content!</strong>")
assertFileContent(t, filepath.Join("public", "de", "index.html"), true,
assertFileContent(t, fs, filepath.Join("public", "de", "index.html"), true,
"Index Title: Home Sweet Home!", "<strong>Content!</strong>")
// Taxonomy list
assertFileContent(t, expectedFilePath(ugly, "public", "nn", "categories", "hugo"), true,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories", "hugo"), true,
"Taxonomy Title: Hugo")
assertFileContent(t, expectedFilePath(ugly, "public", "en", "categories", "hugo"), true,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories", "hugo"), true,
"Taxonomy Title: Taxonomy Hugo")
// Taxonomy terms
assertFileContent(t, expectedFilePath(ugly, "public", "nn", "categories"), true,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories"), true,
"Taxonomy Terms Title: Categories")
assertFileContent(t, expectedFilePath(ugly, "public", "en", "categories"), true,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories"), true,
"Taxonomy Terms Title: Taxonomy Term Categories")
// Sections
assertFileContent(t, expectedFilePath(ugly, "public", "nn", "sect1"), true,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1"), true,
"Section Title: Sect1s")
assertFileContent(t, expectedFilePath(ugly, "public", "nn", "sect2"), true,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect2"), true,
"Section Title: Sect2s")
assertFileContent(t, expectedFilePath(ugly, "public", "en", "sect1"), true,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1"), true,
"Section Title: Section1")
assertFileContent(t, expectedFilePath(ugly, "public", "en", "sect2"), true,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect2"), true,
"Section Title: Section2")
// Regular pages
assertFileContent(t, expectedFilePath(ugly, "public", "en", "sect1", "regular1"), true,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1", "regular1"), true,
"Single Title: Page 01")
assertFileContent(t, expectedFilePath(ugly, "public", "nn", "sect1", "regular2"), true,
assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1", "regular2"), true,
"Single Title: Page 02")
// RSS
assertFileContent(t, filepath.Join("public", "nn", "customrss.xml"), true, "Hugo på norsk", "<rss")
assertFileContent(t, filepath.Join("public", "nn", "sect1", "customrss.xml"), true, "Recent content in Sect1s on Hugo på norsk", "<rss")
assertFileContent(t, filepath.Join("public", "nn", "sect2", "customrss.xml"), true, "Recent content in Sect2s on Hugo på norsk", "<rss")
assertFileContent(t, filepath.Join("public", "nn", "categories", "hugo", "customrss.xml"), true, "Recent content in Hugo on Hugo på norsk", "<rss")
assertFileContent(t, filepath.Join("public", "nn", "categories", "web", "customrss.xml"), true, "Recent content in Web on Hugo på norsk", "<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")
assertFileContent(t, filepath.Join("public", "en", "customrss.xml"), true, "Recent content in Home Sweet Home! on Hugo in English", "<rss")
assertFileContent(t, filepath.Join("public", "en", "sect1", "customrss.xml"), true, "Recent content in Section1 on Hugo in English", "<rss")
assertFileContent(t, filepath.Join("public", "en", "sect2", "customrss.xml"), true, "Recent content in Section2 on Hugo in English", "<rss")
assertFileContent(t, filepath.Join("public", "en", "categories", "hugo", "customrss.xml"), true, "Recent content in Taxonomy Hugo on Hugo in English", "<rss")
assertFileContent(t, filepath.Join("public", "en", "categories", "web", "customrss.xml"), true, "Recent content in Taxonomy Web on Hugo in English", "<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")
}
func TestNodesWithTaxonomies(t *testing.T) {
testCommonResetState()
writeLayoutsForNodeAsPageTests(t)
writeRegularPagesForNodeAsPageTests(t)
fs := hugofs.NewMem()
writeSource(t, filepath.Join("content", "_index.md"), `---
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
writeSource(t, fs, filepath.Join("content", "_index.md"), `---
title: Home With Taxonomies
categories: [
"Hugo",
"Hugo",
"Home"
]
---
`)
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
s := NewSiteDefaultLang()
require.NoError(t, err)
if err := buildAndRenderSite(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
require.NoError(t, h.Build(BuildCfg{}))
assertFileContent(t, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy Title: Hugo", "# Pages: 5")
assertFileContent(t, filepath.Join("public", "categories", "home", "index.html"), true, "Taxonomy Title: Home", "# Pages: 1")
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")
}
func TestNodesWithMenu(t *testing.T) {
testCommonResetState()
writeLayoutsForNodeAsPageTests(t)
writeRegularPagesForNodeAsPageTests(t)
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
writeSource(t, filepath.Join("content", "_index.md"), `---
fs := hugofs.NewMem()
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
writeSource(t, fs, filepath.Join("content", "_index.md"), `---
title: Home With Menu
menu:
mymenu:
@ -449,7 +472,7 @@ menu:
---
`)
writeSource(t, filepath.Join("content", "sect1", "_index.md"), `---
writeSource(t, fs, filepath.Join("content", "sect1", "_index.md"), `---
title: Sect1 With Menu
menu:
mymenu:
@ -457,7 +480,7 @@ menu:
---
`)
writeSource(t, filepath.Join("content", "categories", "hugo", "_index.md"), `---
writeSource(t, fs, filepath.Join("content", "categories", "hugo", "_index.md"), `---
title: Taxonomy With Menu
menu:
mymenu:
@ -465,98 +488,102 @@ menu:
---
`)
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
s := NewSiteDefaultLang()
require.NoError(t, err)
if err := buildAndRenderSite(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
require.NoError(t, h.Build(BuildCfg{}))
assertFileContent(t, filepath.Join("public", "index.html"), true, "Home With Menu", "Home Menu Item: Go Home!: /")
assertFileContent(t, filepath.Join("public", "sect1", "index.html"), true, "Sect1 With Menu", "Section Menu Item: Go Sect1!: /sect1/")
assertFileContent(t, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy With Menu", "Taxonomy Menu Item: Go Tax Hugo!: /categories/hugo/")
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/")
}
func TestNodesWithAlias(t *testing.T) {
testCommonResetState()
writeLayoutsForNodeAsPageTests(t)
writeRegularPagesForNodeAsPageTests(t)
fs := hugofs.NewMem()
writeSource(t, filepath.Join("content", "_index.md"), `---
viper.Set("paginate", 1)
viper.Set("baseURL", "http://base/")
viper.Set("title", "Hugo Rocks!")
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
writeSource(t, fs, filepath.Join("content", "_index.md"), `---
title: Home With Alias
aliases:
- /my/new/home.html
---
`)
viper.Set("paginate", 1)
viper.Set("baseURL", "http://base/")
viper.Set("title", "Hugo Rocks!")
h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
s := NewSiteDefaultLang()
require.NoError(t, err)
if err := buildAndRenderSite(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
require.NoError(t, h.Build(BuildCfg{}))
assertFileContent(t, filepath.Join("public", "index.html"), true, "Home With Alias")
assertFileContent(t, filepath.Join("public", "my", "new", "home.html"), true, "content=\"0; url=http://base/")
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/")
}
func TestNodesWithSectionWithIndexPageOnly(t *testing.T) {
testCommonResetState()
writeLayoutsForNodeAsPageTests(t)
fs := hugofs.NewMem()
writeSource(t, filepath.Join("content", "sect", "_index.md"), `---
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
writeLayoutsForNodeAsPageTests(t, fs)
writeSource(t, fs, filepath.Join("content", "sect", "_index.md"), `---
title: MySection
---
My Section Content
`)
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
s := NewSiteDefaultLang()
require.NoError(t, err)
if err := buildAndRenderSite(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
require.NoError(t, h.Build(BuildCfg{}))
assertFileContent(t, filepath.Join("public", "sect", "index.html"), true, "My Section")
assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
}
func TestNodesWithURLs(t *testing.T) {
testCommonResetState()
writeLayoutsForNodeAsPageTests(t)
fs := hugofs.NewMem()
writeRegularPagesForNodeAsPageTests(t)
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
viper.Set("baseURL", "http://bep.is/base/")
writeSource(t, filepath.Join("content", "sect", "_index.md"), `---
writeLayoutsForNodeAsPageTests(t, fs)
writeRegularPagesForNodeAsPageTests(t, fs)
writeSource(t, fs, filepath.Join("content", "sect", "_index.md"), `---
title: MySection
url: foo.html
---
My Section Content
`)
viper.Set("paginate", 1)
viper.Set("title", "Hugo Rocks!")
viper.Set("baseURL", "http://bep.is/base/")
h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
s := NewSiteDefaultLang()
require.NoError(t, err)
if err := buildAndRenderSite(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
require.NoError(t, h.Build(BuildCfg{}))
assertFileContent(t, filepath.Join("public", "sect", "index.html"), true, "My Section")
assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
s := h.Sites[0]
p := s.RegularPages[0]
@ -573,11 +600,11 @@ My Section Content
}
func writeRegularPagesForNodeAsPageTests(t *testing.T) {
writeRegularPagesForNodeAsPageTestsWithLang(t, "")
func writeRegularPagesForNodeAsPageTests(t *testing.T, fs *hugofs.Fs) {
writeRegularPagesForNodeAsPageTestsWithLang(t, fs, "")
}
func writeRegularPagesForNodeAsPageTestsWithLang(t *testing.T, lang string) {
func writeRegularPagesForNodeAsPageTestsWithLang(t *testing.T, fs *hugofs.Fs, lang string) {
var langStr string
if lang != "" {
@ -597,7 +624,7 @@ func writeRegularPagesForNodeAsPageTestsWithLang(t *testing.T, lang string) {
}
date = date.Add(-24 * time.Duration(i) * time.Hour)
writeSource(t, filepath.Join("content", sect, fmt.Sprintf("regular%d.%smd", i, langStr)), fmt.Sprintf(`---
writeSource(t, fs, filepath.Join("content", sect, fmt.Sprintf("regular%d.%smd", i, langStr)), fmt.Sprintf(`---
title: Page %02d
lastMod : %q
date : %q
@ -612,7 +639,7 @@ Content Page %02d
}
}
func writeNodePagesForNodeAsPageTests(lang string, t *testing.T) {
func writeNodePagesForNodeAsPageTests(t *testing.T, fs *hugofs.Fs, lang string) {
filename := "_index.md"
@ -624,7 +651,7 @@ func writeNodePagesForNodeAsPageTests(lang string, t *testing.T) {
date, _ := time.Parse(format, "2009-01-01")
writeSource(t, filepath.Join("content", filename), fmt.Sprintf(`---
writeSource(t, fs, filepath.Join("content", filename), fmt.Sprintf(`---
title: Home Sweet Home!
date : %q
lastMod : %q
@ -632,14 +659,14 @@ lastMod : %q
l-%s Home **Content!**
`, date.Add(1*24*time.Hour).Format(time.RFC822), date.Add(2*24*time.Hour).Format(time.RFC822), lang))
writeSource(t, filepath.Join("content", "sect1", filename), fmt.Sprintf(`---
writeSource(t, fs, filepath.Join("content", "sect1", filename), fmt.Sprintf(`---
title: Section1
date : %q
lastMod : %q
---
Section1 **Content!**
`, date.Add(3*24*time.Hour).Format(time.RFC822), date.Add(4*24*time.Hour).Format(time.RFC822)))
writeSource(t, filepath.Join("content", "sect2", filename), fmt.Sprintf(`---
writeSource(t, fs, filepath.Join("content", "sect2", filename), fmt.Sprintf(`---
title: Section2
date : %q
lastMod : %q
@ -647,7 +674,7 @@ lastMod : %q
Section2 **Content!**
`, date.Add(5*24*time.Hour).Format(time.RFC822), date.Add(6*24*time.Hour).Format(time.RFC822)))
writeSource(t, filepath.Join("content", "categories", "hugo", filename), fmt.Sprintf(`---
writeSource(t, fs, filepath.Join("content", "categories", "hugo", filename), fmt.Sprintf(`---
title: Taxonomy Hugo
date : %q
lastMod : %q
@ -655,7 +682,7 @@ lastMod : %q
Taxonomy Hugo **Content!**
`, date.Add(7*24*time.Hour).Format(time.RFC822), date.Add(8*24*time.Hour).Format(time.RFC822)))
writeSource(t, filepath.Join("content", "categories", "web", filename), fmt.Sprintf(`---
writeSource(t, fs, filepath.Join("content", "categories", "web", filename), fmt.Sprintf(`---
title: Taxonomy Web
date : %q
lastMod : %q
@ -663,7 +690,7 @@ lastMod : %q
Taxonomy Web **Content!**
`, date.Add(9*24*time.Hour).Format(time.RFC822), date.Add(10*24*time.Hour).Format(time.RFC822)))
writeSource(t, filepath.Join("content", "categories", "hugo-rocks", filename), fmt.Sprintf(`---
writeSource(t, fs, filepath.Join("content", "categories", "hugo-rocks", filename), fmt.Sprintf(`---
title: Taxonomy Hugo Rocks
date : %q
lastMod : %q
@ -671,7 +698,7 @@ lastMod : %q
Taxonomy Hugo Rocks **Content!**
`, date.Add(11*24*time.Hour).Format(time.RFC822), date.Add(12*24*time.Hour).Format(time.RFC822)))
writeSource(t, filepath.Join("content", "categories", filename), fmt.Sprintf(`---
writeSource(t, fs, filepath.Join("content", "categories", filename), fmt.Sprintf(`---
title: Taxonomy Term Categories
date : %q
lastMod : %q
@ -681,8 +708,8 @@ Taxonomy Term Categories **Content!**
}
func writeLayoutsForNodeAsPageTests(t *testing.T) {
writeSource(t, filepath.Join("layouts", "index.html"), `
func writeLayoutsForNodeAsPageTests(t *testing.T, fs *hugofs.Fs) {
writeSource(t, fs, filepath.Join("layouts", "index.html"), `
Index Title: {{ .Title }}
Index Content: {{ .Content }}
# Pages: {{ len .Data.Pages }}
@ -699,14 +726,14 @@ Lastmod: {{ .Lastmod.Format "2006-01-02" }}
GetPage: {{ with .Site.GetPage "section" "sect1" }}{{ .Title }}{{ end }}
`)
writeSource(t, filepath.Join("layouts", "_default", "single.html"), `
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `
Single Title: {{ .Title }}
Single Content: {{ .Content }}
Date: {{ .Date.Format "2006-01-02" }}
Lastmod: {{ .Lastmod.Format "2006-01-02" }}
`)
writeSource(t, filepath.Join("layouts", "_default", "section.html"), `
writeSource(t, fs, filepath.Join("layouts", "_default", "section.html"), `
Section Title: {{ .Title }}
Section Content: {{ .Content }}
# Pages: {{ len .Data.Pages }}
@ -723,7 +750,7 @@ Lastmod: {{ .Lastmod.Format "2006-01-02" }}
`)
// Taxonomy lists
writeSource(t, filepath.Join("layouts", "_default", "taxonomy.html"), `
writeSource(t, fs, filepath.Join("layouts", "_default", "taxonomy.html"), `
Taxonomy Title: {{ .Title }}
Taxonomy Content: {{ .Content }}
# Pages: {{ len .Data.Pages }}
@ -740,7 +767,7 @@ Lastmod: {{ .Lastmod.Format "2006-01-02" }}
`)
// Taxonomy terms
writeSource(t, filepath.Join("layouts", "_default", "terms.html"), `
writeSource(t, fs, filepath.Join("layouts", "_default", "terms.html"), `
Taxonomy Terms Title: {{ .Title }}
Taxonomy Terms Content: {{ .Content }}
{{ range $key, $value := .Data.Terms }}

View File

@ -38,7 +38,6 @@ import (
"github.com/spf13/cast"
bp "github.com/spf13/hugo/bufferpool"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
"github.com/spf13/viper"
)
@ -536,7 +535,7 @@ func (p *Page) getRenderingConfig() *helpers.Blackfriday {
p.renderingConfig = helpers.NewBlackfriday(p.Language())
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())
p.s.Log.FATAL.Printf("Failed to get rendering config for %s:\n%s", p.BaseFileName(), err.Error())
}
})
@ -556,7 +555,7 @@ func (s *Site) newPage(filename string) *Page {
sections: sectionsFromFilename(filename),
}
s.log.DEBUG.Println("Reading from", page.File.Path())
s.Log.DEBUG.Println("Reading from", page.File.Path())
return &page
}
@ -683,7 +682,7 @@ func (s *Site) NewPage(name string) (*Page, error) {
func (p *Page) ReadFrom(buf io.Reader) (int64, error) {
// Parse for metadata & body
if err := p.parse(buf); err != nil {
p.s.log.ERROR.Print(err)
p.s.Log.ERROR.Print(err)
return 0, err
}
@ -738,7 +737,7 @@ func (p *Page) getPermalink() *url.URL {
p.pageURLInit.Do(func() {
u, err := p.createPermalink()
if err != nil {
p.s.log.ERROR.Printf("Failed to create permalink for page %q: %s", p.FullFilePath(), err)
p.s.Log.ERROR.Printf("Failed to create permalink for page %q: %s", p.FullFilePath(), err)
p.permalink = new(url.URL)
return
}
@ -759,16 +758,16 @@ func (p *Page) createPermalink() (*url.URL, error) {
if p.IsNode() {
// No permalink config for nodes (currently)
pURL := strings.TrimSpace(p.Site.pathSpec.URLize(p.URLPath.URL))
pURL := strings.TrimSpace(p.s.PathSpec.URLize(p.URLPath.URL))
pURL = p.addLangPathPrefix(pURL)
pURL = p.Site.pathSpec.URLPrep(pURL)
pURL = p.s.PathSpec.URLPrep(pURL)
url := helpers.MakePermalink(baseURL, pURL)
return url, nil
}
dir := strings.TrimSpace(p.Site.pathSpec.MakePath(filepath.ToSlash(strings.ToLower(p.Source.Dir()))))
pSlug := strings.TrimSpace(p.Site.pathSpec.URLize(p.Slug))
pURL := strings.TrimSpace(p.Site.pathSpec.URLize(p.URLPath.URL))
dir := strings.TrimSpace(p.s.PathSpec.MakePath(filepath.ToSlash(strings.ToLower(p.Source.Dir()))))
pSlug := strings.TrimSpace(p.s.PathSpec.URLize(p.Slug))
pURL := strings.TrimSpace(p.s.PathSpec.URLize(p.URLPath.URL))
var permalink string
var err error
@ -784,10 +783,10 @@ func (p *Page) createPermalink() (*url.URL, error) {
}
} else {
if len(pSlug) > 0 {
permalink = p.Site.pathSpec.URLPrep(path.Join(dir, p.Slug+"."+p.Extension()))
permalink = p.s.PathSpec.URLPrep(path.Join(dir, p.Slug+"."+p.Extension()))
} else {
t := p.Source.TranslationBaseName()
permalink = p.Site.pathSpec.URLPrep(path.Join(dir, (strings.TrimSpace(t) + "." + p.Extension())))
permalink = p.s.PathSpec.URLPrep(path.Join(dir, (strings.TrimSpace(t) + "." + p.Extension())))
}
}
@ -953,22 +952,22 @@ func (p *Page) update(f interface{}) error {
case "date":
p.Date, err = cast.ToTimeE(v)
if err != nil {
p.s.log.ERROR.Printf("Failed to parse date '%v' in page %s", v, p.File.Path())
p.s.Log.ERROR.Printf("Failed to parse date '%v' in page %s", v, p.File.Path())
}
case "lastmod":
p.Lastmod, err = cast.ToTimeE(v)
if err != nil {
p.s.log.ERROR.Printf("Failed to parse lastmod '%v' in page %s", v, p.File.Path())
p.s.Log.ERROR.Printf("Failed to parse lastmod '%v' in page %s", v, p.File.Path())
}
case "publishdate", "pubdate":
p.PublishDate, err = cast.ToTimeE(v)
if err != nil {
p.s.log.ERROR.Printf("Failed to parse publishdate '%v' in page %s", v, p.File.Path())
p.s.Log.ERROR.Printf("Failed to parse publishdate '%v' in page %s", v, p.File.Path())
}
case "expirydate", "unpublishdate":
p.ExpiryDate, err = cast.ToTimeE(v)
if err != nil {
p.s.log.ERROR.Printf("Failed to parse expirydate '%v' in page %s", v, p.File.Path())
p.s.Log.ERROR.Printf("Failed to parse expirydate '%v' in page %s", v, p.File.Path())
}
case "draft":
draft = new(bool)
@ -1040,7 +1039,7 @@ func (p *Page) update(f interface{}) error {
if draft != nil && published != nil {
p.Draft = *draft
p.s.log.ERROR.Printf("page %s has both draft and published settings in its frontmatter. Using draft.", p.File.Path())
p.s.Log.ERROR.Printf("page %s has both draft and published settings in its frontmatter. Using draft.", p.File.Path())
return ErrHasDraftAndPublished
} else if draft != nil {
p.Draft = *draft
@ -1049,7 +1048,7 @@ func (p *Page) update(f interface{}) error {
}
if p.Date.IsZero() && viper.GetBool("useModTimeAsFallback") {
fi, err := hugofs.Source().Stat(filepath.Join(helpers.AbsPathify(viper.GetString("contentDir")), p.File.Path()))
fi, err := p.s.Fs.Source.Stat(filepath.Join(helpers.AbsPathify(viper.GetString("contentDir")), p.File.Path()))
if err == nil {
p.Date = fi.ModTime()
}
@ -1109,7 +1108,7 @@ func (p *Page) getParam(key string, stringToLower bool) interface{} {
return v
}
p.s.log.ERROR.Printf("GetParam(\"%s\"): Unknown type %s\n", key, reflect.TypeOf(v))
p.s.Log.ERROR.Printf("GetParam(\"%s\"): Unknown type %s\n", key, reflect.TypeOf(v))
return nil
}
@ -1251,16 +1250,16 @@ func (p *Page) Menus() PageMenus {
menus, err := cast.ToStringMapE(ms)
if err != nil {
p.s.log.ERROR.Printf("unable to process menus for %q\n", p.Title)
p.s.Log.ERROR.Printf("unable to process menus for %q\n", p.Title)
}
for name, menu := range menus {
menuEntry := MenuEntry{Name: p.LinkTitle(), URL: link, Weight: p.Weight, Menu: name}
if menu != nil {
p.s.log.DEBUG.Printf("found menu: %q, in %q\n", name, p.Title)
p.s.Log.DEBUG.Printf("found menu: %q, in %q\n", name, p.Title)
ime, err := cast.ToStringMapE(menu)
if err != nil {
p.s.log.ERROR.Printf("unable to process menus for %q: %s", p.Title, err)
p.s.Log.ERROR.Printf("unable to process menus for %q: %s", p.Title, err)
}
menuEntry.marshallMap(ime)
@ -1283,7 +1282,7 @@ func (p *Page) Render(layout ...string) template.HTML {
l = p.layouts()
}
return p.s.tmpl.ExecuteTemplateToHTML(p, l...)
return p.s.Tmpl.ExecuteTemplateToHTML(p, l...)
}
func (p *Page) determineMarkupType() string {
@ -1311,8 +1310,8 @@ func (p *Page) parse(reader io.Reader) error {
meta, err := psr.Metadata()
if meta != nil {
if err != nil {
p.s.log.ERROR.Printf("Error parsing page meta data for %s", p.File.Path())
p.s.log.ERROR.Println(err)
p.s.Log.ERROR.Printf("Error parsing page meta data for %s", p.File.Path())
p.s.Log.ERROR.Println(err)
return err
}
if err = p.update(meta); err != nil {
@ -1381,12 +1380,12 @@ func (p *Page) saveSource(by []byte, inpath string, safe bool) (err error) {
if !filepath.IsAbs(inpath) {
inpath = helpers.AbsPathify(inpath)
}
p.s.log.INFO.Println("creating", inpath)
p.s.Log.INFO.Println("creating", inpath)
if safe {
err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), hugofs.Source())
err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source)
} else {
err = helpers.WriteToDisk(inpath, bytes.NewReader(by), hugofs.Source())
err = helpers.WriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source)
}
if err != nil {
return
@ -1455,7 +1454,7 @@ func (p *Page) TargetPath() (outfile string) {
}
return p.addLangFilepathPrefix(filepath.Join(strings.ToLower(
p.Site.pathSpec.MakePath(p.Source.Dir())), strings.TrimSpace(outfile)))
p.s.PathSpec.MakePath(p.Source.Dir())), strings.TrimSpace(outfile)))
}
// Pre render prepare steps
@ -1466,14 +1465,13 @@ func (p *Page) prepareLayouts() error {
var layouts []string
if !p.IsRenderable() {
self := "__" + p.TargetPath()
_, err := p.Site.owner.tmpl.GetClone().New(self).Parse(string(p.Content))
_, err := p.Site.owner.Tmpl.GetClone().New(self).Parse(string(p.Content))
if err != nil {
return err
}
layouts = append(layouts, self)
} else {
layouts = append(layouts, p.layouts()...)
layouts = append(layouts, "_default/single.html")
}
p.layoutsCalculated = layouts
}
@ -1707,7 +1705,7 @@ func (p *Page) initLanguage() {
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.", pageLang)
language = ml.DefaultLang
}

View File

@ -23,7 +23,8 @@ import (
"github.com/spf13/viper"
)
func TestPermalink(t *testing.T) {
// TODO(bep) globals test siteinfo
func _TestPermalink(t *testing.T) {
testCommonResetState()
tests := []struct {

View File

@ -26,7 +26,9 @@ import (
"time"
"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"
@ -465,7 +467,12 @@ activity = "exam"
Hi.
`
var pageTestSite = NewSiteDefaultLang()
func init() {
testCommonResetState()
pageTestSite, _ = NewSiteDefaultLang()
}
var pageTestSite *Site
func checkError(t *testing.T, err error, expected string) {
if err == nil {
@ -606,6 +613,8 @@ func testAllMarkdownEnginesForPages(t *testing.T,
testCommonResetState()
fs := hugofs.NewMem()
if settings != nil {
for k, v := range settings {
viper.Set(k, v)
@ -625,14 +634,10 @@ func testAllMarkdownEnginesForPages(t *testing.T,
}
for i := 0; i < len(fileSourcePairs); i += 2 {
writeSource(t, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1])
writeSource(t, fs, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1])
}
s := NewSiteDefaultLang()
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
require.Len(t, s.RegularPages, len(pageSources))
@ -738,11 +743,14 @@ func TestPageWithDelimiter(t *testing.T) {
// Issue #1076
func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) {
s := newSiteFromSources("simple.md", simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder)
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
testCommonResetState()
fs := hugofs.NewMem()
writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
require.Len(t, s.RegularPages, 1)
@ -759,16 +767,18 @@ func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) {
// Issue #2601
func TestPageRawContent(t *testing.T) {
s := newSiteFromSources("raw.md", `---
testCommonResetState()
fs := hugofs.NewMem()
writeSource(t, fs, filepath.Join("content", "raw.md"), `---
title: Raw
---
**Raw**`)
writeSource(t, filepath.Join("layouts", "_default", "single.html"), `{{ .RawContent }}`)
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .RawContent }}`)
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
require.Len(t, s.RegularPages, 1)
p := s.RegularPages[0]
@ -806,11 +816,12 @@ func TestPageWithEmbeddedScriptTag(t *testing.T) {
}
func TestPageWithAdditionalExtension(t *testing.T) {
s := newSiteFromSources("simple.md", simplePageWithAdditionalExtension)
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
fs := hugofs.NewMem()
writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithAdditionalExtension)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, 1)
@ -820,11 +831,12 @@ func TestPageWithAdditionalExtension(t *testing.T) {
}
func TestTableOfContents(t *testing.T) {
s := newSiteFromSources("tocpage.md", pageWithToC)
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
fs := hugofs.NewMem()
writeSource(t, fs, filepath.Join("content", "tocpage.md"), pageWithToC)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, 1)
@ -850,11 +862,11 @@ func TestPageWithMoreTag(t *testing.T) {
}
func TestPageWithDate(t *testing.T) {
s := newSiteFromSources("simple.md", simplePageRFC3339Date)
fs := hugofs.NewMem()
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageRFC3339Date)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, 1)
@ -1372,11 +1384,11 @@ func TestKind(t *testing.T) {
func TestChompBOM(t *testing.T) {
const utf8BOM = "\xef\xbb\xbf"
s := newSiteFromSources("simple.md", utf8BOM+simplePage)
fs := hugofs.NewMem()
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
writeSource(t, fs, filepath.Join("content", "simple.md"), utf8BOM+simplePage)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, 1)

View File

@ -279,7 +279,7 @@ func (p *Page) Paginator(options ...interface{}) (*Pager, error) {
return
}
pagers, err := paginatePages(p.Data["Pages"], pagerSize, p.sections...)
pagers, err := paginatePages(p.s.PathSpec, p.Data["Pages"], pagerSize, p.sections...)
if err != nil {
initError = err
@ -322,7 +322,7 @@ func (p *Page) Paginate(seq interface{}, options ...interface{}) (*Pager, error)
if p.paginator != nil {
return
}
pagers, err := paginatePages(seq, pagerSize, p.sections...)
pagers, err := paginatePages(p.s.PathSpec, seq, pagerSize, p.sections...)
if err != nil {
initError = err
@ -371,13 +371,13 @@ func resolvePagerSize(options ...interface{}) (int, error) {
return pas, nil
}
func paginatePages(seq interface{}, pagerSize int, sections ...string) (pagers, error) {
func paginatePages(pathSpec *helpers.PathSpec, seq interface{}, pagerSize int, sections ...string) (pagers, error) {
if pagerSize <= 0 {
return nil, errors.New("'paginate' configuration setting must be positive to paginate")
}
urlFactory := newPaginationURLFactory(sections...)
urlFactory := newPaginationURLFactory(pathSpec, sections...)
var paginator *paginator
@ -504,8 +504,7 @@ func newPaginator(elements []paginatedElement, total, size int, urlFactory pagin
return p, nil
}
func newPaginationURLFactory(pathElements ...string) paginationURLFactory {
pathSpec := helpers.CurrentPathSpec()
func newPaginationURLFactory(pathSpec *helpers.PathSpec, pathElements ...string) paginationURLFactory {
basePath := path.Join(pathElements...)

View File

@ -19,10 +19,13 @@ import (
"path/filepath"
"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) {
@ -197,11 +200,22 @@ func TestPaginationURLFactory(t *testing.T) {
testCommonResetState()
viper.Set("paginatePath", "zoo")
unicode := newPaginationURLFactory("новости проекта")
fooBar := newPaginationURLFactory("foo", "bar")
pathSpec := newTestPathSpec()
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))
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/"
if unicoded != unicodedExpected {
t.Fatal("Expected\n", unicodedExpected, "\nGot\n", unicoded)
}
assert.Equal(t, "/foo/bar/zoo/12345/", fooBar(12345))
}
@ -224,13 +238,13 @@ func doTestPaginator(t *testing.T, useViper bool) {
viper.Set("paginate", -1)
}
pages := createTestPages(12)
s := NewSiteDefaultLang()
s, err := NewSiteDefaultLang()
require.NoError(t, err)
n1 := s.newHomePage()
n2 := s.newHomePage()
n1.Data["Pages"] = pages
var paginator1 *Pager
var err error
if useViper {
paginator1, err = n1.Paginator()
@ -261,9 +275,10 @@ func TestPaginatorWithNegativePaginate(t *testing.T) {
testCommonResetState()
viper.Set("paginate", -1)
s := NewSiteDefaultLang()
_, err := s.newHomePage().Paginator()
assert.NotNil(t, err)
s, err := NewSiteDefaultLang()
require.NoError(t, err)
_, err = s.newHomePage().Paginator()
require.Error(t, err)
}
func TestPaginate(t *testing.T) {
@ -280,9 +295,11 @@ func TestPaginatorURL(t *testing.T) {
viper.Set("paginate", 2)
viper.Set("paginatePath", "testing")
fs := hugofs.NewMem()
for i := 0; i < 10; i++ {
// Issue #2177, do not double encode URLs
writeSource(t, filepath.Join("content", "阅读", fmt.Sprintf("page%d.md", (i+1))),
writeSource(t, fs, filepath.Join("content", "阅读", fmt.Sprintf("page%d.md", (i+1))),
fmt.Sprintf(`---
title: Page%d
---
@ -290,8 +307,8 @@ Conten%d
`, (i+1), i+1))
}
writeSource(t, filepath.Join("layouts", "_default", "single.html"), "<html><body>{{.Content}}</body></html>")
writeSource(t, filepath.Join("layouts", "_default", "list.html"),
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "<html><body>{{.Content}}</body></html>")
writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"),
`
<html><body>
Count: {{ .Paginator.TotalNumberOfElements }}
@ -301,11 +318,9 @@ Pages: {{ .Paginator.TotalPages }}
{{ end }}
</body></html>`)
if err := buildAndRenderSite(NewSiteDefaultLang()); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
assertFileContent(t, filepath.Join("public", "阅读", "testing", "2", "index.html"), false, "2: /%E9%98%85%E8%AF%BB/testing/2/")
assertFileContent(t, fs, filepath.Join("public", "阅读", "testing", "2", "index.html"), false, "2: /%E9%98%85%E8%AF%BB/testing/2/")
}
@ -318,12 +333,12 @@ func doTestPaginate(t *testing.T, useViper bool) {
}
pages := createTestPages(6)
s := NewSiteDefaultLang()
s, err := NewSiteDefaultLang()
require.NoError(t, err)
n1 := s.newHomePage()
n2 := s.newHomePage()
var paginator1, paginator2 *Pager
var err error
if useViper {
paginator1, err = n1.Paginate(pages)
@ -351,9 +366,10 @@ func doTestPaginate(t *testing.T, useViper bool) {
}
func TestInvalidOptions(t *testing.T) {
s := NewSiteDefaultLang()
s, err := NewSiteDefaultLang()
require.NoError(t, err)
n1 := s.newHomePage()
_, err := n1.Paginate(createTestPages(1), 1, 2)
_, err = n1.Paginate(createTestPages(1), 1, 2)
assert.NotNil(t, err)
_, err = n1.Paginator(1, 2)
assert.NotNil(t, err)
@ -365,19 +381,22 @@ func TestPaginateWithNegativePaginate(t *testing.T) {
testCommonResetState()
viper.Set("paginate", -1)
s := NewSiteDefaultLang()
_, err := s.newHomePage().Paginate(createTestPages(2))
s, err := NewSiteDefaultLang()
require.NoError(t, err)
_, err = s.newHomePage().Paginate(createTestPages(2))
assert.NotNil(t, err)
}
func TestPaginatePages(t *testing.T) {
groups, _ := createTestPages(31).GroupBy("Weight", "desc")
pathSpec := newTestPathSpec()
for i, seq := range []interface{}{createTestPages(11), groups, WeightedPages{}, PageGroup{}, &Pages{}} {
v, err := paginatePages(seq, 11, "t")
v, err := paginatePages(pathSpec, seq, 11, "t")
assert.NotNil(t, v, "Val %d", i)
assert.Nil(t, err, "Err %d", i)
}
_, err := paginatePages(Site{}, 11, "t")
_, err := paginatePages(pathSpec, Site{}, 11, "t")
assert.NotNil(t, err)
}
@ -387,11 +406,12 @@ func TestPaginatorFollowedByPaginateShouldFail(t *testing.T) {
testCommonResetState()
viper.Set("paginate", 10)
s := NewSiteDefaultLang()
s, err := NewSiteDefaultLang()
require.NoError(t, err)
n1 := s.newHomePage()
n2 := s.newHomePage()
_, err := n1.Paginator()
_, err = n1.Paginator()
assert.Nil(t, err)
_, err = n1.Paginate(createTestPages(2))
assert.NotNil(t, err)
@ -405,14 +425,15 @@ func TestPaginateFollowedByDifferentPaginateShouldFail(t *testing.T) {
testCommonResetState()
viper.Set("paginate", 10)
s := NewSiteDefaultLang()
s, err := NewSiteDefaultLang()
require.NoError(t, err)
n1 := s.newHomePage()
n2 := s.newHomePage()
p1 := createTestPages(2)
p2 := createTestPages(10)
_, err := n1.Paginate(p1)
_, err = n1.Paginate(p1)
assert.Nil(t, err)
_, err = n1.Paginate(p1)

View File

@ -150,14 +150,14 @@ func pageToPermalinkDate(p *Page, dateField string) (string, error) {
func pageToPermalinkTitle(p *Page, _ string) (string, error) {
// Page contains Node which has Title
// (also contains URLPath which has Slug, sometimes)
return p.Site.pathSpec.URLize(p.Title), nil
return p.s.PathSpec.URLize(p.Title), nil
}
// pageToPermalinkFilename returns the URL-safe form of the filename
func pageToPermalinkFilename(p *Page, _ string) (string, error) {
//var extension = p.Source.Ext
//var name = p.Source.Path()[0 : len(p.Source.Path())-len(extension)]
return p.Site.pathSpec.URLize(p.Source.TranslationBaseName()), nil
return p.s.PathSpec.URLize(p.Source.TranslationBaseName()), nil
}
// if the page has a slug, return the slug, else return the title
@ -172,7 +172,7 @@ func pageToPermalinkSlugElseTitle(p *Page, a string) (string, error) {
if strings.HasSuffix(p.Slug, "-") {
p.Slug = p.Slug[0 : len(p.Slug)-1]
}
return p.Site.pathSpec.URLize(p.Slug), nil
return p.s.PathSpec.URLize(p.Slug), nil
}
return pageToPermalinkTitle(p, a)
}

View File

@ -14,12 +14,12 @@
package hugolib
import (
"bytes"
"path/filepath"
"testing"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/deps"
"github.com/spf13/viper"
)
@ -32,28 +32,16 @@ const robotTxtTemplate = `User-agent: Googlebot
func TestRobotsTXTOutput(t *testing.T) {
testCommonResetState()
hugofs.InitMemFs()
viper.Set("baseURL", "http://auth/bub/")
viper.Set("enableRobotsTXT", true)
s := &Site{
Source: &source.InMemorySource{ByteSource: weightedSources},
Language: helpers.NewDefaultLanguage(),
}
fs := hugofs.NewMem()
if err := buildAndRenderSite(s, "robots.txt", robotTxtTemplate); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
writeSource(t, fs, filepath.Join("layouts", "robots.txt"), robotTxtTemplate)
writeSourcesToSource(t, "content", fs, weightedSources...)
robotsFile, err := hugofs.Destination().Open("public/robots.txt")
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
if err != nil {
t.Fatalf("Unable to locate: robots.txt")
}
assertFileContent(t, fs, "public/robots.txt", true, "User-agent: Googlebot")
robots := helpers.ReaderToBytes(robotsFile)
if !bytes.HasPrefix(robots, []byte("User-agent: Googlebot")) {
t.Errorf("Robots file should start with 'User-agent: Googlebot'. %s", robots)
}
}

View File

@ -17,6 +17,8 @@ import (
"path/filepath"
"testing"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
)
@ -28,19 +30,19 @@ func TestRSSOutput(t *testing.T) {
viper.Set("rssURI", rssURI)
viper.Set("title", "RSSTest")
for _, s := range weightedSources {
writeSource(t, filepath.Join("content", "sect", s.Name), string(s.Content))
fs := hugofs.NewMem()
for _, src := range weightedSources {
writeSource(t, fs, filepath.Join("content", "sect", src.Name), string(src.Content))
}
if err := buildAndRenderSite(NewSiteDefaultLang()); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
// Home RSS
assertFileContent(t, filepath.Join("public", rssURI), true, "<?xml", "rss version", "RSSTest")
assertFileContent(t, fs, filepath.Join("public", rssURI), true, "<?xml", "rss version", "RSSTest")
// Section RSS
assertFileContent(t, filepath.Join("public", "sect", rssURI), true, "<?xml", "rss version", "Sects on RSSTest")
assertFileContent(t, fs, filepath.Join("public", "sect", rssURI), true, "<?xml", "rss version", "Sects on RSSTest")
// Taxonomy RSS
assertFileContent(t, filepath.Join("public", "categories", "hugo", rssURI), true, "<?xml", "rss version", "Hugo on RSSTest")
assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", rssURI), true, "<?xml", "rss version", "Hugo on RSSTest")
}

View File

@ -26,7 +26,7 @@ import (
bp "github.com/spf13/hugo/bufferpool"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/tpl"
"github.com/spf13/hugo/tplapi"
)
// ShortcodeWithPage is the "." context in a shortcode template.
@ -211,10 +211,10 @@ const innerCleanupRegexp = `\A<p>(.*)</p>\n\z`
const innerCleanupExpand = "$1"
func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page) string {
tmpl := getShortcodeTemplate(sc.name, p.s.tmpl)
tmpl := getShortcodeTemplate(sc.name, p.s.Tmpl)
if tmpl == nil {
p.s.log.ERROR.Printf("Unable to locate template for shortcode '%s' in page %s", sc.name, p.BaseFileName())
p.s.Log.ERROR.Printf("Unable to locate template for shortcode '%s' in page %s", sc.name, p.BaseFileName())
return ""
}
@ -232,7 +232,7 @@ func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page) string {
case shortcode:
inner += renderShortcode(innerData.(shortcode), data, p)
default:
p.s.log.ERROR.Printf("Illegal state on shortcode rendering of '%s' in page %s. Illegal type in inner data: %s ",
p.s.Log.ERROR.Printf("Illegal state on shortcode rendering of '%s' in page %s. Illegal type in inner data: %s ",
sc.name, p.BaseFileName(), reflect.TypeOf(innerData))
return ""
}
@ -286,7 +286,7 @@ func extractAndRenderShortcodes(stringToParse string, p *Page) (string, map[stri
if err != nil {
// try to render what we have whilst logging the error
p.s.log.ERROR.Println(err.Error())
p.s.Log.ERROR.Println(err.Error())
}
// Save for reuse
@ -398,7 +398,7 @@ Loop:
sc.inner = append(sc.inner, currItem.val)
case tScName:
sc.name = currItem.val
tmpl := getShortcodeTemplate(sc.name, p.s.tmpl)
tmpl := getShortcodeTemplate(sc.name, p.s.Tmpl)
if tmpl == nil {
return sc, fmt.Errorf("Unable to locate template for shortcode '%s' in page %s", sc.name, p.BaseFileName())
@ -566,7 +566,7 @@ func replaceShortcodeTokens(source []byte, prefix string, replacements map[strin
return source, nil
}
func getShortcodeTemplate(name string, t tpl.Template) *template.Template {
func getShortcodeTemplate(name string, t tplapi.Template) *template.Template {
if x := t.Lookup("shortcodes/" + name + ".html"); x != nil {
return x
}
@ -584,9 +584,8 @@ func renderShortcodeWithPage(tmpl *template.Template, data *ShortcodeWithPage) s
err := tmpl.Execute(buffer, data)
isInnerShortcodeCache.RUnlock()
if err != nil {
// TODO(bep) globals
data.Page.s.log.ERROR.Println("error processing shortcode", tmpl.Name(), "\n ERR:", err)
data.Page.s.log.WARN.Println(data)
data.Page.s.Log.ERROR.Println("error processing shortcode", tmpl.Name(), "\n ERR:", err)
data.Page.s.Log.WARN.Println(data)
}
return buffer.String()
}

View File

@ -22,49 +22,52 @@ import (
"strings"
"testing"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/target"
"github.com/spf13/hugo/tpl"
"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 tpl.Template) error) (*Page, error) {
func pageFromString(in, filename string, withTemplate ...func(templ tplapi.Template) error) (*Page, error) {
s := pageTestSite
if len(withTemplate) > 0 {
// Have to create a new site
s = NewSiteDefaultLang(withTemplate...)
var err error
s, err = NewSiteDefaultLang(withTemplate...)
if err != nil {
return nil, err
}
}
return s.NewPageFrom(strings.NewReader(in), filename)
}
func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error) {
func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tplapi.Template) error) {
CheckShortCodeMatchAndError(t, input, expected, withTemplate, false)
}
func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error, expectError bool) {
func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tplapi.Template) error, expectError bool) {
testCommonResetState()
fs := hugofs.NewMem()
// Need some front matter, see https://github.com/spf13/hugo/issues/2337
contentFile := `---
title: "Title"
---
` + input
writeSource(t, "content/simple.md", contentFile)
writeSource(t, fs, "content/simple.md", contentFile)
h, err := newHugoSitesDefaultLanguage()
h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs, WithTemplate: withTemplate})
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
}
require.NoError(t, err)
require.Len(t, h.Sites, 1)
cfg := BuildCfg{SkipRender: true, withTemplate: withTemplate}
err = h.Build(cfg)
err = h.Build(BuildCfg{})
if err != nil && !expectError {
t.Fatalf("Shortcode rendered error %s.", err)
@ -89,7 +92,7 @@ title: "Title"
func TestShortcodeGoFuzzReports(t *testing.T) {
p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error {
p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error {
return templ.AddInternalShortcode("sc.html", `foo`)
})
@ -124,7 +127,7 @@ func TestNonSC(t *testing.T) {
// Issue #929
func TestHyphenatedSC(t *testing.T) {
wt := func(tem tpl.Template) error {
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
return nil
}
@ -134,7 +137,7 @@ func TestHyphenatedSC(t *testing.T) {
// Issue #1753
func TestNoTrailingNewline(t *testing.T) {
wt := func(tem tpl.Template) error {
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("a.html", `{{ .Get 0 }}`)
return nil
}
@ -143,7 +146,7 @@ func TestNoTrailingNewline(t *testing.T) {
}
func TestPositionalParamSC(t *testing.T) {
wt := func(tem tpl.Template) error {
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
return nil
}
@ -156,7 +159,7 @@ func TestPositionalParamSC(t *testing.T) {
}
func TestPositionalParamIndexOutOfBounds(t *testing.T) {
wt := func(tem tpl.Template) error {
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 1 }}`)
return nil
}
@ -166,7 +169,7 @@ func TestPositionalParamIndexOutOfBounds(t *testing.T) {
// some repro issues for panics in Go Fuzz testing
func TestNamedParamSC(t *testing.T) {
wt := func(tem tpl.Template) error {
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
return nil
}
@ -180,7 +183,7 @@ func TestNamedParamSC(t *testing.T) {
// Issue #2294
func TestNestedNamedMissingParam(t *testing.T) {
wt := func(tem tpl.Template) error {
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>`)
tem.AddInternalShortcode("div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
@ -192,7 +195,7 @@ func TestNestedNamedMissingParam(t *testing.T) {
}
func TestIsNamedParamsSC(t *testing.T) {
wt := func(tem tpl.Template) error {
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("byposition.html", `<div id="{{ .Get 0 }}">`)
tem.AddInternalShortcode("byname.html", `<div id="{{ .Get "id" }}">`)
tem.AddInternalShortcode("ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
@ -207,7 +210,7 @@ func TestIsNamedParamsSC(t *testing.T) {
}
func TestInnerSC(t *testing.T) {
wt := func(tem tpl.Template) error {
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
}
@ -217,7 +220,7 @@ func TestInnerSC(t *testing.T) {
}
func TestInnerSCWithMarkdown(t *testing.T) {
wt := func(tem tpl.Template) error {
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
}
@ -230,7 +233,7 @@ func TestInnerSCWithMarkdown(t *testing.T) {
}
func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
wt := func(tem tpl.Template) error {
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
}
@ -259,7 +262,7 @@ func TestEmbeddedSC(t *testing.T) {
}
func TestNestedSC(t *testing.T) {
wt := func(tem tpl.Template) error {
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
tem.AddInternalShortcode("scn2.html", `<div>SC2</div>`)
return nil
@ -270,7 +273,7 @@ func TestNestedSC(t *testing.T) {
}
func TestNestedComplexSC(t *testing.T) {
wt := func(tem tpl.Template) error {
wt := func(tem tplapi.Template) error {
tem.AddInternalShortcode("row.html", `-row-{{ .Inner}}-rowStop-`)
tem.AddInternalShortcode("column.html", `-col-{{.Inner }}-colStop-`)
tem.AddInternalShortcode("aside.html", `-aside-{{ .Inner }}-asideStop-`)
@ -285,7 +288,7 @@ func TestNestedComplexSC(t *testing.T) {
}
func TestParentShortcode(t *testing.T) {
wt := func(tem tpl.Template) error {
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 }}`)
tem.AddInternalShortcode("r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
@ -382,7 +385,7 @@ func TestExtractShortcodes(t *testing.T) {
fmt.Sprintf("Hello %sworld%s. And that's it.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""},
} {
p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error {
p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error {
templ.AddInternalShortcode("tag.html", `tag`)
templ.AddInternalShortcode("sc1.html", `sc1`)
templ.AddInternalShortcode("sc2.html", `sc2`)
@ -471,7 +474,7 @@ func TestShortcodesInSite(t *testing.T) {
expected string
}{
{"sect/doc1.md", `a{{< b >}}c`,
filepath.FromSlash("sect/doc1/index.html"), "<p>abc</p>\n"},
filepath.FromSlash("public/sect/doc1/index.html"), "<p>abc</p>\n"},
// Issue #1642: Multiple shortcodes wrapped in P
// Deliberately forced to pass even if they maybe shouldn't.
{"sect/doc2.md", `a
@ -481,7 +484,7 @@ func TestShortcodesInSite(t *testing.T) {
{{< d >}}
e`,
filepath.FromSlash("sect/doc2/index.html"),
filepath.FromSlash("public/sect/doc2/index.html"),
"<p>a</p>\n\n<p>b<br />\nc\nd</p>\n\n<p>e</p>\n"},
{"sect/doc3.md", `a
@ -491,7 +494,7 @@ e`,
{{< d >}}
e`,
filepath.FromSlash("sect/doc3/index.html"),
filepath.FromSlash("public/sect/doc3/index.html"),
"<p>a</p>\n\n<p>b<br />\nc</p>\n\nd\n\n<p>e</p>\n"},
{"sect/doc4.md", `a
{{< b >}}
@ -510,22 +513,22 @@ e`,
`,
filepath.FromSlash("sect/doc4/index.html"),
filepath.FromSlash("public/sect/doc4/index.html"),
"<p>a\nb\nb\nb\nb\nb</p>\n"},
// #2192 #2209: Shortcodes in markdown headers
{"sect/doc5.md", `# {{< b >}}
## {{% c %}}`,
filepath.FromSlash("sect/doc5/index.html"), "\n\n<h1 id=\"hahahugoshortcode-1hbhb\">b</h1>\n\n<h2 id=\"hahahugoshortcode-2hbhb\">c</h2>\n"},
filepath.FromSlash("public/sect/doc5/index.html"), "\n\n<h1 id=\"hahahugoshortcode-1hbhb\">b</h1>\n\n<h2 id=\"hahahugoshortcode-2hbhb\">c</h2>\n"},
// #2223 pygments
{"sect/doc6.md", "\n```bash\nb: {{< b >}} c: {{% c %}}\n```\n",
filepath.FromSlash("sect/doc6/index.html"),
filepath.FromSlash("public/sect/doc6/index.html"),
"b: b c: c\n</code></pre></div>\n"},
// #2249
{"sect/doc7.ad", `_Shortcodes:_ *b: {{< b >}} c: {{% c %}}*`,
filepath.FromSlash("sect/doc7/index.html"),
filepath.FromSlash("public/sect/doc7/index.html"),
"<div class=\"paragraph\">\n<p><em>Shortcodes:</em> <strong>b: b c: c</strong></p>\n</div>\n"},
{"sect/doc8.rst", `**Shortcodes:** *b: {{< b >}} c: {{% c %}}*`,
filepath.FromSlash("sect/doc8/index.html"),
filepath.FromSlash("public/sect/doc8/index.html"),
"<div class=\"document\">\n\n\n<p><strong>Shortcodes:</strong> <em>b: b c: c</em></p>\n</div>"},
{"sect/doc9.mmark", `
---
@ -534,7 +537,7 @@ menu:
parent: 'parent'
---
**Shortcodes:** *b: {{< b >}} c: {{% c %}}*`,
filepath.FromSlash("sect/doc9/index.html"),
filepath.FromSlash("public/sect/doc9/index.html"),
"<p><strong>Shortcodes:</strong> <em>b: b c: c</em></p>\n"},
// Issue #1229: Menus not available in shortcode.
{"sect/doc10.md", `---
@ -545,7 +548,7 @@ tags:
- Menu
---
**Menus:** {{< menu >}}`,
filepath.FromSlash("sect/doc10/index.html"),
filepath.FromSlash("public/sect/doc10/index.html"),
"<p><strong>Menus:</strong> 1</p>\n"},
// Issue #2323: Taxonomies not available in shortcode.
{"sect/doc11.md", `---
@ -553,7 +556,7 @@ tags:
- Bugs
---
**Tags:** {{< tags >}}`,
filepath.FromSlash("sect/doc11/index.html"),
filepath.FromSlash("public/sect/doc11/index.html"),
"<p><strong>Tags:</strong> 2</p>\n"},
}
@ -563,13 +566,7 @@ tags:
sources[i] = source.ByteSource{Name: filepath.FromSlash(test.contentPath), Content: []byte(test.content)}
}
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: false}},
Language: helpers.NewDefaultLanguage(),
}
addTemplates := func(templ tpl.Template) error {
addTemplates := func(templ tplapi.Template) error {
templ.AddTemplate("_default/single.html", "{{.Content}}")
templ.AddInternalShortcode("b.html", `b`)
@ -582,15 +579,11 @@ tags:
}
sites, err := newHugoSites(DepsCfg{}, s)
fs := hugofs.NewMem()
if err != nil {
t.Fatalf("Failed to build site: %s", err)
}
writeSourcesToSource(t, "content", fs, sources...)
if err = sites.Build(BuildCfg{withTemplate: addTemplates}); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
buildSingleSite(t, deps.DepsCfg{WithTemplate: addTemplates, Fs: fs}, BuildCfg{})
for _, test := range tests {
if strings.HasSuffix(test.contentPath, ".ad") && !helpers.HasAsciidoc() {
@ -604,17 +597,7 @@ tags:
continue
}
file, err := hugofs.Destination().Open(test.outFile)
if err != nil {
t.Fatalf("Did not find %s in target: %s", test.outFile, err)
}
content := helpers.ReaderToString(file)
if !strings.Contains(content, test.expected) {
t.Fatalf("%s content expected:\n%q\ngot:\n%q", test.outFile, test.expected, content)
}
assertFileContent(t, fs, test.outFile, true, test.expected)
}
}

View File

@ -35,12 +35,14 @@ import (
"github.com/spf13/afero"
"github.com/spf13/cast"
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"
"github.com/spf13/viper"
@ -106,57 +108,81 @@ type Site struct {
Language *helpers.Language
// Logger etc.
*deps
*deps.Deps `json:"-"`
}
// reset returns a new Site prepared for rebuild.
func (s *Site) reset() *Site {
return &Site{deps: s.deps, Language: s.Language, owner: s.owner, PageCollections: newPageCollections()}
return &Site{Deps: s.Deps, owner: s.owner, PageCollections: newPageCollections()}
}
// newSite creates a new site in the given language.
func newSite(lang *helpers.Language, deps *deps, withTemplate ...func(templ tpl.Template) error) *Site {
// newSite creates a new site with the given configuration.
func newSite(cfg deps.DepsCfg) (*Site, error) {
c := newPageCollections()
// TODO(bep) globals
viper.Set("currentContentLanguage", lang)
if deps == nil {
depsCfg := DepsCfg{WithTemplate: withTemplate}
deps = newDeps(depsCfg)
if cfg.Language == nil {
cfg.Language = helpers.NewDefaultLanguage()
}
return &Site{deps: deps, Language: lang, PageCollections: c, Info: newSiteInfo(siteBuilderCfg{pageCollections: c, language: lang})}
s := &Site{PageCollections: c, Language: cfg.Language}
s.Info = newSiteInfo(siteBuilderCfg{s: s, pageCollections: c, language: s.Language})
return s, nil
}
// NewSite creates a new site with the given dependency configuration.
// The site will have a template system loaded and ready to use.
// Note: This is mainly used in single site tests.
func NewSite(cfg deps.DepsCfg) (*Site, error) {
s, err := newSite(cfg)
if err != nil {
return nil, err
}
if err := applyDepsIfNeeded(cfg, s); err != nil {
return nil, err
}
return s, nil
}
// NewSiteDefaultLang creates a new site in the default language.
func NewSiteDefaultLang(withTemplate ...func(templ tpl.Template) error) *Site {
return newSite(helpers.NewDefaultLanguage(), nil, withTemplate...)
// 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...)
}
// Convenience func used in tests.
func newSiteFromSources(pathContentPairs ...string) *Site {
if len(pathContentPairs)%2 != 0 {
panic("pathContentPairs must come in pairs")
// 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 NewEnglishSite(withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
return newSiteForLang(helpers.NewLanguage("en"), withTemplate...)
}
// NewSiteDefaultLang creates a new site in the default language.
func newSiteForLang(lang *helpers.Language, 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: lang}
s, err := newSite(cfg)
if err != nil {
return nil, err
}
sources := make([]source.ByteSource, 0)
for i := 0; i < len(pathContentPairs); i += 2 {
path := pathContentPairs[i]
content := pathContentPairs[i+1]
sources = append(sources, source.ByteSource{Name: filepath.FromSlash(path), Content: []byte(content)})
}
lang := helpers.NewDefaultLanguage()
return &Site{
deps: newDeps(DepsCfg{}),
PageCollections: newPageCollections(),
Source: &source.InMemorySource{ByteSource: sources},
Language: lang,
Info: newSiteInfo(siteBuilderCfg{language: lang}),
if err := applyDepsIfNeeded(cfg, s); err != nil {
return nil, err
}
return s, nil
}
type targetList struct {
@ -202,14 +228,13 @@ type SiteInfo struct {
Data *map[string]interface{}
owner *HugoSites
s *Site
multilingual *Multilingual
Language *helpers.Language
LanguagePrefix string
Languages helpers.Languages
defaultContentLanguageInSubdir bool
sectionPagesMenu string
pathSpec *helpers.PathSpec
}
func (s *SiteInfo) String() string {
@ -219,15 +244,19 @@ func (s *SiteInfo) String() string {
// Used in tests.
type siteBuilderCfg struct {
language *helpers.Language
language *helpers.Language
// TOD(bep) globals fs
s *Site
fs *hugofs.Fs
pageCollections *PageCollections
baseURL string
}
// TODO(bep) globals get rid of this
func newSiteInfo(cfg siteBuilderCfg) SiteInfo {
return SiteInfo{
s: cfg.s,
BaseURL: template.URL(cfg.baseURL),
pathSpec: helpers.NewPathSpecFromConfig(cfg.language),
multilingual: newMultiLingualForLanguage(cfg.language),
PageCollections: cfg.pageCollections,
}
@ -498,7 +527,7 @@ type whatChanged struct {
// It returns whetever the content source was changed.
func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) {
s.log.DEBUG.Printf("Rebuild for events %q", events)
s.Log.DEBUG.Printf("Rebuild for events %q", events)
s.timerStep("initialize rebuild")
@ -533,8 +562,25 @@ func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) {
}
if len(tmplChanged) > 0 {
s.prepTemplates(nil)
s.owner.tmpl.PrintErrors()
sites := s.owner.Sites
first := sites[0]
// TOD(bep) globals clean
if err := first.Deps.LoadTemplates(); err != nil {
s.Log.ERROR.Println(err)
}
s.Tmpl.PrintErrors()
for i := 1; i < len(sites); i++ {
site := sites[i]
var err error
site.Deps, err = first.Deps.ForLanguage(site.Language)
if err != nil {
return whatChanged{}, err
}
}
s.timerStep("template prep")
}
@ -544,7 +590,7 @@ func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) {
if len(i18nChanged) > 0 {
if err := s.readI18nSources(); err != nil {
s.log.ERROR.Println(err)
s.Log.ERROR.Println(err)
}
}
@ -595,7 +641,7 @@ func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) {
// it's been updated
if ev.Op&fsnotify.Rename == fsnotify.Rename {
// If the file is still on disk, it's only been updated, if it's not, it's been moved
if ex, err := afero.Exists(hugofs.Source(), ev.Name); !ex || err != nil {
if ex, err := afero.Exists(s.Fs.Source, ev.Name); !ex || err != nil {
path, _ := helpers.GetRelativePath(ev.Name, s.getContentDir(ev.Name))
s.removePageByPath(path)
continue
@ -613,7 +659,7 @@ func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) {
file, err := s.reReadFile(ev.Name)
if err != nil {
s.log.ERROR.Println("Error reading file", ev.Name, ";", err)
s.Log.ERROR.Println("Error reading file", ev.Name, ";", err)
}
if file != nil {
@ -647,7 +693,7 @@ func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) {
for i := 0; i < 2; i++ {
err := <-errs
if err != nil {
s.log.ERROR.Println(err)
s.Log.ERROR.Println(err)
}
}
@ -660,29 +706,8 @@ func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) {
}
func (s *Site) prepTemplates(withTemplate func(templ tpl.Template) error) error {
wt := func(tmpl tpl.Template) error {
// TODO(bep) global error handling
tmpl.LoadTemplates(s.absLayoutDir())
if s.hasTheme() {
tmpl.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme")
}
if withTemplate != nil {
if err := withTemplate(tmpl); err != nil {
return err
}
}
return nil
}
s.refreshTemplates(wt)
return nil
}
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 %q", sources)
s.Data = make(map[string]interface{})
var current map[string]interface{}
for _, currentSource := range sources {
@ -717,7 +742,7 @@ func (s *Site) loadData(sources []source.Input) (err error) {
// this warning could happen if
// 1. A theme uses the same key; the main data folder wins
// 2. A sub folder uses the same key: the sub folder wins
s.log.WARN.Printf("Data for key '%s' in path '%s' is overridden in subfolder", key, r.Path())
s.Log.WARN.Printf("Data for key '%s' in path '%s' is overridden in subfolder", key, r.Path())
}
data[key] = value
}
@ -740,21 +765,21 @@ 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())
s.Log.WARN.Printf("Data not supported for extension '%s'", f.Extension())
return nil, nil
}
}
func (s *Site) readI18nSources() error {
i18nSources := []source.Input{&source.Filesystem{Base: s.absI18nDir()}}
i18nSources := []source.Input{source.NewFilesystem(s.Fs, s.absI18nDir())}
themeI18nDir, err := helpers.GetThemeI18nDirPath()
themeI18nDir, err := s.PathSpec.GetThemeI18nDirPath()
if err == nil {
i18nSources = []source.Input{&source.Filesystem{Base: themeI18nDir}, i18nSources[0]}
i18nSources = []source.Input{source.NewFilesystem(s.Fs, themeI18nDir), i18nSources[0]}
}
if err = loadI18n(i18nSources); err != nil {
if err = s.loadI18n(i18nSources); err != nil {
return err
}
@ -763,12 +788,12 @@ func (s *Site) readI18nSources() error {
func (s *Site) readDataFromSourceFS() error {
dataSources := make([]source.Input, 0, 2)
dataSources = append(dataSources, &source.Filesystem{Base: s.absDataDir()})
dataSources = append(dataSources, source.NewFilesystem(s.Fs, s.absDataDir()))
// have to be last - duplicate keys in earlier entries will win
themeDataDir, err := helpers.GetThemeDataDirPath()
themeDataDir, err := s.PathSpec.GetThemeDataDirPath()
if err == nil {
dataSources = append(dataSources, &source.Filesystem{Base: themeDataDir})
dataSources = append(dataSources, source.NewFilesystem(s.Fs, themeDataDir))
}
err = s.loadData(dataSources)
@ -781,10 +806,7 @@ func (s *Site) process(config BuildCfg) (err error) {
if err = s.initialize(); err != nil {
return
}
s.prepTemplates(config.withTemplate)
s.owner.tmpl.PrintErrors()
s.timerStep("initialize & template prep")
s.timerStep("initialize")
if err = s.readDataFromSourceFS(); err != nil {
return
@ -817,7 +839,6 @@ func (s *Site) setCurrentLanguageConfig() error {
viper.Set("currentContentLanguage", s.Language)
// Cache the current config.
helpers.InitConfigProviderForCurrentContentLanguage()
s.Info.pathSpec = helpers.CurrentPathSpec()
return tpl.SetTranslateLang(s.Language)
}
@ -873,7 +894,7 @@ func (s *Site) initialize() (err error) {
// May be supplied in tests.
if s.Source != nil && len(s.Source.Files()) > 0 {
s.log.DEBUG.Println("initialize: Source is already set")
s.Log.DEBUG.Println("initialize: Source is already set")
return
}
@ -883,10 +904,7 @@ func (s *Site) initialize() (err error) {
staticDir := helpers.AbsPathify(viper.GetString("staticDir") + "/")
s.Source = &source.Filesystem{
AvoidPaths: []string{staticDir},
Base: s.absContentDir(),
}
s.Source = source.NewFilesystem(s.Fs, s.absContentDir(), staticDir)
return
}
@ -897,7 +915,7 @@ func (s *SiteInfo) HomeAbsURL() string {
if s.IsMultiLingual() {
base = s.Language.Lang
}
return s.pathSpec.AbsURL(base, false)
return s.owner.AbsURL(base, false)
}
// SitemapAbsURL is a convenience method giving the absolute URL to the sitemap.
@ -966,7 +984,7 @@ func (s *Site) initializeSiteInfo() {
Permalinks: permalinks,
Data: &s.Data,
owner: s.owner,
pathSpec: helpers.NewPathSpecFromConfig(lang),
s: s,
}
s.Info.RSSLink = s.Info.permalinkStr(lang.GetString("rssURI"))
@ -1081,11 +1099,11 @@ func (s *Site) getRealDir(base, path string) string {
return base
}
realDir, err := helpers.GetRealPath(hugofs.Source(), base)
realDir, err := helpers.GetRealPath(s.Fs.Source, base)
if err != nil {
if !os.IsNotExist(err) {
s.log.ERROR.Printf("Failed to get real path for %s: %s", path, err)
s.Log.ERROR.Printf("Failed to get real path for %s: %s", path, err)
}
return ""
}
@ -1102,7 +1120,7 @@ func (s *Site) absPublishDir() string {
}
func (s *Site) checkDirectories() (err error) {
if b, _ := helpers.DirExists(s.absContentDir(), hugofs.Source()); !b {
if b, _ := helpers.DirExists(s.absContentDir(), s.Fs.Source); !b {
return errors.New("No source directory found, expecting to find it at " + s.absContentDir())
}
return
@ -1110,10 +1128,10 @@ func (s *Site) checkDirectories() (err error) {
// reReadFile resets file to be read from disk again
func (s *Site) reReadFile(absFilePath string) (*source.File, error) {
s.log.INFO.Println("rereading", absFilePath)
s.Log.INFO.Println("rereading", absFilePath)
var file *source.File
reader, err := source.NewLazyFileReader(hugofs.Source(), absFilePath)
reader, err := source.NewLazyFileReader(s.Fs.Source, absFilePath)
if err != nil {
return nil, err
}
@ -1131,7 +1149,7 @@ func (s *Site) readPagesFromSource() chan error {
panic(fmt.Sprintf("s.Source not set %s", s.absContentDir()))
}
s.log.DEBUG.Printf("Read %d pages from source", len(s.Source.Files()))
s.Log.DEBUG.Printf("Read %d pages from source", len(s.Source.Files()))
errs := make(chan error)
if len(s.Source.Files()) < 1 {
@ -1231,7 +1249,7 @@ func readSourceFile(s *Site, file *source.File, results chan<- HandledResult) {
if h != nil {
h.Read(file, s, results)
} else {
s.log.ERROR.Println("Unsupported File Type", file.Path())
s.Log.ERROR.Println("Unsupported File Type", file.Path())
}
}
@ -1372,17 +1390,17 @@ func (s *Site) getMenusFromConfig() Menus {
for name, menu := range menus {
m, err := cast.ToSliceE(menu)
if err != nil {
s.log.ERROR.Printf("unable to process menus in site config\n")
s.log.ERROR.Println(err)
s.Log.ERROR.Printf("unable to process menus in site config\n")
s.Log.ERROR.Println(err)
} else {
for _, entry := range m {
s.log.DEBUG.Printf("found menu: %q, in site config\n", name)
s.Log.DEBUG.Printf("found menu: %q, in site config\n", name)
menuEntry := MenuEntry{Menu: name}
ime, err := cast.ToStringMapE(entry)
if err != nil {
s.log.ERROR.Printf("unable to process menus in site config\n")
s.log.ERROR.Println(err)
s.Log.ERROR.Printf("unable to process menus in site config\n")
s.Log.ERROR.Println(err)
}
menuEntry.marshallMap(ime)
@ -1407,7 +1425,7 @@ func (s *SiteInfo) createNodeMenuEntryURL(in string) string {
}
// make it match the nodes
menuEntryURL := in
menuEntryURL = helpers.SanitizeURLKeepTrailingSlash(s.pathSpec.URLize(menuEntryURL))
menuEntryURL = helpers.SanitizeURLKeepTrailingSlash(s.s.PathSpec.URLize(menuEntryURL))
if !s.canonifyURLs {
menuEntryURL = helpers.AddContextRoot(string(s.BaseURL), menuEntryURL)
}
@ -1454,7 +1472,7 @@ func (s *Site) assembleMenus() {
for name, me := range p.Menus() {
if _, ok := flat[twoD{name, me.KeyName()}]; ok {
s.log.ERROR.Printf("Two or more menu items have the same name/identifier in Menu %q: %q.\nRename or set an unique identifier.\n", name, me.KeyName())
s.Log.ERROR.Printf("Two or more menu items have the same name/identifier in Menu %q: %q.\nRename or set an unique identifier.\n", name, me.KeyName())
continue
}
flat[twoD{name, me.KeyName()}] = me
@ -1490,6 +1508,13 @@ func (s *Site) assembleMenus() {
}
}
func (s *Site) getTaxonomyKey(key string) string {
if s.Info.preserveTaxonomyNames {
// Keep as is
return key
}
return s.PathSpec.MakePathSanitized(key)
}
func (s *Site) assembleTaxonomies() {
s.Taxonomies = make(TaxonomyList)
s.taxonomiesPluralSingular = make(map[string]string)
@ -1497,7 +1522,7 @@ func (s *Site) assembleTaxonomies() {
taxonomies := s.Language.GetStringMapString("taxonomies")
s.log.INFO.Printf("found taxonomies: %#v\n", taxonomies)
s.Log.INFO.Printf("found taxonomies: %#v\n", taxonomies)
for singular, plural := range taxonomies {
s.Taxonomies[plural] = make(Taxonomy)
@ -1513,21 +1538,21 @@ func (s *Site) assembleTaxonomies() {
if v, ok := vals.([]string); ok {
for _, idx := range v {
x := WeightedPage{weight.(int), p}
s.Taxonomies[plural].add(idx, x, s.Info.preserveTaxonomyNames)
s.Taxonomies[plural].add(s.getTaxonomyKey(idx), x)
if s.Info.preserveTaxonomyNames {
// Need to track the original
s.taxonomiesOrigKey[fmt.Sprintf("%s-%s", plural, kp(idx))] = idx
s.taxonomiesOrigKey[fmt.Sprintf("%s-%s", plural, s.PathSpec.MakePathSanitized(idx))] = idx
}
}
} else if v, ok := vals.(string); ok {
x := WeightedPage{weight.(int), p}
s.Taxonomies[plural].add(v, x, s.Info.preserveTaxonomyNames)
s.Taxonomies[plural].add(s.getTaxonomyKey(v), x)
if s.Info.preserveTaxonomyNames {
// Need to track the original
s.taxonomiesOrigKey[fmt.Sprintf("%s-%s", plural, kp(v))] = v
s.taxonomiesOrigKey[fmt.Sprintf("%s-%s", plural, s.PathSpec.MakePathSanitized(v))] = v
}
} else {
s.log.ERROR.Printf("Invalid %s in %s\n", plural, p.File.Path())
s.Log.ERROR.Printf("Invalid %s in %s\n", plural, p.File.Path())
}
}
}
@ -1564,7 +1589,7 @@ func (s *Site) assembleSections() {
sectionPages := s.findPagesByKind(KindSection)
for i, p := range regularPages {
s.Sections.add(p.Section(), WeightedPage{regularPages[i].Weight, regularPages[i]}, s.Info.preserveTaxonomyNames)
s.Sections.add(s.getTaxonomyKey(p.Section()), WeightedPage{regularPages[i].Weight, regularPages[i]})
}
// Add sections without regular pages, but with a content page
@ -1665,18 +1690,18 @@ func (s *Site) appendThemeTemplates(in []string) []string {
// Stats prints Hugo builds stats to the console.
// This is what you see after a successful hugo build.
func (s *Site) Stats() {
s.log.FEEDBACK.Printf("Built site for language %s:\n", s.Language.Lang)
s.log.FEEDBACK.Println(s.draftStats())
s.log.FEEDBACK.Println(s.futureStats())
s.log.FEEDBACK.Println(s.expiredStats())
s.log.FEEDBACK.Printf("%d regular pages created\n", len(s.RegularPages))
s.log.FEEDBACK.Printf("%d other pages created\n", (len(s.Pages) - len(s.RegularPages)))
s.log.FEEDBACK.Printf("%d non-page files copied\n", len(s.Files))
s.log.FEEDBACK.Printf("%d paginator pages created\n", s.Info.paginationPageCount)
s.Log.FEEDBACK.Printf("Built site for language %s:\n", s.Language.Lang)
s.Log.FEEDBACK.Println(s.draftStats())
s.Log.FEEDBACK.Println(s.futureStats())
s.Log.FEEDBACK.Println(s.expiredStats())
s.Log.FEEDBACK.Printf("%d regular pages created\n", len(s.RegularPages))
s.Log.FEEDBACK.Printf("%d other pages created\n", (len(s.Pages) - len(s.RegularPages)))
s.Log.FEEDBACK.Printf("%d non-page files copied\n", len(s.Files))
s.Log.FEEDBACK.Printf("%d paginator pages created\n", s.Info.paginationPageCount)
taxonomies := s.Language.GetStringMapString("taxonomies")
for _, pl := range taxonomies {
s.log.FEEDBACK.Printf("%d %s created\n", len(s.Taxonomies[pl]), pl)
s.Log.FEEDBACK.Printf("%d %s created\n", len(s.Taxonomies[pl]), pl)
}
}
@ -1701,11 +1726,11 @@ func (s *SiteInfo) permalink(plink string) string {
func (s *SiteInfo) permalinkStr(plink string) string {
return helpers.MakePermalink(
viper.GetString("baseURL"),
s.pathSpec.URLizeAndPrep(plink)).String()
s.s.PathSpec.URLizeAndPrep(plink)).String()
}
func (s *Site) renderAndWriteXML(name string, dest string, d interface{}, layouts ...string) error {
s.log.DEBUG.Printf("Render XML for %q to %q", name, dest)
s.Log.DEBUG.Printf("Render XML for %q to %q", name, dest)
renderBuffer := bp.GetBuffer()
defer bp.PutBuffer(renderBuffer)
renderBuffer.WriteString("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n")
@ -1797,7 +1822,7 @@ func (s *Site) renderAndWritePage(name string, dest string, d interface{}, layou
if outBuffer.Len() == 0 {
s.log.WARN.Printf("%s is rendered empty\n", dest)
s.Log.WARN.Printf("%s is rendered empty\n", dest)
if dest == "/" {
debugAddend := ""
if !viper.GetBool("verbose") {
@ -1829,7 +1854,8 @@ Your rendered home page is blank: /index.html is zero-length
func (s *Site) renderForLayouts(name string, d interface{}, w io.Writer, layouts ...string) error {
layout, found := s.findFirstLayout(layouts...)
if !found {
s.log.WARN.Printf("Unable to locate layout for %s: %s\n", name, layouts)
s.Log.WARN.Printf("[%s] Unable to locate layout for %s: %s\n", s.Language.Lang, name, layouts)
return nil
}
@ -1850,7 +1876,7 @@ func (s *Site) renderForLayouts(name string, d interface{}, w io.Writer, layouts
func (s *Site) findFirstLayout(layouts ...string) (string, bool) {
for _, layout := range layouts {
if s.owner.tmpl.Lookup(layout) != nil {
if s.Tmpl.Lookup(layout) != nil {
return layout, true
}
}
@ -1860,7 +1886,7 @@ func (s *Site) findFirstLayout(layouts ...string) (string, bool) {
func (s *Site) renderThing(d interface{}, layout string, w io.Writer) error {
// If the template doesn't exist, then return, but leave the Writer open
if templ := s.owner.tmpl.Lookup(layout); templ != nil {
if templ := s.Tmpl.Lookup(layout); templ != nil {
return templ.Execute(w, d)
}
return fmt.Errorf("Layout not found: %s", layout)
@ -1893,6 +1919,9 @@ func (s *Site) languageAliasTarget() target.AliasPublisher {
}
func (s *Site) initTargetList() {
if s.Fs == nil {
panic("Must have Fs")
}
s.targetListInit.Do(func() {
langDir := ""
if s.Language.Lang != s.Info.multilingual.DefaultLang.Lang || s.Info.defaultContentLanguageInSubdir {
@ -1900,6 +1929,7 @@ func (s *Site) initTargetList() {
}
if s.targets.page == nil {
s.targets.page = &target.PagePub{
Fs: s.Fs,
PublishDir: s.absPublishDir(),
UglyURLs: viper.GetBool("uglyURLs"),
LangDir: langDir,
@ -1907,6 +1937,7 @@ func (s *Site) initTargetList() {
}
if s.targets.pageUgly == nil {
s.targets.pageUgly = &target.PagePub{
Fs: s.Fs,
PublishDir: s.absPublishDir(),
UglyURLs: true,
LangDir: langDir,
@ -1914,17 +1945,20 @@ func (s *Site) initTargetList() {
}
if s.targets.file == nil {
s.targets.file = &target.Filesystem{
Fs: s.Fs,
PublishDir: s.absPublishDir(),
}
}
if s.targets.alias == nil {
s.targets.alias = &target.HTMLRedirectAlias{
Fs: s.Fs,
PublishDir: s.absPublishDir(),
Templates: s.owner.tmpl.Lookup("alias.html"),
Templates: s.Tmpl.Lookup("alias.html"),
}
}
if s.targets.languageAlias == nil {
s.targets.languageAlias = &target.HTMLRedirectAlias{
Fs: s.Fs,
PublishDir: s.absPublishDir(),
AllowRoot: true,
}
@ -1933,12 +1967,12 @@ func (s *Site) initTargetList() {
}
func (s *Site) writeDestFile(path string, reader io.Reader) (err error) {
s.log.DEBUG.Println("creating file:", path)
s.Log.DEBUG.Println("creating file:", path)
return s.fileTarget().Publish(path, reader)
}
func (s *Site) writeDestPage(path string, publisher target.Publisher, reader io.Reader) (err error) {
s.log.DEBUG.Println("creating page:", path)
s.Log.DEBUG.Println("creating page:", path)
return publisher.Publish(path, reader)
}
@ -1956,11 +1990,11 @@ func (s *Site) publishDestAlias(aliasPublisher target.AliasPublisher, path, perm
}
permalink, err = helpers.GetRelativePath(permalink, path)
if err != nil {
s.log.ERROR.Println("Failed to make a RelativeURL alias:", path, "redirecting to", permalink)
s.Log.ERROR.Println("Failed to make a RelativeURL alias:", path, "redirecting to", permalink)
}
permalink = filepath.ToSlash(permalink)
}
s.log.DEBUG.Println("creating alias:", path, "redirecting to", permalink)
s.Log.DEBUG.Println("creating alias:", path, "redirecting to", permalink)
return aliasPublisher.Publish(path, permalink, p)
}
@ -2051,7 +2085,7 @@ func (s *Site) newHomePage() *Page {
}
func (s *Site) setPageURLs(p *Page, in string) {
p.URLPath.URL = s.Info.pathSpec.URLizeAndPrep(in)
p.URLPath.URL = s.PathSpec.URLizeAndPrep(in)
p.URLPath.Permalink = s.Info.permalink(p.URLPath.URL)
p.RSSLink = template.HTML(s.Info.permalink(in + ".xml"))
}
@ -2063,7 +2097,7 @@ func (s *Site) newTaxonomyPage(plural, key string) *Page {
p.sections = []string{plural, key}
if s.Info.preserveTaxonomyNames {
key = s.Info.pathSpec.MakePathSanitized(key)
key = s.PathSpec.MakePathSanitized(key)
}
if s.Info.preserveTaxonomyNames {

View File

@ -16,6 +16,11 @@ package hugolib
import (
"encoding/json"
"testing"
"path/filepath"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
)
// Issue #1123
@ -23,9 +28,15 @@ import (
// May be smart to run with: -timeout 4000ms
func TestEncodePage(t *testing.T) {
fs := hugofs.NewMem()
// borrowed from menu_test.go
s := createTestSite(menuPageSources)
testSiteSetup(s, t)
for _, src := range menuPageSources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
_, err := json.Marshal(s)
check(t, err)

View File

@ -66,7 +66,7 @@ func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.Wa
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)
s.Log.DEBUG.Printf("Render %s to %q with layouts %q", p.Kind, targetPath, layouts)
if err := s.renderAndWritePage("page "+p.FullFilePath(), targetPath, p, s.appendThemeTemplates(layouts)...); err != nil {
results <- err
@ -88,7 +88,7 @@ func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.Wa
// renderPaginator must be run after the owning Page has been rendered.
func (s *Site) renderPaginator(p *Page) error {
if p.paginator != nil {
s.log.DEBUG.Printf("Render paginator for page %q", p.Path())
s.Log.DEBUG.Printf("Render paginator for page %q", p.Path())
paginatePath := helpers.Config().GetString("paginatePath")
// write alias for page 1
@ -267,14 +267,14 @@ func (s *Site) renderAliases() error {
if s.owner.multilingual.enabled() {
mainLang := s.owner.multilingual.DefaultLang.Lang
if s.Info.defaultContentLanguageInSubdir {
mainLangURL := s.Info.pathSpec.AbsURL(mainLang, false)
s.log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
mainLangURL := s.PathSpec.AbsURL(mainLang, 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
}
} else {
mainLangURL := s.Info.pathSpec.AbsURL("", false)
s.log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
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 {
return err
}

View File

@ -18,16 +18,15 @@ import (
"path/filepath"
"strings"
"testing"
"time"
"github.com/bep/inflect"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/target"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -47,37 +46,6 @@ func init() {
testMode = true
}
// Issue #1797
func TestReadPagesFromSourceWithEmptySource(t *testing.T) {
testCommonResetState()
viper.Set("defaultExtension", "html")
viper.Set("verbose", true)
viper.Set("baseURL", "http://auth/bub")
sources := []source.ByteSource{}
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: true}},
}
var err error
d := time.Second * 2
ticker := time.NewTicker(d)
select {
case err = <-s.readPagesFromSource():
break
case <-ticker.C:
err = fmt.Errorf("ReadPagesFromSource() never returns in %s", d.String())
}
ticker.Stop()
if err != nil {
t.Fatalf("Unable to read source: %s", err)
}
}
func pageMust(p *Page, err error) *Page {
if err != nil {
panic(err)
@ -86,11 +54,12 @@ func pageMust(p *Page, err error) *Page {
}
func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
s := newSiteFromSources("content/a/file.md", pageSimpleTitle)
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
fs := hugofs.NewMem()
writeSource(t, fs, filepath.Join("content", "a", "file.md"), pageSimpleTitle)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
require.Len(t, s.RegularPages, 1)
@ -104,12 +73,15 @@ func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
func TestRenderWithInvalidTemplate(t *testing.T) {
s := NewSiteDefaultLang()
if err := buildAndRenderSite(s, "missing", templateMissingFunc); err != nil {
t.Fatalf("Got build error: %s", err)
}
fs := hugofs.NewMem()
errCount := s.log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError)
writeSource(t, fs, filepath.Join("content", "foo.md"), "foo")
withTemplate := createWithTemplateFromNameValues("missing", templateMissingFunc)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, WithTemplate: withTemplate}, BuildCfg{})
errCount := s.Log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError)
// TODO(bep) globals clean up the template error handling
// The template errors are stored in a slice etc. so we get 4 log entries
@ -122,7 +94,6 @@ func TestRenderWithInvalidTemplate(t *testing.T) {
func TestDraftAndFutureRender(t *testing.T) {
testCommonResetState()
hugofs.InitMemFs()
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*")},
@ -131,17 +102,14 @@ func TestDraftAndFutureRender(t *testing.T) {
}
siteSetup := func(t *testing.T) *Site {
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: sources},
Language: helpers.NewDefaultLanguage(),
fs := hugofs.NewMem()
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
return s
return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
}
viper.Set("baseURL", "http://auth/bub")
@ -183,24 +151,20 @@ func TestDraftAndFutureRender(t *testing.T) {
func TestFutureExpirationRender(t *testing.T) {
testCommonResetState()
hugofs.InitMemFs()
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 {
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: sources},
Language: helpers.NewDefaultLanguage(),
fs := hugofs.NewMem()
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
return s
return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
}
viper.Set("baseURL", "http://auth/bub")
@ -282,16 +246,18 @@ THE END.`, refShortcode)),
},
}
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: uglyURLs}},
Language: helpers.NewDefaultLanguage(),
fs := hugofs.NewMem()
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
if err := buildAndRenderSite(s, "_default/single.html", "{{.Content}}"); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
s := buildSingleSite(
t,
deps.DepsCfg{
Fs: fs,
WithTemplate: createWithTemplateFromNameValues("_default/single.html", "{{.Content}}")},
BuildCfg{})
if len(s.RegularPages) != 3 {
t.Fatalf("Expected 3 got %d pages", len(s.AllPages))
@ -301,23 +267,14 @@ THE END.`, refShortcode)),
doc string
expected string
}{
{filepath.FromSlash(fmt.Sprintf("sect/doc1%s", expectedPathSuffix)), fmt.Sprintf("<p>Ref 2: %s/sect/doc2%s</p>\n", expectedBase, expectedURLSuffix)},
{filepath.FromSlash(fmt.Sprintf("sect/doc2%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong></p>\n\n%s/sect/doc1%s\n\n<p>THE END.</p>\n", expectedBase, expectedURLSuffix)},
{filepath.FromSlash(fmt.Sprintf("sect/doc3%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong>%s/sect/doc3%s.</p>\n", expectedBase, expectedURLSuffix)},
{filepath.FromSlash(fmt.Sprintf("public/sect/doc1%s", expectedPathSuffix)), fmt.Sprintf("<p>Ref 2: %s/sect/doc2%s</p>\n", expectedBase, expectedURLSuffix)},
{filepath.FromSlash(fmt.Sprintf("public/sect/doc2%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong></p>\n\n%s/sect/doc1%s\n\n<p>THE END.</p>\n", expectedBase, expectedURLSuffix)},
{filepath.FromSlash(fmt.Sprintf("public/sect/doc3%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong>%s/sect/doc3%s.</p>\n", expectedBase, expectedURLSuffix)},
}
for _, test := range tests {
file, err := hugofs.Destination().Open(test.doc)
assertFileContent(t, fs, test.doc, true, test.expected)
if err != nil {
t.Fatalf("Did not find %s in target: %s", test.doc, err)
}
content := helpers.ReaderToString(file)
if content != test.expected {
t.Fatalf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content)
}
}
}
@ -350,21 +307,19 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
{Name: filepath.FromSlash("sect/doc2.md"), Content: []byte("---\nurl: /ugly.html\nmarkup: markdown\n---\n# title\ndoc2 *content*")},
}
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: uglyURLs, PublishDir: "public"}},
Language: helpers.NewDefaultLanguage(),
fs := hugofs.NewMem()
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
if err := buildAndRenderSite(s,
"index.html", "Home Sweet {{ if.IsHome }}Home{{ end }}.",
"_default/single.html", "{{.Content}}{{ if.IsHome }}This is not home!{{ end }}",
"404.html", "Page Not Found.{{ if.IsHome }}This is not home!{{ end }}",
"rss.xml", "<root>RSS</root>",
"sitemap.xml", "<root>SITEMAP</root>"); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
writeSource(t, fs, filepath.Join("layouts", "index.html"), "Home Sweet {{ if.IsHome }}Home{{ end }}.")
writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}{{ if.IsHome }}This is not home!{{ end }}")
writeSource(t, fs, filepath.Join("layouts", "404.html"), "Page Not Found.{{ if.IsHome }}This is not home!{{ end }}")
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{})
var expectedPagePath string
if uglyURLs {
@ -391,7 +346,7 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
}
for _, test := range tests {
content := readDestination(t, test.doc)
content := readDestination(t, fs, test.doc)
if content != test.expected {
t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content)
@ -435,17 +390,16 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
{Name: filepath.FromSlash("ラーメン/doc3.html"), Content: []byte("doc3")},
}
fs := hugofs.NewMem()
for _, source := range sources {
writeSource(t, filepath.Join("content", source.Name), string(source.Content))
writeSource(t, fs, filepath.Join("content", source.Name), string(source.Content))
}
s := NewSiteDefaultLang()
writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}")
writeSource(t, fs, filepath.Join("layouts", "_default/list.html"), "{{.Title}}")
if err := buildAndRenderSite(s,
"_default/single.html", "{{.Content}}",
"_default/list.html", "{{ .Title }}"); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
tests := []struct {
doc string
@ -466,14 +420,13 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
test.expected = inflect.Pluralize(test.expected)
}
assertFileContent(t, filepath.Join("public", test.doc), true, test.expected)
assertFileContent(t, fs, filepath.Join("public", test.doc), true, test.expected)
}
}
func TestSkipRender(t *testing.T) {
testCommonResetState()
hugofs.InitMemFs()
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>")},
@ -488,37 +441,38 @@ func TestSkipRender(t *testing.T) {
viper.Set("defaultExtension", "html")
viper.Set("verbose", true)
viper.Set("canonifyURLs", true)
viper.Set("uglyURLs", true)
viper.Set("baseURL", "http://auth/bub")
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: true}},
Language: helpers.NewDefaultLanguage(),
fs := hugofs.NewMem()
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
if err := buildAndRenderSite(s,
"_default/single.html", "{{.Content}}",
"head", "<head><script src=\"script.js\"></script></head>",
"head_abs", "<head><script src=\"/script.js\"></script></head>"); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}")
writeSource(t, fs, filepath.Join("layouts", "head"), "<head><script src=\"script.js\"></script></head>")
writeSource(t, fs, filepath.Join("layouts", "head_abs"), "<head><script src=\"/script.js\"></script></head>")
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
tests := []struct {
doc string
expected string
}{
{filepath.FromSlash("sect/doc1.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
{filepath.FromSlash("sect/doc2.html"), "<!doctype html><html><body>more content</body></html>"},
{filepath.FromSlash("sect/doc3.html"), "\n\n<h1 id=\"doc3\">doc3</h1>\n\n<p><em>some</em> content</p>\n"},
{filepath.FromSlash("sect/doc4.html"), "\n\n<h1 id=\"doc4\">doc4</h1>\n\n<p><em>some content</em></p>\n"},
{filepath.FromSlash("sect/doc5.html"), "<!doctype html><html><head><script src=\"script.js\"></script></head><body>body5</body></html>"},
{filepath.FromSlash("sect/doc6.html"), "<!doctype html><html><head><script src=\"http://auth/bub/script.js\"></script></head><body>body5</body></html>"},
{filepath.FromSlash("doc7.html"), "<html><body>doc7 content</body></html>"},
{filepath.FromSlash("sect/doc8.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
{filepath.FromSlash("public/sect/doc1.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
{filepath.FromSlash("public/sect/doc2.html"), "<!doctype html><html><body>more content</body></html>"},
{filepath.FromSlash("public/sect/doc3.html"), "\n\n<h1 id=\"doc3\">doc3</h1>\n\n<p><em>some</em> content</p>\n"},
{filepath.FromSlash("public/sect/doc4.html"), "\n\n<h1 id=\"doc4\">doc4</h1>\n\n<p><em>some content</em></p>\n"},
{filepath.FromSlash("public/sect/doc5.html"), "<!doctype html><html><head><script src=\"script.js\"></script></head><body>body5</body></html>"},
{filepath.FromSlash("public/sect/doc6.html"), "<!doctype html><html><head><script src=\"http://auth/bub/script.js\"></script></head><body>body5</body></html>"},
{filepath.FromSlash("public/doc7.html"), "<html><body>doc7 content</body></html>"},
{filepath.FromSlash("public/sect/doc8.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
}
for _, test := range tests {
file, err := hugofs.Destination().Open(test.doc)
file, err := fs.Destination.Open(test.doc)
if err != nil {
t.Fatalf("Did not find %s in target.", test.doc)
}
@ -535,8 +489,8 @@ func TestAbsURLify(t *testing.T) {
testCommonResetState()
viper.Set("defaultExtension", "html")
viper.Set("uglyURLs", true)
hugofs.InitMemFs()
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>")},
@ -545,34 +499,27 @@ func TestAbsURLify(t *testing.T) {
for _, canonify := range []bool{true, false} {
viper.Set("canonifyURLs", canonify)
viper.Set("baseURL", baseURL)
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: true}},
Language: helpers.NewDefaultLanguage(),
}
t.Logf("Rendering with baseURL %q and canonifyURLs set %v", viper.GetString("baseURL"), canonify)
if err := buildAndRenderSite(s, "blue/single.html", templateWithURLAbs); err != nil {
t.Fatalf("Failed to build site: %s", err)
fs := hugofs.NewMem()
for _, src := range sources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
writeSource(t, fs, filepath.Join("layouts", "blue/single.html"), templateWithURLAbs)
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
tests := []struct {
file, expected string
}{
{"blue/doc2.html", "<a href=\"%s/foobar.jpg\">Going</a>"},
{"sect/doc1.html", "<!doctype html><html><head></head><body><a href=\"#frag1\">link</a></body></html>"},
{"public/blue/doc2.html", "<a href=\"%s/foobar.jpg\">Going</a>"},
{"public/sect/doc1.html", "<!doctype html><html><head></head><body><a href=\"#frag1\">link</a></body></html>"},
}
for _, test := range tests {
file, err := hugofs.Destination().Open(filepath.FromSlash(test.file))
if err != nil {
t.Fatalf("Unable to locate rendered content: %s", test.file)
}
content := helpers.ReaderToString(file)
expected := test.expected
if strings.Contains(expected, "%s") {
@ -583,9 +530,8 @@ func TestAbsURLify(t *testing.T) {
expected = strings.Replace(expected, baseURL, "", -1)
}
if content != expected {
t.Errorf("AbsURLify with baseURL %q content expected:\n%q\ngot\n%q", baseURL, expected, content)
}
assertFileContent(t, fs, test.file, true, expected)
}
}
}
@ -639,18 +585,16 @@ var weightedSources = []source.ByteSource{
func TestOrderedPages(t *testing.T) {
testCommonResetState()
hugofs.InitMemFs()
viper.Set("baseURL", "http://auth/bub")
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: weightedSources},
Language: helpers.NewDefaultLanguage(),
fs := hugofs.NewMem()
for _, src := range weightedSources {
writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
}
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to process site: %s", err)
}
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, 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)
@ -709,23 +653,17 @@ func TestGroupedPages(t *testing.T) {
}
}()
hugofs.InitMemFs()
viper.Set("baseURL", "http://auth/bub")
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: groupedSources},
Language: helpers.NewDefaultLanguage(),
}
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
fs := hugofs.NewMem()
writeSourcesToSource(t, "content", fs, groupedSources...)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
rbysection, err := s.RegularPages.GroupBy("Section", "desc")
if err != nil {
t.Fatalf("Unable to make PageGroup array: %s", err)
}
if rbysection[0].Key != "sect3" {
t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "sect3", rbysection[0].Key)
}
@ -885,7 +823,6 @@ Front Matter with weighted tags and categories`)
func TestWeightedTaxonomies(t *testing.T) {
testCommonResetState()
hugofs.InitMemFs()
sources := []source.ByteSource{
{Name: filepath.FromSlash("sect/doc1.md"), Content: pageWithWeightedTaxonomies2},
{Name: filepath.FromSlash("sect/doc2.md"), Content: pageWithWeightedTaxonomies1},
@ -898,15 +835,10 @@ func TestWeightedTaxonomies(t *testing.T) {
viper.Set("baseURL", "http://auth/bub")
viper.Set("taxonomies", taxonomies)
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: sources},
Language: helpers.NewDefaultLanguage(),
}
if err := buildSiteSkipRender(s); err != nil {
t.Fatalf("Failed to process site: %s", err)
}
fs := hugofs.NewMem()
writeSourcesToSource(t, "content", fs, sources...)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, 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)
@ -935,7 +867,6 @@ func findPage(site *Site, f string) *Page {
}
func setupLinkingMockSite(t *testing.T) *Site {
hugofs.InitMemFs()
sources := []source.ByteSource{
{Name: filepath.FromSlash("index.md"), Content: []byte("")},
{Name: filepath.FromSlash("rootfile.md"), Content: []byte("")},
@ -968,17 +899,10 @@ func setupLinkingMockSite(t *testing.T) *Site {
map[string]interface{}{
"sourceRelativeLinksProjectFolder": "/docs"})
site := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: sources},
Language: helpers.NewDefaultLanguage(),
}
fs := hugofs.NewMem()
writeSourcesToSource(t, "content", fs, sources...)
return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
if err := buildSiteSkipRender(site); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
return site
}
func TestRefLinking(t *testing.T) {

View File

@ -17,13 +17,13 @@ import (
"path/filepath"
"testing"
"github.com/spf13/hugo/helpers"
"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"
)
const slugDoc1 = "---\ntitle: slug doc 1\nslug: slug-doc-1\naliases:\n - sd1/foo/\n - sd2\n - sd3/\n - sd4.html\n---\nslug doc 1 content\n"
@ -62,7 +62,8 @@ func TestShouldNotAddTrailingSlashToBaseURL(t *testing.T) {
{"http://base.com", "http://base.com"}} {
viper.Set("baseURL", this.in)
s := NewSiteDefaultLang()
s, err := NewSiteDefaultLang()
require.NoError(t, err)
s.initializeSiteInfo()
if s.Info.BaseURL != template.URL(this.expected) {
@ -74,32 +75,27 @@ func TestShouldNotAddTrailingSlashToBaseURL(t *testing.T) {
func TestPageCount(t *testing.T) {
testCommonResetState()
hugofs.InitMemFs()
viper.Set("uglyURLs", false)
viper.Set("paginate", 10)
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: urlFakeSource},
Language: helpers.NewDefaultLanguage(),
}
if err := buildAndRenderSite(s, "indexes/blue.html", indexTemplate); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
_, err := hugofs.Destination().Open("public/blue")
fs := hugofs.NewMem()
writeSourcesToSource(t, "content", fs, urlFakeSource...)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
_, err := s.Fs.Destination.Open("public/blue")
if err != nil {
t.Errorf("No indexed rendered.")
}
for _, s := range []string{
for _, pth := range []string{
"public/sd1/foo/index.html",
"public/sd2/index.html",
"public/sd3/index.html",
"public/sd4.html",
} {
if _, err := hugofs.Destination().Open(filepath.FromSlash(s)); err != nil {
t.Errorf("No alias rendered: %s", s)
if _, err := s.Fs.Destination.Open(filepath.FromSlash(pth)); err != nil {
t.Errorf("No alias rendered: %s", pth)
}
}
}

View File

@ -18,8 +18,9 @@ import (
"reflect"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/tplapi"
"github.com/spf13/viper"
)
@ -45,24 +46,21 @@ func doTestSitemapOutput(t *testing.T, internal bool) {
viper.Set("baseURL", "http://auth/bub/")
s := &Site{
deps: newDeps(DepsCfg{}),
Source: &source.InMemorySource{ByteSource: weightedSources},
Language: helpers.NewDefaultLanguage(),
}
fs := hugofs.NewMem()
if internal {
if err := buildAndRenderSite(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
depsCfg := deps.DepsCfg{Fs: fs}
} else {
if err := buildAndRenderSite(s, "sitemap.xml", sitemapTemplate); err != nil {
t.Fatalf("Failed to build site: %s", err)
if !internal {
depsCfg.WithTemplate = func(templ tplapi.Template) error {
templ.AddTemplate("sitemap.xml", sitemapTemplate)
return nil
}
}
assertFileContent(t, "public/sitemap.xml", true,
writeSourcesToSource(t, "content", fs, weightedSources...)
s := buildSingleSite(t, depsCfg, BuildCfg{})
assertFileContent(t, s.Fs, "public/sitemap.xml", true,
// Regular page
" <loc>http://auth/bub/sect/doc1/</loc>",
// Home page

View File

@ -16,8 +16,6 @@ package hugolib
import (
"fmt"
"sort"
"github.com/spf13/hugo/helpers"
)
// The TaxonomyList is a list of all taxonomies and their values
@ -59,26 +57,15 @@ type OrderedTaxonomyEntry struct {
WeightedPages WeightedPages
}
// KeyPrep... Taxonomies should be case insensitive. Can make it easily conditional later.
func kp(in string) string {
return helpers.CurrentPathSpec().MakePathSanitized(in)
}
// Get the weighted pages for the given key.
func (i Taxonomy) Get(key string) WeightedPages {
if val, ok := i[key]; ok {
return val
}
return i[kp(key)]
return i[key]
}
// Count the weighted pages for the given key.
func (i Taxonomy) Count(key string) int { return len(i[kp(key)]) }
func (i Taxonomy) Count(key string) int { return len(i[key]) }
func (i Taxonomy) add(key string, w WeightedPage, pretty bool) {
if !pretty {
key = kp(key)
}
func (i Taxonomy) add(key string, w WeightedPage) {
i[key] = append(i[key], w)
}

View File

@ -18,6 +18,9 @@ import (
"reflect"
"testing"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
)
@ -31,16 +34,14 @@ func TestByCountOrderOfTaxonomies(t *testing.T) {
viper.Set("taxonomies", taxonomies)
writeSource(t, filepath.Join("content", "page.md"), pageYamlWithTaxonomiesA)
fs := hugofs.NewMem()
site := NewSiteDefaultLang()
writeSource(t, fs, filepath.Join("content", "page.md"), pageYamlWithTaxonomiesA)
if err := buildSiteSkipRender(site); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
st := make([]string, 0)
for _, t := range site.Taxonomies["tags"].ByCount() {
for _, t := range s.Taxonomies["tags"].ByCount() {
st = append(st, t.Name)
}

View File

@ -0,0 +1,99 @@
// 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 hugolib
import (
"fmt"
"path/filepath"
"testing"
"strings"
"github.com/spf13/viper"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
)
func TestAllTemplateEngines(t *testing.T) {
noOp := func(s string) string {
return s
}
amberFixer := func(s string) string {
fixed := strings.Replace(s, "{{ .Title", "{{ Title", -1)
fixed = strings.Replace(fixed, ".Content", "Content", -1)
fixed = strings.Replace(fixed, "{{", "#{", -1)
fixed = strings.Replace(fixed, "}}", "}", -1)
fixed = strings.Replace(fixed, `title "hello world"`, `title("hello world")`, -1)
return fixed
}
for _, config := range []struct {
suffix string
templateFixer func(s string) string
}{
{"amber", amberFixer},
{"html", noOp},
{"ace", noOp},
} {
doTestTemplateEngine(t, config.suffix, config.templateFixer)
}
}
func doTestTemplateEngine(t *testing.T, suffix string, templateFixer func(s string) string) {
testCommonResetState()
fs := hugofs.NewMem()
viper.SetFs(fs.Source)
writeSource(t, fs, filepath.Join("content", "p.md"), `
---
title: My Title
---
My Content
`)
t.Log("Testing", suffix)
templTemplate := `
p
|
| Page Title: {{ .Title }}
br
| Page Content: {{ .Content }}
br
| {{ title "hello world" }}
`
templ := templateFixer(templTemplate)
t.Log(templ)
writeSource(t, fs, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
assertFileContent(t, fs, filepath.Join("public", "p", "index.html"), true,
"Page Title: My Title",
"My Content",
"Hello World",
)
}

View File

@ -17,127 +17,133 @@ import (
"path/filepath"
"testing"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/viper"
)
func TestBaseGoTemplate(t *testing.T) {
var fs *hugofs.Fs
// Variants:
// 1. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
// 2. <current-path>/baseof.<suffix>
// 3. _default/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
// 4. _default/baseof.<suffix>
for i, this := range []struct {
for _, this := range []struct {
setup func(t *testing.T)
assert func(t *testing.T)
}{
{
// Variant 1
func(t *testing.T) {
writeSource(t, filepath.Join("layouts", "section", "sect-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
writeSource(t, fs, filepath.Join("layouts", "section", "sect-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, fs, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
},
func(t *testing.T) {
assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
},
},
{
// Variant 2
func(t *testing.T) {
writeSource(t, filepath.Join("layouts", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, filepath.Join("layouts", "index.html"), `{{define "main"}}index{{ end }}`)
writeSource(t, fs, filepath.Join("layouts", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, fs, filepath.Join("layouts", "index.html"), `{{define "main"}}index{{ end }}`)
},
func(t *testing.T) {
assertFileContent(t, filepath.Join("public", "index.html"), false, "Base: index")
assertFileContent(t, fs, filepath.Join("public", "index.html"), false, "Base: index")
},
},
{
// Variant 3
func(t *testing.T) {
writeSource(t, filepath.Join("layouts", "_default", "list-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, filepath.Join("layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
writeSource(t, fs, filepath.Join("layouts", "_default", "list-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
},
func(t *testing.T) {
assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: list")
assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: list")
},
},
{
// Variant 4
func(t *testing.T) {
writeSource(t, filepath.Join("layouts", "_default", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, filepath.Join("layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
writeSource(t, fs, filepath.Join("layouts", "_default", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
},
func(t *testing.T) {
assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: list")
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")
writeSource(t, filepath.Join("layouts", "section", "sect-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, filepath.Join("themes", "mytheme", "layouts", "section", "sect-baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
writeSource(t, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
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, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
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")
writeSource(t, filepath.Join("themes", "mytheme", "layouts", "section", "sect-baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
writeSource(t, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ 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, filepath.Join("public", "sect", "index.html"), false, "Base Theme: sect")
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")
writeSource(t, filepath.Join("layouts", "_default", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
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, filepath.Join("public", "sect", "index.html"), false, "Base: list")
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")
writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "list.html"), `{{define "main"}}list{{ 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, filepath.Join("public", "sect", "index.html"), false, "Base Theme: list")
assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base Theme: list")
},
},
} {
testCommonResetState()
writeSource(t, filepath.Join("content", "sect", "page.md"), `---
fs = hugofs.NewMem()
writeSource(t, fs, filepath.Join("content", "sect", "page.md"), `---
title: Template test
---
Some content
`)
this.setup(t)
if err := buildAndRenderSite(NewSiteDefaultLang()); err != nil {
t.Fatalf("[%d] Failed to build site: %s", i, err)
}
buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
this.assert(t)

View File

@ -0,0 +1,53 @@
package hugolib
import (
"path/filepath"
"testing"
"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"
)
func newTestDepsConfig() deps.DepsCfg {
return deps.DepsCfg{Fs: hugofs.NewMem()}
}
func newTestPathSpec() *helpers.PathSpec {
return helpers.NewPathSpec(hugofs.NewMem(), viper.GetViper())
}
func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tplapi.Template) error {
return func(templ tplapi.Template) error {
for i := 0; i < len(additionalTemplates); i += 2 {
err := templ.AddTemplate(additionalTemplates[i], additionalTemplates[i+1])
if err != nil {
return err
}
}
return nil
}
}
func buildSingleSite(t *testing.T, depsCfg deps.DepsCfg, buildCfg BuildCfg) *Site {
h, err := NewHugoSitesFromConfiguration(depsCfg)
require.NoError(t, err)
require.Len(t, h.Sites, 1)
require.NoError(t, h.Build(buildCfg))
return h.Sites[0]
}
func writeSourcesToSource(t *testing.T, base string, fs *hugofs.Fs, sources ...source.ByteSource) {
for _, src := range sources {
writeSource(t, fs, filepath.Join(base, src.Name), string(src.Content))
}
}

View File

@ -38,6 +38,12 @@ type Filesystem struct {
files []*File
Base string
AvoidPaths []string
fs *hugofs.Fs
}
func NewFilesystem(fs *hugofs.Fs, base string, avoidPaths ...string) *Filesystem {
return &Filesystem{fs: fs, Base: base, AvoidPaths: avoidPaths}
}
func (f *Filesystem) FilesByExts(exts ...string) []*File {
@ -92,7 +98,7 @@ func (f *Filesystem) captureFiles() {
return err
}
if b {
rd, err := NewLazyFileReader(hugofs.Source(), filePath)
rd, err := NewLazyFileReader(f.fs.Source, filePath)
if err != nil {
return err
}
@ -101,7 +107,10 @@ func (f *Filesystem) captureFiles() {
return err
}
err := helpers.SymbolicWalk(hugofs.Source(), f.Base, walker)
if f.fs == nil {
panic("Must have a fs")
}
err := helpers.SymbolicWalk(f.fs.Source, f.Base, walker)
if err != nil {
jww.ERROR.Println(err)
@ -119,7 +128,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 := hugofs.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

View File

@ -19,10 +19,12 @@ import (
"runtime"
"strings"
"testing"
"github.com/spf13/hugo/hugofs"
)
func TestEmptySourceFilesystem(t *testing.T) {
src := &Filesystem{Base: "Empty"}
src := NewFilesystem(hugofs.NewMem(), "Empty")
if len(src.Files()) != 0 {
t.Errorf("new filesystem should contain 0 files.")
}
@ -37,13 +39,12 @@ type TestPath struct {
}
func TestAddFile(t *testing.T) {
fs := hugofs.NewMem()
tests := platformPaths
for _, test := range tests {
base := platformBase
srcDefault := new(Filesystem)
srcWithBase := &Filesystem{
Base: base,
}
srcDefault := NewFilesystem(fs, "")
srcWithBase := NewFilesystem(fs, base)
for _, src := range []*Filesystem{srcDefault, srcWithBase} {
@ -99,8 +100,10 @@ func TestUnicodeNorm(t *testing.T) {
{NFC: "é", NFD: "\x65\xcc\x81"},
}
fs := hugofs.NewMem()
for _, path := range paths {
src := new(Filesystem)
src := NewFilesystem(fs, "")
_ = src.add(path.NFD, strings.NewReader(""))
f := src.Files()[0]
if f.BaseFileName() != path.NFC {

View File

@ -41,6 +41,8 @@ type Output interface {
type Filesystem struct {
PublishDir string
Fs *hugofs.Fs
}
func (fs *Filesystem) Publish(path string, r io.Reader) (err error) {
@ -49,7 +51,7 @@ func (fs *Filesystem) Publish(path string, r io.Reader) (err error) {
return
}
return helpers.WriteToDisk(translated, r, hugofs.Destination())
return helpers.WriteToDisk(translated, r, fs.Fs.Destination)
}
func (fs *Filesystem) Translate(src string) (dest string, err error) {

View File

@ -46,6 +46,8 @@ type HTMLRedirectAlias struct {
PublishDir string
Templates *template.Template
AllowRoot bool // for the language redirects
Fs *hugofs.Fs
}
func (h *HTMLRedirectAlias) Translate(alias string) (aliasPath string, err error) {
@ -145,5 +147,5 @@ func (h *HTMLRedirectAlias) Publish(path string, permalink string, page interfac
return
}
return helpers.WriteToDisk(path, buffer, hugofs.Destination())
return helpers.WriteToDisk(path, buffer, h.Fs.Destination)
}

View File

@ -35,6 +35,8 @@ type PagePub struct {
// LangDir will contain the subdir for the language, i.e. "en", "de" etc.
// It will be empty if the site is rendered in root.
LangDir string
Fs *hugofs.Fs
}
func (pp *PagePub) Publish(path string, r io.Reader) (err error) {
@ -44,7 +46,7 @@ func (pp *PagePub) Publish(path string, r io.Reader) (err error) {
return
}
return helpers.WriteToDisk(translated, r, hugofs.Destination())
return helpers.WriteToDisk(translated, r, pp.Fs.Destination)
}
func (pp *PagePub) Translate(src string) (dest string, err error) {

View File

@ -16,9 +16,13 @@ package target
import (
"path/filepath"
"testing"
"github.com/spf13/hugo/hugofs"
)
func TestPageTranslator(t *testing.T) {
fs := hugofs.NewMem()
tests := []struct {
content string
expected string
@ -37,7 +41,7 @@ func TestPageTranslator(t *testing.T) {
}
for _, test := range tests {
f := new(PagePub)
f := &PagePub{Fs: fs}
dest, err := f.Translate(filepath.FromSlash(test.content))
expected := filepath.FromSlash(test.expected)
if err != nil {

42
tpl/amber_compiler.go Normal file
View File

@ -0,0 +1,42 @@
// 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 tpl
import (
"html/template"
"github.com/eknkc/amber"
)
func (gt *GoHTMLTemplate) CompileAmberWithTemplate(b []byte, path string, t *template.Template) (*template.Template, error) {
c := amber.New()
if err := c.ParseData(b, path); err != nil {
return nil, err
}
data, err := c.CompileString()
if err != nil {
return nil, err
}
tpl, err := t.Funcs(gt.amberFuncMap).Parse(data)
if err != nil {
return nil, err
}
return tpl, nil
}

View File

@ -24,33 +24,12 @@ import (
"github.com/eknkc/amber"
"github.com/spf13/afero"
bp "github.com/spf13/hugo/bufferpool"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
jww "github.com/spf13/jwalterweatherman"
"github.com/yosssi/ace"
)
// TODO(bep) globals get rid of the rest of the jww.ERR etc.
//var tmpl *GoHTMLTemplate
// TODO(bep) an interface with hundreds of methods ... remove it.
// And unexport most of these methods.
type Template interface {
ExecuteTemplate(wr io.Writer, name string, data interface{}) error
Lookup(name string) *template.Template
Templates() []*template.Template
New(name string) *template.Template
GetClone() *template.Template
LoadTemplates(absPath string)
LoadTemplatesWithPrefix(absPath, prefix string)
AddTemplate(name, tpl string) error
AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error
AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error
AddInternalTemplate(prefix, name, tpl string) error
AddInternalShortcode(name, tpl string) error
PrintErrors()
Funcs(funcMap template.FuncMap)
}
type templateErr struct {
name string
@ -70,52 +49,105 @@ type GoHTMLTemplate struct {
funcster *templateFuncster
// TODO(bep) globals template
log *jww.Notepad
amberFuncMap template.FuncMap
*deps.Deps
}
// New returns a new Hugo Template System
type TemplateProvider struct{}
var DefaultTemplateProvider *TemplateProvider
// Update updates the Hugo Template System in the provided Deps.
// with all the additional features, templates & functions
func New(logger *jww.Notepad, withTemplate ...func(templ Template) error) *GoHTMLTemplate {
func (*TemplateProvider) Update(deps *deps.Deps) error {
// TODO(bep) check that this isn't called too many times.
tmpl := &GoHTMLTemplate{
Template: template.New(""),
overlays: make(map[string]*template.Template),
errors: make([]*templateErr, 0),
log: logger,
Deps: deps,
}
tmpl.funcster = newTemplateFuncster(tmpl)
deps.Tmpl = tmpl
// The URL funcs in the funcMap is somewhat language dependent,
// so we need to wait until the language and site config is loaded.
// TODO(bep) globals
tmpl.funcster.initFuncMap()
// TODO(bep) globals
for k, v := range tmpl.funcster.funcMap {
amber.FuncMap[k] = v
}
tmpl.initFuncs(deps)
tmpl.LoadEmbedded()
for _, wt := range withTemplate {
err := wt(tmpl)
if deps.WithTemplate != nil {
err := deps.WithTemplate(tmpl)
if err != nil {
tmpl.errors = append(tmpl.errors, &templateErr{"init", err})
}
}
tmpl.markReady()
tmpl.MarkReady()
return nil
}
// Clone clones
func (*TemplateProvider) Clone(d *deps.Deps) error {
t := d.Tmpl.(*GoHTMLTemplate)
// 1. Clone the clone with new template funcs
// 2. Clone any overlays with new template funcs
tmpl := &GoHTMLTemplate{
Template: template.Must(t.Template.Clone()),
overlays: make(map[string]*template.Template),
errors: make([]*templateErr, 0),
Deps: d,
}
d.Tmpl = tmpl
tmpl.initFuncs(d)
for k, v := range t.overlays {
vc := template.Must(v.Clone())
vc.Funcs(tmpl.funcster.funcMap)
tmpl.overlays[k] = vc
}
tmpl.MarkReady()
return nil
}
func (t *GoHTMLTemplate) initFuncs(d *deps.Deps) {
t.funcster = newTemplateFuncster(d)
// The URL funcs in the funcMap is somewhat language dependent,
// so we need to wait until the language and site config is loaded.
t.funcster.initFuncMap()
t.amberFuncMap = template.FuncMap{}
for k, v := range amber.FuncMap {
t.amberFuncMap[k] = v
}
for k, v := range t.funcster.funcMap {
t.amberFuncMap[k] = v
// Hacky, but we need to make sure that the func names are in the global map.
amber.FuncMap[k] = func() string {
panic("should never be invoked")
return ""
}
}
return tmpl
}
func (t *GoHTMLTemplate) Funcs(funcMap template.FuncMap) {
t.Template.Funcs(funcMap)
}
func (t *GoHTMLTemplate) partial(name string, contextList ...interface{}) template.HTML {
func (t *GoHTMLTemplate) Partial(name string, contextList ...interface{}) template.HTML {
if strings.HasPrefix("partials/", name) {
name = name[8:]
}
@ -147,8 +179,8 @@ func (t *GoHTMLTemplate) executeTemplate(context interface{}, w io.Writer, layou
}
}
if !worked {
t.log.ERROR.Println("Unable to render", layouts)
t.log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts)
t.Log.ERROR.Println("Unable to render", layouts)
t.Log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts)
}
}
@ -186,9 +218,9 @@ func (t *GoHTMLTemplate) LoadEmbedded() {
t.EmbedTemplates()
}
// markReady marks the template as "ready for execution". No changes allowed
// MarkReady marks the template as "ready for execution". No changes allowed
// after this is set.
func (t *GoHTMLTemplate) markReady() {
func (t *GoHTMLTemplate) MarkReady() {
if t.clone == nil {
t.clone = template.Must(t.Template.Clone())
}
@ -244,7 +276,7 @@ func (t *GoHTMLTemplate) AddTemplateFileWithMaster(name, overlayFilename, master
masterTpl := t.Lookup(masterFilename)
if masterTpl == nil {
b, err := afero.ReadFile(hugofs.Source(), masterFilename)
b, err := afero.ReadFile(t.Fs.Source, masterFilename)
if err != nil {
return err
}
@ -257,7 +289,7 @@ func (t *GoHTMLTemplate) AddTemplateFileWithMaster(name, overlayFilename, master
}
}
b, err := afero.ReadFile(hugofs.Source(), overlayFilename)
b, err := afero.ReadFile(t.Fs.Source, overlayFilename)
if err != nil {
return err
}
@ -315,19 +347,13 @@ func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) er
switch ext {
case ".amber":
templateName := strings.TrimSuffix(name, filepath.Ext(name)) + ".html"
compiler := amber.New()
b, err := afero.ReadFile(hugofs.Source(), path)
b, err := afero.ReadFile(t.Fs.Source, path)
if err != nil {
return err
}
// Parse the input data
if err := compiler.ParseData(b, path); err != nil {
return err
}
templ, err := compiler.CompileWithTemplate(t.New(templateName))
templ, err := t.CompileAmberWithTemplate(b, path, t.New(templateName))
if err != nil {
return err
}
@ -335,14 +361,14 @@ func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) er
return applyTemplateTransformers(templ)
case ".ace":
var innerContent, baseContent []byte
innerContent, err := afero.ReadFile(hugofs.Source(), path)
innerContent, err := afero.ReadFile(t.Fs.Source, path)
if err != nil {
return err
}
if baseTemplatePath != "" {
baseContent, err = afero.ReadFile(hugofs.Source(), baseTemplatePath)
baseContent, err = afero.ReadFile(t.Fs.Source, baseTemplatePath)
if err != nil {
return err
}
@ -355,13 +381,13 @@ func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) er
return t.AddTemplateFileWithMaster(name, path, baseTemplatePath)
}
b, err := afero.ReadFile(hugofs.Source(), path)
b, err := afero.ReadFile(t.Fs.Source, path)
if err != nil {
return err
}
t.log.DEBUG.Printf("Add template file from path %s", path)
t.Log.DEBUG.Printf("Add template file from path %s", path)
return t.AddTemplate(name, string(b))
}
@ -391,25 +417,25 @@ func isBaseTemplate(path string) bool {
}
func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) {
t.log.DEBUG.Printf("Load templates from path %q prefix %q", absPath, prefix)
t.Log.DEBUG.Printf("Load templates from path %q prefix %q", absPath, prefix)
walker := func(path string, fi os.FileInfo, err error) error {
if err != nil {
return nil
}
t.log.DEBUG.Println("Template path", path)
t.Log.DEBUG.Println("Template path", path)
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
link, err := filepath.EvalSymlinks(absPath)
if err != nil {
t.log.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", absPath, err)
t.Log.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", absPath, err)
return nil
}
linkfi, err := hugofs.Source().Stat(link)
linkfi, err := t.Fs.Source.Stat(link)
if err != nil {
t.log.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
t.Log.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
return nil
}
if !linkfi.Mode().IsRegular() {
t.log.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", absPath)
t.Log.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", absPath)
}
return nil
}
@ -441,7 +467,7 @@ func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) {
// This may be a view that shouldn't have base template
// Have to look inside it to make sure
needsBase, err := helpers.FileContainsAny(path, innerMarkers, hugofs.Source())
needsBase, err := helpers.FileContainsAny(path, innerMarkers, t.Fs.Source)
if err != nil {
return err
}
@ -482,7 +508,7 @@ func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) {
for _, pair := range pairsToCheck {
pathsToCheck := basePathsToCheck(pair, layoutDir, themeDir)
for _, pathToCheck := range pathsToCheck {
if ok, err := helpers.Exists(pathToCheck, hugofs.Source()); err == nil && ok {
if ok, err := helpers.Exists(pathToCheck, t.Fs.Source); err == nil && ok {
baseTemplatePath = pathToCheck
break Loop
}
@ -492,14 +518,14 @@ func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) {
}
if err := t.AddTemplateFile(tplName, baseTemplatePath, path); err != nil {
t.log.ERROR.Printf("Failed to add template %s in path %s: %s", tplName, path, err)
t.Log.ERROR.Printf("Failed to add template %s in path %s: %s", tplName, path, err)
}
}
return nil
}
if err := helpers.SymbolicWalk(hugofs.Source(), absPath, walker); err != nil {
t.log.ERROR.Printf("Failed to load templates: %s", err)
if err := helpers.SymbolicWalk(t.Fs.Source, absPath, walker); err != nil {
t.Log.ERROR.Printf("Failed to load templates: %s", err)
}
}
@ -526,6 +552,6 @@ func (t *GoHTMLTemplate) LoadTemplates(absPath string) {
func (t *GoHTMLTemplate) PrintErrors() {
for i, e := range t.errors {
t.log.ERROR.Println(i, ":", e.err)
t.Log.ERROR.Println(i, ":", e.err)
}
}

View File

@ -43,8 +43,8 @@ import (
"github.com/bep/inflect"
"github.com/spf13/afero"
"github.com/spf13/cast"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
@ -56,14 +56,15 @@ import (
// Some of the template funcs are'nt entirely stateless.
type templateFuncster struct {
t *GoHTMLTemplate
funcMap template.FuncMap
cachedPartials partialCache
*deps.Deps
}
func newTemplateFuncster(t *GoHTMLTemplate) *templateFuncster {
func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
return &templateFuncster{
t: t,
Deps: deps,
cachedPartials: partialCache{p: make(map[string]template.HTML)},
}
}
@ -424,7 +425,7 @@ func resetImageConfigCache() {
// imageConfig returns the image.Config for the specified path relative to the
// working directory. resetImageConfigCache must be run beforehand.
func imageConfig(path interface{}) (image.Config, error) {
func (t *templateFuncster) imageConfig(path interface{}) (image.Config, error) {
filename, err := cast.ToStringE(path)
if err != nil {
return image.Config{}, err
@ -443,7 +444,7 @@ func imageConfig(path interface{}) (image.Config, error) {
return config, nil
}
f, err := hugofs.WorkingDir().Open(filename)
f, err := t.Fs.WorkingDir.Open(filename)
if err != nil {
return image.Config{}, err
}
@ -1013,7 +1014,7 @@ func where(seq, key interface{}, args ...interface{}) (interface{}, error) {
}
// apply takes a map, array, or slice and returns a new slice with the function fname applied over it.
func (tf *templateFuncster) apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) {
func (t *templateFuncster) apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) {
if seq == nil {
return make([]interface{}, 0), nil
}
@ -1028,7 +1029,7 @@ func (tf *templateFuncster) apply(seq interface{}, fname string, args ...interfa
return nil, errors.New("can't iterate over a nil value")
}
fn, found := tf.funcMap[fname]
fn, found := t.funcMap[fname]
if !found {
return nil, errors.New("can't find function " + fname)
}
@ -1528,26 +1529,27 @@ type partialCache struct {
// Get retrieves partial output from the cache based upon the partial name.
// If the partial is not found in the cache, the partial is rendered and added
// to the cache.
func (tf *templateFuncster) Get(key, name string, context interface{}) (p template.HTML) {
func (t *templateFuncster) Get(key, name string, context interface{}) (p template.HTML) {
var ok bool
tf.cachedPartials.RLock()
p, ok = tf.cachedPartials.p[key]
tf.cachedPartials.RUnlock()
t.cachedPartials.RLock()
p, ok = t.cachedPartials.p[key]
t.cachedPartials.RUnlock()
if ok {
return p
}
tf.cachedPartials.Lock()
if p, ok = tf.cachedPartials.p[key]; !ok {
tf.cachedPartials.Unlock()
p = tf.t.partial(name, context)
t.cachedPartials.Lock()
if p, ok = t.cachedPartials.p[key]; !ok {
t.cachedPartials.Unlock()
p = t.Tmpl.Partial(name, context)
t.cachedPartials.Lock()
t.cachedPartials.p[key] = p
tf.cachedPartials.Lock()
tf.cachedPartials.p[key] = p
}
tf.cachedPartials.Unlock()
t.cachedPartials.Unlock()
return p
}
@ -1556,14 +1558,14 @@ func (tf *templateFuncster) Get(key, name string, context interface{}) (p templa
// string parameter (a string slice actually, but be only use a variadic
// argument to make it optional) can be passed so that a given partial can have
// multiple uses. The cache is created with name+variant as the key.
func (tf *templateFuncster) partialCached(name string, context interface{}, variant ...string) template.HTML {
func (t *templateFuncster) partialCached(name string, context interface{}, variant ...string) template.HTML {
key := name
if len(variant) > 0 {
for i := 0; i < len(variant); i++ {
key += variant[i]
}
}
return tf.Get(key, name, context)
return t.Get(key, name, context)
}
// regexpCache represents a cache of regexp objects protected by a mutex.
@ -1814,23 +1816,23 @@ func readFile(fs *afero.BasePathFs, filename string) (string, error) {
// configured WorkingDir.
// It returns the contents as a string.
// There is a upper size limit set at 1 megabytes.
func readFileFromWorkingDir(i interface{}) (string, error) {
func (t *templateFuncster) readFileFromWorkingDir(i interface{}) (string, error) {
s, err := cast.ToStringE(i)
if err != nil {
return "", err
}
return readFile(hugofs.WorkingDir(), s)
return readFile(t.Fs.WorkingDir, s)
}
// readDirFromWorkingDir listst the directory content relative to the
// configured WorkingDir.
func readDirFromWorkingDir(i interface{}) ([]os.FileInfo, error) {
func (t *templateFuncster) readDirFromWorkingDir(i interface{}) ([]os.FileInfo, error) {
path, err := cast.ToStringE(i)
if err != nil {
return nil, err
}
list, err := afero.ReadDir(hugofs.WorkingDir(), path)
list, err := afero.ReadDir(t.Fs.WorkingDir, path)
if err != nil {
return nil, fmt.Errorf("Failed to read Directory %s with error message %s", path, err)
@ -2074,20 +2076,20 @@ func htmlUnescape(in interface{}) (string, error) {
return html.UnescapeString(conv), nil
}
func absURL(a interface{}) (template.HTML, error) {
func (t *templateFuncster) absURL(a interface{}) (template.HTML, error) {
s, err := cast.ToStringE(a)
if err != nil {
return "", nil
}
return template.HTML(helpers.CurrentPathSpec().AbsURL(s, false)), nil
return template.HTML(t.PathSpec.AbsURL(s, false)), nil
}
func relURL(a interface{}) (template.HTML, error) {
func (t *templateFuncster) relURL(a interface{}) (template.HTML, error) {
s, err := cast.ToStringE(a)
if err != nil {
return "", nil
}
return template.HTML(helpers.CurrentPathSpec().RelURL(s, false)), nil
return template.HTML(t.PathSpec.RelURL(s, false)), nil
}
// getenv retrieves the value of the environment variable named by the key.
@ -2101,19 +2103,19 @@ func getenv(key interface{}) (string, error) {
return os.Getenv(skey), nil
}
func (tf *templateFuncster) initFuncMap() {
func (t *templateFuncster) initFuncMap() {
funcMap := template.FuncMap{
"absURL": absURL,
"absURL": t.absURL,
"absLangURL": func(i interface{}) (template.HTML, error) {
s, err := cast.ToStringE(i)
if err != nil {
return "", err
}
return template.HTML(helpers.CurrentPathSpec().AbsURL(s, true)), nil
return template.HTML(t.PathSpec.AbsURL(s, true)), nil
},
"add": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '+') },
"after": after,
"apply": tf.apply,
"apply": t.apply,
"base64Decode": base64Decode,
"base64Encode": base64Encode,
"chomp": chomp,
@ -2130,8 +2132,8 @@ func (tf *templateFuncster) initFuncMap() {
"findRE": findRE,
"first": first,
"ge": ge,
"getCSV": getCSV,
"getJSON": getJSON,
"getCSV": t.getCSV,
"getJSON": t.getJSON,
"getenv": getenv,
"gt": gt,
"hasPrefix": hasPrefix,
@ -2139,7 +2141,7 @@ func (tf *templateFuncster) initFuncMap() {
"htmlEscape": htmlEscape,
"htmlUnescape": htmlUnescape,
"humanize": humanize,
"imageConfig": imageConfig,
"imageConfig": t.imageConfig,
"in": in,
"index": index,
"int": func(v interface{}) (int, error) { return cast.ToIntE(v) },
@ -2158,21 +2160,21 @@ func (tf *templateFuncster) initFuncMap() {
"mul": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '*') },
"ne": ne,
"now": func() time.Time { return time.Now() },
"partial": tf.t.partial,
"partialCached": tf.partialCached,
"partial": t.Tmpl.Partial,
"partialCached": t.partialCached,
"plainify": plainify,
"pluralize": pluralize,
"querify": querify,
"readDir": readDirFromWorkingDir,
"readFile": readFileFromWorkingDir,
"readDir": t.readDirFromWorkingDir,
"readFile": t.readFileFromWorkingDir,
"ref": ref,
"relURL": relURL,
"relURL": t.relURL,
"relLangURL": func(i interface{}) (template.HTML, error) {
s, err := cast.ToStringE(i)
if err != nil {
return "", err
}
return template.HTML(helpers.CurrentPathSpec().RelURL(s, true)), nil
return template.HTML(t.PathSpec.RelURL(s, true)), nil
},
"relref": relRef,
"replace": replace,
@ -2201,12 +2203,12 @@ func (tf *templateFuncster) initFuncMap() {
"trim": trim,
"truncate": truncate,
"upper": upper,
"urlize": helpers.CurrentPathSpec().URLize,
"urlize": t.PathSpec.URLize,
"where": where,
"i18n": i18nTranslate,
"T": i18nTranslate,
}
tf.funcMap = funcMap
tf.t.Funcs(funcMap)
t.funcMap = funcMap
t.Tmpl.Funcs(funcMap)
}

View File

@ -31,6 +31,9 @@ import (
"testing"
"time"
"github.com/spf13/hugo/tplapi"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"io/ioutil"
@ -43,9 +46,17 @@ import (
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var logger = jww.NewNotepad(jww.LevelFatal, jww.LevelFatal, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
var (
logger = jww.NewNotepad(jww.LevelFatal, jww.LevelFatal, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
defaultDepsConfig = deps.DepsCfg{
Language: helpers.NewLanguage("en"),
Logger: logger,
TemplateProvider: DefaultTemplateProvider,
}
)
type tstNoStringer struct {
}
@ -80,8 +91,7 @@ func tstInitTemplates() {
func TestFuncsInTemplate(t *testing.T) {
viper.Reset()
defer viper.Reset()
testReset()
workingDir := "/home/hugo"
@ -89,10 +99,9 @@ func TestFuncsInTemplate(t *testing.T) {
viper.Set("currentContentLanguage", helpers.NewDefaultLanguage())
viper.Set("multilingual", true)
fs := &afero.MemMapFs{}
hugofs.InitFs(fs)
fs := hugofs.NewMem()
afero.WriteFile(fs, filepath.Join(workingDir, "README.txt"), []byte("Hugo Rocks!"), 0755)
afero.WriteFile(fs.Source, filepath.Join(workingDir, "README.txt"), []byte("Hugo Rocks!"), 0755)
// Add the examples from the docs: As a smoke test and to make sure the examples work.
// TODO(bep): docs: fix title example
@ -244,7 +253,7 @@ urlize: bat-man
`
var b bytes.Buffer
templ, err := New(logger).New("test").Parse(in)
var data struct {
Title string
Section string
@ -259,11 +268,21 @@ urlize: bat-man
tstInitTemplates()
if err != nil {
t.Fatal("Got error on parse", err)
config := defaultDepsConfig
config.WithTemplate = func(templ tplapi.Template) error {
if _, err := templ.New("test").Parse(in); err != nil {
t.Fatal("Got error on parse", err)
}
return nil
}
config.Fs = fs
d := deps.New(config)
if err := d.LoadTemplates(); err != nil {
t.Fatal(err)
}
err = templ.Execute(&b, &data)
err := d.Tmpl.Lookup("test").Execute(&b, &data)
if err != nil {
t.Fatal("Got error on execute", err)
@ -624,15 +643,13 @@ func blankImage(width, height int) []byte {
}
func TestImageConfig(t *testing.T) {
viper.Reset()
defer viper.Reset()
testReset()
workingDir := "/home/hugo"
viper.Set("workingDir", workingDir)
fs := &afero.MemMapFs{}
hugofs.InitFs(fs)
f := newTestFuncster()
for i, this := range []struct {
resetCache bool
@ -692,13 +709,13 @@ func TestImageConfig(t *testing.T) {
},
},
} {
afero.WriteFile(fs, filepath.Join(workingDir, this.path), this.input, 0755)
afero.WriteFile(f.Fs.Source, filepath.Join(workingDir, this.path), this.input, 0755)
if this.resetCache {
resetImageConfigCache()
}
result, err := imageConfig(this.path)
result, err := f.imageConfig(this.path)
if err != nil {
t.Errorf("imageConfig returned error: %s", err)
}
@ -712,15 +729,15 @@ func TestImageConfig(t *testing.T) {
}
}
if _, err := imageConfig(t); err == nil {
if _, err := f.imageConfig(t); err == nil {
t.Error("Expected error from imageConfig when passed invalid path")
}
if _, err := imageConfig("non-existent.png"); err == nil {
if _, err := f.imageConfig("non-existent.png"); err == nil {
t.Error("Expected error from imageConfig when passed non-existent file")
}
if _, err := imageConfig(""); err == nil {
if _, err := f.imageConfig(""); err == nil {
t.Error("Expected error from imageConfig when passed empty path")
}
@ -2381,14 +2398,11 @@ func TestDefault(t *testing.T) {
{map[string]string{"foo": "dog"}, `{{ default "nope" .foo "extra" }}`, ``, false},
{map[string]interface{}{"images": []string{}}, `{{ default "default.jpg" (index .images 0) }}`, `default.jpg`, true},
} {
tmpl, err := New(logger).New("test").Parse(this.tpl)
if err != nil {
t.Errorf("[%d] unable to create new html template %q: %s", i, this.tpl, err)
continue
}
tmpl := newTestTemplate(t, "test", this.tpl)
buf := new(bytes.Buffer)
err = tmpl.Execute(buf, this.input)
err := tmpl.Execute(buf, this.input)
if (err == nil) != this.ok {
t.Errorf("[%d] execute template returned unexpected error: %s", i, err)
continue
@ -2520,6 +2534,7 @@ func TestSafeCSS(t *testing.T) {
}
}
// TODO(bep) what is this? Also look above.
func TestSafeJS(t *testing.T) {
for i, this := range []struct {
str string
@ -2560,6 +2575,7 @@ func TestSafeJS(t *testing.T) {
}
}
// TODO(bep) what is this?
func TestSafeURL(t *testing.T) {
for i, this := range []struct {
str string
@ -2716,18 +2732,16 @@ func TestSHA256(t *testing.T) {
}
func TestReadFile(t *testing.T) {
viper.Reset()
defer viper.Reset()
testReset()
workingDir := "/home/hugo"
viper.Set("workingDir", workingDir)
fs := &afero.MemMapFs{}
hugofs.InitFs(fs)
f := newTestFuncster()
afero.WriteFile(fs, filepath.Join(workingDir, "/f/f1.txt"), []byte("f1-content"), 0755)
afero.WriteFile(fs, filepath.Join("/home", "f2.txt"), []byte("f2-content"), 0755)
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)
for i, this := range []struct {
filename string
@ -2739,7 +2753,7 @@ func TestReadFile(t *testing.T) {
{filepath.FromSlash("f/f1.txt"), "f1-content"},
{filepath.FromSlash("../f2.txt"), false},
} {
result, err := readFileFromWorkingDir(this.filename)
result, err := f.readFileFromWorkingDir(this.filename)
if b, ok := this.expect.(bool); ok && !b {
if err == nil {
t.Errorf("[%d] readFile didn't return an expected error", i)
@ -2770,8 +2784,6 @@ func TestPartialCached(t *testing.T) {
{"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . "%s" }}`, "header"},
}
results := make(map[string]string, len(testCases))
var data struct {
Title string
Section string
@ -2791,26 +2803,32 @@ func TestPartialCached(t *testing.T) {
tmp = tc.tmpl
}
tmpl, err := New(logger).New("testroot").Parse(tmp)
if err != nil {
t.Fatalf("[%d] unable to create new html template: %s", i, err)
defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
err := templ.AddTemplate("testroot", tmp)
if err != nil {
return err
}
err = templ.AddTemplate("partials/"+tc.name, tc.partial)
if err != nil {
return err
}
return nil
}
if tmpl == nil {
t.Fatalf("[%d] tmpl should not be nil!", i)
}
tmpl.New("partials/" + tc.name).Parse(tc.partial)
de := deps.New(defaultDepsConfig)
require.NoError(t, de.LoadTemplates())
buf := new(bytes.Buffer)
err = tmpl.Execute(buf, &data)
templ := de.Tmpl.Lookup("testroot")
err := templ.Execute(buf, &data)
if err != nil {
t.Fatalf("[%d] error executing template: %s", i, err)
}
for j := 0; j < 10; j++ {
buf2 := new(bytes.Buffer)
err = tmpl.Execute(buf2, nil)
err := templ.Execute(buf2, nil)
if err != nil {
t.Fatalf("[%d] error executing template 2nd time: %s", i, err)
}
@ -2819,33 +2837,33 @@ func TestPartialCached(t *testing.T) {
t.Fatalf("[%d] cached results do not match:\nResult 1:\n%q\nResult 2:\n%q", i, buf, buf2)
}
}
// double-check against previous test cases of the same variant
previous, ok := results[tc.name+tc.variant]
if !ok {
results[tc.name+tc.variant] = buf.String()
} else {
if previous != buf.String() {
t.Errorf("[%d] cached variant differs from previous rendering; got:\n%q\nwant:\n%q", i, buf.String(), previous)
}
}
}
}
func BenchmarkPartial(b *testing.B) {
tstInitTemplates()
tmpl, err := New(logger).New("testroot").Parse(`{{ partial "bench1" . }}`)
if err != nil {
b.Fatalf("unable to create new html template: %s", err)
defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
err := templ.AddTemplate("testroot", `{{ partial "bench1" . }}`)
if err != nil {
return err
}
err = templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`)
if err != nil {
return err
}
return nil
}
tmpl.New("partials/bench1").Parse(`{{ shuffle (seq 1 10) }}`)
de := deps.New(defaultDepsConfig)
require.NoError(b, de.LoadTemplates())
buf := new(bytes.Buffer)
tmpl := de.Tmpl.Lookup("testroot")
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err = tmpl.Execute(buf, nil); err != nil {
if err := tmpl.Execute(buf, nil); err != nil {
b.Fatalf("error executing template: %s", err)
}
buf.Reset()
@ -2853,38 +2871,29 @@ func BenchmarkPartial(b *testing.B) {
}
func BenchmarkPartialCached(b *testing.B) {
tstInitTemplates()
tmpl, err := New(logger).New("testroot").Parse(`{{ partialCached "bench1" . }}`)
if err != nil {
b.Fatalf("unable to create new html template: %s", err)
}
tmpl.New("partials/bench1").Parse(`{{ shuffle (seq 1 10) }}`)
buf := new(bytes.Buffer)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err = tmpl.Execute(buf, nil); err != nil {
b.Fatalf("error executing template: %s", err)
defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
err := templ.AddTemplate("testroot", `{{ partialCached "bench1" . }}`)
if err != nil {
return err
}
err = templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`)
if err != nil {
return err
}
buf.Reset()
}
}
func BenchmarkPartialCachedVariants(b *testing.B) {
tmpl, err := New(logger).New("testroot").Parse(`{{ partialCached "bench1" . "header" }}`)
if err != nil {
b.Fatalf("unable to create new html template: %s", err)
return nil
}
tmpl.New("partials/bench1").Parse(`{{ shuffle (seq 1 10) }}`)
de := deps.New(defaultDepsConfig)
require.NoError(b, de.LoadTemplates())
buf := new(bytes.Buffer)
tmpl := de.Tmpl.Lookup("testroot")
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err = tmpl.Execute(buf, nil); err != nil {
if err := tmpl.Execute(buf, nil); err != nil {
b.Fatalf("error executing template: %s", err)
}
buf.Reset()
@ -2892,5 +2901,25 @@ func BenchmarkPartialCachedVariants(b *testing.B) {
}
func newTestFuncster() *templateFuncster {
return New(logger).funcster
d := deps.New(defaultDepsConfig)
if err := d.LoadTemplates(); err != nil {
panic(err)
}
return d.Tmpl.(*GoHTMLTemplate).funcster
}
func newTestTemplate(t *testing.T, name, template string) *template.Template {
defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
err := templ.AddTemplate(name, template)
if err != nil {
return err
}
return nil
}
de := deps.New(defaultDepsConfig)
require.NoError(t, de.LoadTemplates())
return de.Tmpl.Lookup(name)
}

View File

@ -33,6 +33,7 @@ type translate struct {
current bundle.TranslateFunc
}
// TODO(bep) global translator
var translator *translate
// SetTranslateLang sets the translations language to use during template processing.

View File

@ -28,7 +28,6 @@ import (
"github.com/spf13/afero"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
)
@ -165,25 +164,25 @@ func resGetLocal(url string, fs afero.Fs) ([]byte, error) {
}
// resGetResource loads the content of a local or remote file
func resGetResource(url string) ([]byte, error) {
func (t *templateFuncster) resGetResource(url string) ([]byte, error) {
if url == "" {
return nil, nil
}
if strings.Contains(url, "://") {
return resGetRemote(url, hugofs.Source(), http.DefaultClient)
return resGetRemote(url, t.Fs.Source, http.DefaultClient)
}
return resGetLocal(url, hugofs.Source())
return resGetLocal(url, t.Fs.Source)
}
// getJSON expects one or n-parts of a URL to a resource which can either be a local or a remote one.
// If you provide multiple parts they will be joined together to the final URL.
// GetJSON returns nil or parsed JSON to use in a short code.
func getJSON(urlParts ...string) interface{} {
func (t *templateFuncster) getJSON(urlParts ...string) interface{} {
var v interface{}
url := strings.Join(urlParts, "")
for i := 0; i <= resRetries; i++ {
c, err := resGetResource(url)
c, err := t.resGetResource(url)
if err != nil {
jww.ERROR.Printf("Failed to get json resource %s with error message %s", url, err)
return nil
@ -194,7 +193,7 @@ func 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, hugofs.Source())
resDeleteCache(url, t.Fs.Source)
continue
}
break
@ -220,18 +219,18 @@ func parseCSV(c []byte, sep string) ([][]string, error) {
// The data separator can be a comma, semi-colon, pipe, etc, but only one character.
// If you provide multiple parts for the URL they will be joined together to the final URL.
// GetCSV returns nil or a slice slice to use in a short code.
func getCSV(sep string, urlParts ...string) [][]string {
func (t *templateFuncster) getCSV(sep string, urlParts ...string) [][]string {
var d [][]string
url := strings.Join(urlParts, "")
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, hugofs.Source())
resDeleteCache(url, t.Fs.Source)
}
for i := 0; i <= resRetries; i++ {
c, err := resGetResource(url)
c, err := t.resGetResource(url)
if err == nil && !bytes.Contains(c, []byte(sep)) {
err = errors.New("Cannot find separator " + sep + " in CSV.")

View File

@ -80,8 +80,10 @@ func TestScpCache(t *testing.T) {
}
func TestScpGetLocal(t *testing.T) {
fs := new(afero.MemMapFs)
testReset()
fs := hugofs.NewMem()
ps := helpers.FilePathSeparator
tests := []struct {
path string
content []byte
@ -95,12 +97,12 @@ func TestScpGetLocal(t *testing.T) {
for _, test := range tests {
r := bytes.NewReader(test.content)
err := helpers.WriteToDisk(test.path, r, fs)
err := helpers.WriteToDisk(test.path, r, fs.Source)
if err != nil {
t.Error(err)
}
c, err := resGetLocal(test.path, fs)
c, err := resGetLocal(test.path, fs.Source)
if err != nil {
t.Errorf("Error getting resource content: %s", err)
}
@ -212,9 +214,9 @@ type wd struct {
Reset func()
}
func testRetryWhenDone() wd {
func testRetryWhenDone(f *templateFuncster) wd {
cd := viper.GetString("cacheDir")
viper.Set("cacheDir", helpers.GetTempDir("", hugofs.Source()))
viper.Set("cacheDir", helpers.GetTempDir("", f.Fs.Source))
var tmpSleep time.Duration
tmpSleep, resSleep = resSleep, time.Millisecond
return wd{func() {
@ -224,7 +226,10 @@ func testRetryWhenDone() wd {
}
func TestGetJSONFailParse(t *testing.T) {
defer testRetryWhenDone().Reset()
f := newTestFuncster()
defer testRetryWhenDone(f).Reset()
reqCount := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -242,7 +247,7 @@ func TestGetJSONFailParse(t *testing.T) {
defer os.Remove(getCacheFileID(url))
want := map[string]interface{}{"gomeetup": []interface{}{"Sydney", "San Francisco", "Stockholm"}}
have := getJSON(url)
have := f.getJSON(url)
assert.NotNil(t, have)
if have != nil {
assert.EqualValues(t, want, have)
@ -250,7 +255,9 @@ func TestGetJSONFailParse(t *testing.T) {
}
func TestGetCSVFailParseSep(t *testing.T) {
defer testRetryWhenDone().Reset()
f := newTestFuncster()
defer testRetryWhenDone(f).Reset()
reqCount := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -271,7 +278,7 @@ func TestGetCSVFailParseSep(t *testing.T) {
defer os.Remove(getCacheFileID(url))
want := [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}
have := getCSV(",", url)
have := f.getCSV(",", url)
assert.NotNil(t, have)
if have != nil {
assert.EqualValues(t, want, have)
@ -279,7 +286,10 @@ func TestGetCSVFailParseSep(t *testing.T) {
}
func TestGetCSVFailParse(t *testing.T) {
defer testRetryWhenDone().Reset()
f := newTestFuncster()
defer testRetryWhenDone(f).Reset()
reqCount := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -302,7 +312,7 @@ func TestGetCSVFailParse(t *testing.T) {
defer os.Remove(getCacheFileID(url))
want := [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}
have := getCSV(",", url)
have := f.getCSV(",", url)
assert.NotNil(t, have)
if have != nil {
assert.EqualValues(t, want, have)

View File

@ -25,9 +25,21 @@ import (
"testing"
"github.com/spf13/afero"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"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) {
@ -68,11 +80,19 @@ html lang=en
d := "DATA"
templ := New(logger, func(templ Template) error {
config := defaultDepsConfig
config.WithTemplate = func(templ tplapi.Template) error {
return templ.AddAceTemplate("mytemplate.ace", basePath, innerPath,
[]byte(this.baseContent), []byte(this.innerContent))
}
})
a := deps.New(config)
if err := a.LoadTemplates(); err != nil {
t.Fatal(err)
}
templ := a.Tmpl.(*GoHTMLTemplate)
if len(templ.errors) > 0 && this.expectErr == 0 {
t.Errorf("Test %d with root '%s' errored: %v", i, root, templ.errors)
@ -81,7 +101,7 @@ html lang=en
}
var buff bytes.Buffer
err := templ.ExecuteTemplate(&buff, "mytemplate.html", d)
err := a.Tmpl.ExecuteTemplate(&buff, "mytemplate.html", d)
if err != nil && this.expectErr == 0 {
t.Errorf("Test %d with root '%s' errored: %s", i, root, err)
@ -93,6 +113,7 @@ html lang=en
t.Errorf("Test %d with root '%s' got\n%s\nexpected\n%s", i, root, result, this.expect)
}
}
}
}
@ -124,52 +145,59 @@ func TestAddTemplateFileWithMaster(t *testing.T) {
{`tpl`, `{{.0.E}}`, 0, false},
} {
hugofs.InitMemFs()
templ := New(logger)
overlayTplName := "ot"
masterTplName := "mt"
finalTplName := "tp"
defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
err := templ.AddTemplateFileWithMaster(finalTplName, overlayTplName, masterTplName)
if b, ok := this.expect.(bool); ok && !b {
if err == nil {
t.Errorf("[%d] AddTemplateFileWithMaster didn't return an expected error", i)
}
} else {
if err != nil {
t.Errorf("[%d] AddTemplateFileWithMaster failed: %s", i, err)
return nil
}
resultTpl := templ.Lookup(finalTplName)
if resultTpl == nil {
t.Errorf("[%d] AddTemplateFileWithMaster: Result template not found", i)
return nil
}
var b bytes.Buffer
err := resultTpl.Execute(&b, nil)
if err != nil {
t.Errorf("[%d] AddTemplateFileWithMaster execute failed: %s", i, err)
return nil
}
resultContent := b.String()
if resultContent != this.expect {
t.Errorf("[%d] AddTemplateFileWithMaster got \n%s but expected \n%v", i, resultContent, this.expect)
}
}
return nil
}
defaultDepsConfig.Fs = hugofs.NewMem()
if this.writeSkipper != 1 {
afero.WriteFile(hugofs.Source(), masterTplName, []byte(this.masterTplContent), 0644)
afero.WriteFile(defaultDepsConfig.Fs.Source, masterTplName, []byte(this.masterTplContent), 0644)
}
if this.writeSkipper != 2 {
afero.WriteFile(hugofs.Source(), overlayTplName, []byte(this.overlayTplContent), 0644)
afero.WriteFile(defaultDepsConfig.Fs.Source, overlayTplName, []byte(this.overlayTplContent), 0644)
}
err := templ.AddTemplateFileWithMaster(finalTplName, overlayTplName, masterTplName)
if b, ok := this.expect.(bool); ok && !b {
if err == nil {
t.Errorf("[%d] AddTemplateFileWithMaster didn't return an expected error", i)
}
} else {
if err != nil {
t.Errorf("[%d] AddTemplateFileWithMaster failed: %s", i, err)
continue
}
resultTpl := templ.Lookup(finalTplName)
if resultTpl == nil {
t.Errorf("[%d] AddTemplateFileWithMaster: Result template not found", i)
continue
}
var b bytes.Buffer
err := resultTpl.Execute(&b, nil)
if err != nil {
t.Errorf("[%d] AddTemplateFileWithMaster execute failed: %s", i, err)
continue
}
resultContent := b.String()
if resultContent != this.expect {
t.Errorf("[%d] AddTemplateFileWithMaster got \n%s but expected \n%v", i, resultContent, this.expect)
}
}
deps.New(defaultDepsConfig)
}
@ -258,23 +286,29 @@ func TestTplGoFuzzReports(t *testing.T) {
H: "a,b,c,d,e,f",
}
templ := New(logger, func(templ Template) error {
defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
return templ.AddTemplate("fuzz", this.data)
}
})
de := deps.New(defaultDepsConfig)
require.NoError(t, de.LoadTemplates())
templ := de.Tmpl.(*GoHTMLTemplate)
if len(templ.errors) > 0 && this.expectErr == 0 {
t.Errorf("Test %d errored: %v", i, templ.errors)
} else if len(templ.errors) == 0 && this.expectErr == 1 {
t.Errorf("#1 Test %d should have errored", i)
}
err := templ.ExecuteTemplate(ioutil.Discard, "fuzz", d)
err := de.Tmpl.ExecuteTemplate(ioutil.Discard, "fuzz", d)
if err != nil && this.expectErr == 0 {
t.Fatalf("Test %d errored: %s", i, err)
} else if err == nil && this.expectErr == 2 {
t.Fatalf("#2 Test %d should have errored", i)
}
}
}

28
tplapi/template.go Normal file
View File

@ -0,0 +1,28 @@
package tplapi
import (
"html/template"
"io"
)
// TODO(bep) make smaller
// TODO(bep) consider putting this into /tpl and the implementation in /tpl/tplimpl or something
type Template interface {
ExecuteTemplate(wr io.Writer, name string, data interface{}) error
ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML
Lookup(name string) *template.Template
Templates() []*template.Template
New(name string) *template.Template
GetClone() *template.Template
LoadTemplates(absPath string)
LoadTemplatesWithPrefix(absPath, prefix string)
AddTemplate(name, tpl string) error
AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error
AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error
AddInternalTemplate(prefix, name, tpl string) error
AddInternalShortcode(name, tpl string) error
Partial(name string, contextList ...interface{}) template.HTML
PrintErrors()
Funcs(funcMap template.FuncMap)
MarkReady()
}