diff --git a/commands/benchmark.go b/commands/benchmark.go index a879e8941..42966c67a 100644 --- a/commands/benchmark.go +++ b/commands/benchmark.go @@ -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 } } diff --git a/commands/gendoc.go b/commands/gendoc.go index 046d3839c..5ffd084e1 100644 --- a/commands/gendoc.go +++ b/commands/gendoc.go @@ -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 { diff --git a/commands/genman.go b/commands/genman.go index d1f54ae31..f7a3a424d 100644 --- a/commands/genman.go +++ b/commands/genman.go @@ -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 diff --git a/commands/hugo.go b/commands/hugo.go index f4204cad1..566e68603 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -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) diff --git a/commands/import_jekyll.go b/commands/import_jekyll.go index 7e55e0670..151fffa8a 100644 --- a/commands/import_jekyll.go +++ b/commands/import_jekyll.go @@ -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 } diff --git a/commands/new.go b/commands/new.go index 0b6ada534..b4a2740f3 100644 --- a/commands/new.go +++ b/commands/new.go @@ -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 } diff --git a/commands/new_test.go b/commands/new_test.go index 5991e1813..acb3d7598 100644 --- a/commands/new_test.go +++ b/commands/new_test.go @@ -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)) } diff --git a/commands/server.go b/commands/server.go index 45d776998..6b1776f4a 100644 --- a/commands/server.go +++ b/commands/server.go @@ -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) diff --git a/commands/undraft.go b/commands/undraft.go index 8e287651f..4f3bcfe23 100644 --- a/commands/undraft.go +++ b/commands/undraft.go @@ -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) } diff --git a/create/content.go b/create/content.go index 195080d88..6a03c8c9b 100644 --- a/create/content.go +++ b/create/content.go @@ -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 } diff --git a/create/content_test.go b/create/content_test.go index cdee13fb8..df29527fe 100644 --- a/create/content_test.go +++ b/create/content_test.go @@ -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 } diff --git a/deps/deps.go b/deps/deps.go new file mode 100644 index 000000000..d09b760aa --- /dev/null +++ b/deps/deps.go @@ -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 +} diff --git a/helpers/configProvider.go b/helpers/configProvider.go index e63112c0c..b96018257 100644 --- a/helpers/configProvider.go +++ b/helpers/configProvider.go @@ -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 + } diff --git a/helpers/path.go b/helpers/path.go index 2e154062e..83a91deba 100644 --- a/helpers/path.go +++ b/helpers/path.go @@ -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) } diff --git a/helpers/path_test.go b/helpers/path_test.go index f1407fb15..4d1ac28aa 100644 --- a/helpers/path_test.go +++ b/helpers/path_test.go @@ -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 diff --git a/helpers/pathspec.go b/helpers/pathspec.go index d95dcde7a..0fc957b3b 100644 --- a/helpers/pathspec.go +++ b/helpers/pathspec.go @@ -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"), } } diff --git a/helpers/pathspec_test.go b/helpers/pathspec_test.go index 9cd0af80e..42d828519 100644 --- a/helpers/pathspec_test.go +++ b/helpers/pathspec_test.go @@ -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) } diff --git a/helpers/pygments.go b/helpers/pygments.go index 5e9812d72..8e6d1a998 100644 --- a/helpers/pygments.go +++ b/helpers/pygments.go @@ -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") diff --git a/helpers/url_test.go b/helpers/url_test.go index 8dbec3f7c..b50a9efd8 100644 --- a/helpers/url_test.go +++ b/helpers/url_test.go @@ -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 { diff --git a/hugofs/fs.go b/hugofs/fs.go index 7f8abd337..3afa17956 100644 --- a/hugofs/fs.go +++ b/hugofs/fs.go @@ -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 } diff --git a/hugofs/fs_test.go b/hugofs/fs_test.go index 55007009f..5482e6d27 100644 --- a/hugofs/fs_test.go +++ b/hugofs/fs_test.go @@ -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) } diff --git a/hugolib/alias_test.go b/hugolib/alias_test.go index 87bc9b130..22803d22e 100644 --- a/hugolib/alias_test.go +++ b/hugolib/alias_test.go @@ -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 = "
ALIASTEMPLATE" 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, "}} `) - 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) diff --git a/hugolib/embedded_shortcodes_test.go b/hugolib/embedded_shortcodes_test.go index 61c40cf01..64a92247b 100644 --- a/hugolib/embedded_shortcodes_test.go +++ b/hugolib/embedded_shortcodes_test.go @@ -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 }) diff --git a/hugolib/gitinfo.go b/hugolib/gitinfo.go index 2893db06f..82baa3250 100644 --- a/hugolib/gitinfo.go +++ b/hugolib/gitinfo.go @@ -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 } diff --git a/hugolib/handler_page.go b/hugolib/handler_page.go index 2026f2bbf..6b6b17173 100644 --- a/hugolib/handler_page.go +++ b/hugolib/handler_page.go @@ -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())) diff --git a/hugolib/handler_test.go b/hugolib/handler_test.go index ba5daa8c2..01e6793a6 100644 --- a/hugolib/handler_test.go +++ b/hugolib/handler_test.go @@ -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("more content")}, - {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 ��� IHDR����������:~›U��� IDATWcø��ZMoñ����IEND®B`‚")}, - {Name: filepath.FromSlash("sect/img2.gif"), Content: []byte("GIF89a��€��ÿÿÿ���,�������D�;")}, - {Name: filepath.FromSlash("sect/img2.spf"), Content: []byte("****FAKE-FILETYPE****")}, - {Name: filepath.FromSlash("doc7.html"), Content: []byte("doc7 content")}, - {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_abs", ""); 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"), "more content") + 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 ��� IHDR����������:~›U��� IDATWcø��ZMoñ����IEND®B`‚") + writeSource(t, fs, filepath.FromSlash("content/sect/img2.gif"), "GIF89a��€��ÿÿÿ���,�������D�;") + writeSource(t, fs, filepath.FromSlash("content/sect/img2.spf"), "****FAKE-FILETYPE****") + writeSource(t, fs, filepath.FromSlash("content/doc7.html"), "doc7 content") + 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"), "") + writeSource(t, fs, filepath.FromSlash("head_abs"), " 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) } diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index b3b176018..e915d11da 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -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 diff --git a/hugolib/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go index a8fc9a58f..9abb17d5e 100644 --- a/hugolib/hugo_sites_build_test.go +++ b/hugolib/hugo_sites_build_test.go @@ -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, ``) + assertFileContent(t, fs, "public/index.html", true, ``) } else { // should have redirect back to root - assertFileContent(t, "public/fr/index.html", true, ``) + assertFileContent(t, fs, "public/fr/index.html", true, ``) } - 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, "abc
\n"}, + filepath.FromSlash("public/sect/doc1/index.html"), "abc
\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"), "a
\n\nb
\nc\nd
e
\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"), "a
\n\nb
\nc
e
\n"}, {"sect/doc4.md", `a {{< b >}} @@ -510,22 +513,22 @@ e`, `, - filepath.FromSlash("sect/doc4/index.html"), + filepath.FromSlash("public/sect/doc4/index.html"), "a\nb\nb\nb\nb\nb
\n"}, // #2192 #2209: Shortcodes in markdown headers {"sect/doc5.md", `# {{< b >}} ## {{% c %}}`, - filepath.FromSlash("sect/doc5/index.html"), "\n\nShortcodes: b: b c: c
\nShortcodes: b: b c: c
\nShortcodes: b: b c: c
\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"), "Menus: 1
\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"), "Tags: 2
\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) } } diff --git a/hugolib/site.go b/hugolib/site.go index c887a9305..6afc18a69 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -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("\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 { diff --git a/hugolib/siteJSONEncode_test.go b/hugolib/siteJSONEncode_test.go index 170db4b4d..1218bfd34 100644 --- a/hugolib/siteJSONEncode_test.go +++ b/hugolib/siteJSONEncode_test.go @@ -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) diff --git a/hugolib/site_render.go b/hugolib/site_render.go index b6a9cae54..84df78c1c 100644 --- a/hugolib/site_render.go +++ b/hugolib/site_render.go @@ -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 } diff --git a/hugolib/site_test.go b/hugolib/site_test.go index 342cae615..3f1a8b066 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -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("Ref 2: %s/sect/doc2%s
\n", expectedBase, expectedURLSuffix)}, - {filepath.FromSlash(fmt.Sprintf("sect/doc2%s", expectedPathSuffix)), fmt.Sprintf("Ref 1:
\n\n%s/sect/doc1%s\n\nTHE END.
\n", expectedBase, expectedURLSuffix)}, - {filepath.FromSlash(fmt.Sprintf("sect/doc3%s", expectedPathSuffix)), fmt.Sprintf("Ref 1:%s/sect/doc3%s.
\n", expectedBase, expectedURLSuffix)}, + {filepath.FromSlash(fmt.Sprintf("public/sect/doc1%s", expectedPathSuffix)), fmt.Sprintf("Ref 2: %s/sect/doc2%s
\n", expectedBase, expectedURLSuffix)}, + {filepath.FromSlash(fmt.Sprintf("public/sect/doc2%s", expectedPathSuffix)), fmt.Sprintf("Ref 1:
\n\n%s/sect/doc1%s\n\nTHE END.
\n", expectedBase, expectedURLSuffix)}, + {filepath.FromSlash(fmt.Sprintf("public/sect/doc3%s", expectedPathSuffix)), fmt.Sprintf("Ref 1:%s/sect/doc3%s.
\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", "some content
\n"}, - {filepath.FromSlash("sect/doc2.html"), "more content"}, - {filepath.FromSlash("sect/doc3.html"), "\n\nsome content
\n"}, - {filepath.FromSlash("sect/doc4.html"), "\n\nsome content
\n"}, - {filepath.FromSlash("sect/doc5.html"), "body5"}, - {filepath.FromSlash("sect/doc6.html"), "body5"}, - {filepath.FromSlash("doc7.html"), "doc7 content"}, - {filepath.FromSlash("sect/doc8.html"), "\n\nsome content
\n"}, + {filepath.FromSlash("public/sect/doc1.html"), "\n\nsome content
\n"}, + {filepath.FromSlash("public/sect/doc2.html"), "more content"}, + {filepath.FromSlash("public/sect/doc3.html"), "\n\nsome content
\n"}, + {filepath.FromSlash("public/sect/doc4.html"), "\n\nsome content
\n"}, + {filepath.FromSlash("public/sect/doc5.html"), "body5"}, + {filepath.FromSlash("public/sect/doc6.html"), "body5"}, + {filepath.FromSlash("public/doc7.html"), "doc7 content"}, + {filepath.FromSlash("public/sect/doc8.html"), "\n\nsome content
\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("link")}, {Name: filepath.FromSlash("blue/doc2.html"), Content: []byte("---\nf: t\n---\nmore content")}, @@ -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", "Going"}, - {"sect/doc1.html", "link"}, + {"public/blue/doc2.html", "Going"}, + {"public/sect/doc1.html", "link"}, } 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) { diff --git a/hugolib/site_url_test.go b/hugolib/site_url_test.go index 99d04460d..5706b9fb5 100644 --- a/hugolib/site_url_test.go +++ b/hugolib/site_url_test.go @@ -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) } } } diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go index 95f8739ec..15d71cc6f 100644 --- a/hugolib/sitemap_test.go +++ b/hugolib/sitemap_test.go @@ -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 "