Rework the Destination filesystem to make --renderStaticToDisk work

See #9626
This commit is contained in:
Bjørn Erik Pedersen 2022-03-21 09:35:15 +01:00
parent b08193971a
commit d070bdf10f
No known key found for this signature in database
GPG key ID: 330E6E2BD4859D8F
75 changed files with 651 additions and 566 deletions

View file

@ -184,7 +184,7 @@ dir = "/"
} }
func newTestConfig() config.Provider { func newTestConfig() config.Provider {
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject")) cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject"))
cfg.Set("contentDir", "content") cfg.Set("contentDir", "content")
cfg.Set("dataDir", "data") cfg.Set("dataDir", "data")

View file

@ -342,6 +342,7 @@ func newPathsSpec(t *testing.T, fs afero.Fs, configStr string) *helpers.PathSpec
cfg, err := config.FromConfigString(configStr, "toml") cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
initConfig(fs, cfg) initConfig(fs, cfg)
config.SetBaseTestDefaults(cfg)
p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, cfg), cfg, nil) p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, cfg), cfg, nil)
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
return p return p

View file

@ -30,6 +30,7 @@ import (
"github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/paths"
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
@ -42,6 +43,7 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/bep/debounce" "github.com/bep/debounce"
"github.com/bep/overlayfs"
"github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/helpers"
@ -73,8 +75,10 @@ type commandeer struct {
// be fast enough that we could maybe just add it for all server modes. // be fast enough that we could maybe just add it for all server modes.
changeDetector *fileChangeDetector changeDetector *fileChangeDetector
// We need to reuse this on server rebuilds. // We need to reuse these on server rebuilds.
destinationFs afero.Fs // These 2 will be different if --renderStaticToDisk is set.
publishDirFs afero.Fs
publishDirServerFs afero.Fs
h *hugoBuilderCommon h *hugoBuilderCommon
ftch flagsToConfigHandler ftch flagsToConfigHandler
@ -162,7 +166,8 @@ func (c *commandeer) Set(key string, value any) {
} }
func (c *commandeer) initFs(fs *hugofs.Fs) error { func (c *commandeer) initFs(fs *hugofs.Fs) error {
c.destinationFs = fs.Destination c.publishDirFs = fs.PublishDir
c.publishDirServerFs = fs.PublishDirServer
c.DepsCfg.Fs = fs c.DepsCfg.Fs = fs
return nil return nil
@ -378,28 +383,63 @@ func (c *commandeer) loadConfig() error {
createMemFs := config.GetBool("renderToMemory") createMemFs := config.GetBool("renderToMemory")
c.renderStaticToDisk = config.GetBool("renderStaticToDisk") c.renderStaticToDisk = config.GetBool("renderStaticToDisk")
if createMemFs && !c.renderStaticToDisk { if createMemFs {
// Rendering to memoryFS, publish to Root regardless of publishDir. // Rendering to memoryFS, publish to Root regardless of publishDir.
config.Set("publishDir", "/") config.Set("publishDir", "/")
config.Set("publishDirStatic", "/")
} else if c.renderStaticToDisk {
// Hybrid, render dynamic content to Root.
config.Set("publishDirStatic", config.Get("publishDir"))
config.Set("publishDir", "/")
} }
c.fsCreate.Do(func() { c.fsCreate.Do(func() {
fs := hugofs.NewFrom(sourceFs, config) fs := hugofs.NewFrom(sourceFs, config)
if c.destinationFs != nil { if c.publishDirFs != nil {
// Need to reuse the destination on server rebuilds. // Need to reuse the destination on server rebuilds.
fs.Destination = c.destinationFs fs.PublishDir = c.publishDirFs
} else if createMemFs && c.renderStaticToDisk { fs.PublishDirServer = c.publishDirServerFs
// Writes the dynamic output on memory, } else {
// while serve others directly from publishDir
publishDir := config.GetString("publishDir") publishDir := config.GetString("publishDir")
writableFs := afero.NewBasePathFs(afero.NewMemMapFs(), publishDir) publishDirStatic := config.GetString("publishDirStatic")
publicFs := afero.NewOsFs() workingDir := config.GetString("workingDir")
fs.Destination = afero.NewCopyOnWriteFs(afero.NewReadOnlyFs(publicFs), writableFs) absPublishDir := paths.AbsPathify(workingDir, publishDir)
fs.DestinationStatic = publicFs absPublishDirStatic := paths.AbsPathify(workingDir, publishDirStatic)
if c.renderStaticToDisk {
// Writes the dynamic output oton memory,
// while serve others directly from /public on disk.
dynamicFs := afero.NewMemMapFs()
staticFs := afero.NewBasePathFs(afero.NewOsFs(), absPublishDirStatic)
// Serve from both the static and dynamic fs,
// the first will take priority.
// THis is a read-only filesystem,
// we do all the writes to
// fs.Destination and fs.DestinationStatic.
fs.PublishDirServer = overlayfs.New(
overlayfs.Options{
Fss: []afero.Fs{
dynamicFs,
staticFs,
},
},
)
fs.PublishDir = dynamicFs
fs.PublishDirStatic = staticFs
} else if createMemFs { } else if createMemFs {
// Hugo writes the output to memory instead of the disk. // Hugo writes the output to memory instead of the disk.
fs.Destination = new(afero.MemMapFs) fs.PublishDir = new(afero.MemMapFs)
fs.PublishDirServer = fs.PublishDir
fs.PublishDirStatic = fs.PublishDir
} else {
// Write everything to disk.
fs.PublishDir = afero.NewBasePathFs(afero.NewOsFs(), absPublishDir)
fs.PublishDirServer = fs.PublishDir
fs.PublishDirStatic = fs.PublishDir
}
} }
if c.fastRenderMode { if c.fastRenderMode {
@ -413,15 +453,15 @@ func (c *commandeer) loadConfig() error {
} }
changeDetector.PrepareNew() changeDetector.PrepareNew()
fs.Destination = hugofs.NewHashingFs(fs.Destination, changeDetector) fs.PublishDir = hugofs.NewHashingFs(fs.PublishDir, changeDetector)
fs.DestinationStatic = hugofs.NewHashingFs(fs.DestinationStatic, changeDetector) fs.PublishDirStatic = hugofs.NewHashingFs(fs.PublishDirStatic, changeDetector)
c.changeDetector = changeDetector c.changeDetector = changeDetector
} }
if c.Cfg.GetBool("logPathWarnings") { if c.Cfg.GetBool("logPathWarnings") {
// Note that we only care about the "dynamic creates" here, // Note that we only care about the "dynamic creates" here,
// so skip the static fs. // so skip the static fs.
fs.Destination = hugofs.NewCreateCountingFs(fs.Destination) fs.PublishDir = hugofs.NewCreateCountingFs(fs.PublishDir)
} }
// To debug hard-to-find path issues. // To debug hard-to-find path issues.

View file

@ -18,10 +18,9 @@ import (
"os" "os"
"time" "time"
"github.com/gohugoio/hugo/hugolib/paths"
"github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/loggers"
hpaths "github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/helpers"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -243,14 +242,14 @@ func (cc *hugoBuilderCommon) timeTrack(start time.Time, name string) {
func (cc *hugoBuilderCommon) getConfigDir(baseDir string) string { func (cc *hugoBuilderCommon) getConfigDir(baseDir string) string {
if cc.cfgDir != "" { if cc.cfgDir != "" {
return paths.AbsPathify(baseDir, cc.cfgDir) return hpaths.AbsPathify(baseDir, cc.cfgDir)
} }
if v, found := os.LookupEnv("HUGO_CONFIGDIR"); found { if v, found := os.LookupEnv("HUGO_CONFIGDIR"); found {
return paths.AbsPathify(baseDir, v) return hpaths.AbsPathify(baseDir, v)
} }
return paths.AbsPathify(baseDir, "config") return hpaths.AbsPathify(baseDir, "config")
} }
func (cc *hugoBuilderCommon) getEnvironment(isServer bool) string { func (cc *hugoBuilderCommon) getEnvironment(isServer bool) string {

View file

@ -22,8 +22,6 @@ import (
"github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/htesting"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/hugofs"
@ -38,15 +36,13 @@ import (
func TestExecute(t *testing.T) { func TestExecute(t *testing.T) {
c := qt.New(t) c := qt.New(t)
createSite := func(c *qt.C) (string, func()) { createSite := func(c *qt.C) string {
dir, clean, err := createSimpleTestSite(t, testSiteConfig{}) dir := createSimpleTestSite(t, testSiteConfig{})
c.Assert(err, qt.IsNil) return dir
return dir, clean
} }
c.Run("hugo", func(c *qt.C) { c.Run("hugo", func(c *qt.C) {
dir, clean := createSite(c) dir := createSite(c)
defer clean()
resp := Execute([]string{"-s=" + dir}) resp := Execute([]string{"-s=" + dir})
c.Assert(resp.Err, qt.IsNil) c.Assert(resp.Err, qt.IsNil)
result := resp.Result result := resp.Result
@ -56,8 +52,7 @@ func TestExecute(t *testing.T) {
}) })
c.Run("hugo, set environment", func(c *qt.C) { c.Run("hugo, set environment", func(c *qt.C) {
dir, clean := createSite(c) dir := createSite(c)
defer clean()
resp := Execute([]string{"-s=" + dir, "-e=staging"}) resp := Execute([]string{"-s=" + dir, "-e=staging"})
c.Assert(resp.Err, qt.IsNil) c.Assert(resp.Err, qt.IsNil)
result := resp.Result result := resp.Result
@ -65,9 +60,8 @@ func TestExecute(t *testing.T) {
}) })
c.Run("convert toJSON", func(c *qt.C) { c.Run("convert toJSON", func(c *qt.C) {
dir, clean := createSite(c) dir := createSite(c)
output := filepath.Join(dir, "myjson") output := filepath.Join(dir, "myjson")
defer clean()
resp := Execute([]string{"convert", "toJSON", "-s=" + dir, "-e=staging", "-o=" + output}) resp := Execute([]string{"convert", "toJSON", "-s=" + dir, "-e=staging", "-o=" + output})
c.Assert(resp.Err, qt.IsNil) c.Assert(resp.Err, qt.IsNil)
converted := readFileFrom(c, filepath.Join(output, "content", "p1.md")) converted := readFileFrom(c, filepath.Join(output, "content", "p1.md"))
@ -75,8 +69,7 @@ func TestExecute(t *testing.T) {
}) })
c.Run("config, set environment", func(c *qt.C) { c.Run("config, set environment", func(c *qt.C) {
dir, clean := createSite(c) dir := createSite(c)
defer clean()
out, err := captureStdout(func() error { out, err := captureStdout(func() error {
resp := Execute([]string{"config", "-s=" + dir, "-e=staging"}) resp := Execute([]string{"config", "-s=" + dir, "-e=staging"})
return resp.Err return resp.Err
@ -86,16 +79,14 @@ func TestExecute(t *testing.T) {
}) })
c.Run("deploy, environment set", func(c *qt.C) { c.Run("deploy, environment set", func(c *qt.C) {
dir, clean := createSite(c) dir := createSite(c)
defer clean()
resp := Execute([]string{"deploy", "-s=" + dir, "-e=staging", "--target=mydeployment", "--dryRun"}) resp := Execute([]string{"deploy", "-s=" + dir, "-e=staging", "--target=mydeployment", "--dryRun"})
c.Assert(resp.Err, qt.Not(qt.IsNil)) c.Assert(resp.Err, qt.Not(qt.IsNil))
c.Assert(resp.Err.Error(), qt.Contains, `no driver registered for "hugocloud"`) c.Assert(resp.Err.Error(), qt.Contains, `no driver registered for "hugocloud"`)
}) })
c.Run("list", func(c *qt.C) { c.Run("list", func(c *qt.C) {
dir, clean := createSite(c) dir := createSite(c)
defer clean()
out, err := captureStdout(func() error { out, err := captureStdout(func() error {
resp := Execute([]string{"list", "all", "-s=" + dir, "-e=staging"}) resp := Execute([]string{"list", "all", "-s=" + dir, "-e=staging"})
return resp.Err return resp.Err
@ -105,8 +96,7 @@ func TestExecute(t *testing.T) {
}) })
c.Run("new theme", func(c *qt.C) { c.Run("new theme", func(c *qt.C) {
dir, clean := createSite(c) dir := createSite(c)
defer clean()
themesDir := filepath.Join(dir, "mythemes") themesDir := filepath.Join(dir, "mythemes")
resp := Execute([]string{"new", "theme", "mytheme", "-s=" + dir, "-e=staging", "--themesDir=" + themesDir}) resp := Execute([]string{"new", "theme", "mytheme", "-s=" + dir, "-e=staging", "--themesDir=" + themesDir})
c.Assert(resp.Err, qt.IsNil) c.Assert(resp.Err, qt.IsNil)
@ -115,8 +105,7 @@ func TestExecute(t *testing.T) {
}) })
c.Run("new site", func(c *qt.C) { c.Run("new site", func(c *qt.C) {
dir, clean := createSite(c) dir := createSite(c)
defer clean()
siteDir := filepath.Join(dir, "mysite") siteDir := filepath.Join(dir, "mysite")
resp := Execute([]string{"new", "site", siteDir, "-e=staging"}) resp := Execute([]string{"new", "site", siteDir, "-e=staging"})
c.Assert(resp.Err, qt.IsNil) c.Assert(resp.Err, qt.IsNil)
@ -167,7 +156,7 @@ func TestFlags(t *testing.T) {
name: "ignoreVendorPaths", name: "ignoreVendorPaths",
args: []string{"server", "--ignoreVendorPaths=github.com/**"}, args: []string{"server", "--ignoreVendorPaths=github.com/**"},
check: func(c *qt.C, cmd *serverCmd) { check: func(c *qt.C, cmd *serverCmd) {
cfg := config.New() cfg := config.NewWithTestDefaults()
cmd.flagsToConfig(cfg) cmd.flagsToConfig(cfg)
c.Assert(cfg.Get("ignoreVendorPaths"), qt.Equals, "github.com/**") c.Assert(cfg.Get("ignoreVendorPaths"), qt.Equals, "github.com/**")
}, },
@ -208,7 +197,7 @@ func TestFlags(t *testing.T) {
c.Assert(sc.serverPort, qt.Equals, 1366) c.Assert(sc.serverPort, qt.Equals, 1366)
c.Assert(sc.environment, qt.Equals, "testing") c.Assert(sc.environment, qt.Equals, "testing")
cfg := config.New() cfg := config.NewWithTestDefaults()
sc.flagsToConfig(cfg) sc.flagsToConfig(cfg)
c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination") c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination")
c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent") c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent")
@ -253,14 +242,8 @@ func TestFlags(t *testing.T) {
func TestCommandsExecute(t *testing.T) { func TestCommandsExecute(t *testing.T) {
c := qt.New(t) c := qt.New(t)
dir, clean, err := createSimpleTestSite(t, testSiteConfig{}) dir := createSimpleTestSite(t, testSiteConfig{})
c.Assert(err, qt.IsNil) dirOut := t.TempDir()
dirOut, clean2, err := htesting.CreateTempDir(hugofs.Os, "hugo-cli-out")
c.Assert(err, qt.IsNil)
defer clean()
defer clean2()
sourceFlag := fmt.Sprintf("-s=%s", dir) sourceFlag := fmt.Sprintf("-s=%s", dir)
@ -297,6 +280,11 @@ func TestCommandsExecute(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
name := "hugo"
if len(test.commands) > 0 {
name = test.commands[0]
}
c.Run(name, func(c *qt.C) {
b := newCommandsBuilder().addAll().build() b := newCommandsBuilder().addAll().build()
hugoCmd := b.getCommand() hugoCmd := b.getCommand()
test.flags = append(test.flags, "--quiet") test.flags = append(test.flags, "--quiet")
@ -317,9 +305,10 @@ func TestCommandsExecute(t *testing.T) {
// Assert that we have not left any development debug artifacts in // Assert that we have not left any development debug artifacts in
// the code. // the code.
if b.c != nil { if b.c != nil {
_, ok := b.c.destinationFs.(types.DevMarker) _, ok := b.c.publishDirFs.(types.DevMarker)
c.Assert(ok, qt.Equals, false) c.Assert(ok, qt.Equals, false)
} }
})
} }
} }
@ -329,11 +318,8 @@ type testSiteConfig struct {
contentDir string contentDir string
} }
func createSimpleTestSite(t testing.TB, cfg testSiteConfig) (string, func(), error) { func createSimpleTestSite(t testing.TB, cfg testSiteConfig) string {
d, clean, e := htesting.CreateTempDir(hugofs.Os, "hugo-cli") dir := t.TempDir()
if e != nil {
return "", nil, e
}
cfgStr := ` cfgStr := `
@ -352,23 +338,23 @@ title = "Hugo Commands"
contentDir = cfg.contentDir contentDir = cfg.contentDir
} }
os.MkdirAll(filepath.Join(d, "public"), 0777) os.MkdirAll(filepath.Join(dir, "public"), 0777)
// Just the basic. These are for CLI tests, not site testing. // Just the basic. These are for CLI tests, not site testing.
writeFile(t, filepath.Join(d, "config.toml"), cfgStr) writeFile(t, filepath.Join(dir, "config.toml"), cfgStr)
writeFile(t, filepath.Join(d, "config", "staging", "params.toml"), `myparam="paramstaging"`) writeFile(t, filepath.Join(dir, "config", "staging", "params.toml"), `myparam="paramstaging"`)
writeFile(t, filepath.Join(d, "config", "staging", "deployment.toml"), ` writeFile(t, filepath.Join(dir, "config", "staging", "deployment.toml"), `
[[targets]] [[targets]]
name = "mydeployment" name = "mydeployment"
URL = "hugocloud://hugotestbucket" URL = "hugocloud://hugotestbucket"
`) `)
writeFile(t, filepath.Join(d, "config", "testing", "params.toml"), `myparam="paramtesting"`) writeFile(t, filepath.Join(dir, "config", "testing", "params.toml"), `myparam="paramtesting"`)
writeFile(t, filepath.Join(d, "config", "production", "params.toml"), `myparam="paramproduction"`) writeFile(t, filepath.Join(dir, "config", "production", "params.toml"), `myparam="paramproduction"`)
writeFile(t, filepath.Join(d, "static", "myfile.txt"), `Hello World!`) writeFile(t, filepath.Join(dir, "static", "myfile.txt"), `Hello World!`)
writeFile(t, filepath.Join(d, contentDir, "p1.md"), ` writeFile(t, filepath.Join(dir, contentDir, "p1.md"), `
--- ---
title: "P1" title: "P1"
weight: 1 weight: 1
@ -378,20 +364,20 @@ Content
`) `)
writeFile(t, filepath.Join(d, "layouts", "_default", "single.html"), ` writeFile(t, filepath.Join(dir, "layouts", "_default", "single.html"), `
Single: {{ .Title }} Single: {{ .Title }}
`) `)
writeFile(t, filepath.Join(d, "layouts", "_default", "list.html"), ` writeFile(t, filepath.Join(dir, "layouts", "_default", "list.html"), `
List: {{ .Title }} List: {{ .Title }}
Environment: {{ hugo.Environment }} Environment: {{ hugo.Environment }}
`) `)
return d, clean, nil return dir
} }
func writeFile(t testing.TB, filename, content string) { func writeFile(t testing.TB, filename, content string) {

View file

@ -508,7 +508,7 @@ func (c *commandeer) build() error {
c.hugo().PrintProcessingStats(os.Stdout) c.hugo().PrintProcessingStats(os.Stdout)
fmt.Println() fmt.Println()
if createCounter, ok := c.destinationFs.(hugofs.DuplicatesReporter); ok { if createCounter, ok := c.publishDirFs.(hugofs.DuplicatesReporter); ok {
dupes := createCounter.ReportDuplicates() dupes := createCounter.ReportDuplicates()
if dupes != "" { if dupes != "" {
c.logger.Warnln("Duplicate target paths:", dupes) c.logger.Warnln("Duplicate target paths:", dupes)
@ -634,11 +634,7 @@ func chmodFilter(dst, src os.FileInfo) bool {
} }
func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint64, error) { func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
publishDir := c.hugo().PathSpec.PublishDir publishDir := helpers.FilePathSeparator
// If root, remove the second '/'
if publishDir == "//" {
publishDir = helpers.FilePathSeparator
}
if sourceFs.PublishFolder != "" { if sourceFs.PublishFolder != "" {
publishDir = filepath.Join(publishDir, sourceFs.PublishFolder) publishDir = filepath.Join(publishDir, sourceFs.PublishFolder)
@ -651,9 +647,9 @@ func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint6
syncer.NoChmod = c.Cfg.GetBool("noChmod") syncer.NoChmod = c.Cfg.GetBool("noChmod")
syncer.ChmodFilter = chmodFilter syncer.ChmodFilter = chmodFilter
syncer.SrcFs = fs syncer.SrcFs = fs
syncer.DestFs = c.Fs.Destination syncer.DestFs = c.Fs.PublishDir
if c.renderStaticToDisk { if c.renderStaticToDisk {
syncer.DestFs = c.Fs.DestinationStatic syncer.DestFs = c.Fs.PublishDirStatic
} }
// Now that we are using a unionFs for the static directories // Now that we are using a unionFs for the static directories
// We can effectively clean the publishDir on initial sync // We can effectively clean the publishDir on initial sync

View file

@ -36,12 +36,10 @@ title = "Hugo Commands"
contentDir = "thisdoesnotexist" contentDir = "thisdoesnotexist"
` `
dir, clean, err := createSimpleTestSite(t, testSiteConfig{configTOML: cfgStr, contentDir: contentDir}) dir := createSimpleTestSite(t, testSiteConfig{configTOML: cfgStr, contentDir: contentDir})
c.Assert(err, qt.IsNil)
defer clean()
cmd.SetArgs([]string{"-s=" + dir, "-c=" + contentDir}) cmd.SetArgs([]string{"-s=" + dir, "-c=" + contentDir})
_, err = cmd.ExecuteC() _, err := cmd.ExecuteC()
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
} }

View file

@ -29,10 +29,7 @@ func captureStdout(f func() error) (string, error) {
func TestListAll(t *testing.T) { func TestListAll(t *testing.T) {
c := qt.New(t) c := qt.New(t)
dir, clean, err := createSimpleTestSite(t, testSiteConfig{}) dir := createSimpleTestSite(t, testSiteConfig{})
defer clean()
c.Assert(err, qt.IsNil)
hugoCmd := newCommandsBuilder().addAll().build() hugoCmd := newCommandsBuilder().addAll().build()
cmd := hugoCmd.getCommand() cmd := hugoCmd.getCommand()

View file

@ -122,8 +122,10 @@ func (n *newSiteCmd) newSite(cmd *cobra.Command, args []string) error {
} }
forceNew, _ := cmd.Flags().GetBool("force") forceNew, _ := cmd.Flags().GetBool("force")
cfg := config.New()
return n.doNewSite(hugofs.NewDefault(config.New()), createpath, forceNew) cfg.Set("workingDir", createpath)
cfg.Set("publishDir", "public")
return n.doNewSite(hugofs.NewDefault(cfg), createpath, forceNew)
} }
func createConfig(fs *hugofs.Fs, inpath string, kind string) (err error) { func createConfig(fs *hugofs.Fs, inpath string, kind string) (err error) {

View file

@ -23,6 +23,7 @@ import (
"net/url" "net/url"
"os" "os"
"os/signal" "os/signal"
"path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
@ -148,7 +149,7 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
var serverCfgInit sync.Once var serverCfgInit sync.Once
cfgInit := func(c *commandeer) (rerr error) { cfgInit := func(c *commandeer) (rerr error) {
c.Set("renderToMemory", !sc.renderToDisk) c.Set("renderToMemory", !(sc.renderToDisk || sc.renderStaticToDisk))
c.Set("renderStaticToDisk", sc.renderStaticToDisk) c.Set("renderStaticToDisk", sc.renderStaticToDisk)
if cmd.Flags().Changed("navigateToChanged") { if cmd.Flags().Changed("navigateToChanged") {
c.Set("navigateToChanged", sc.navigateToChanged) c.Set("navigateToChanged", sc.navigateToChanged)
@ -330,13 +331,18 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
port := f.c.serverPorts[i].p port := f.c.serverPorts[i].p
listener := f.c.serverPorts[i].ln listener := f.c.serverPorts[i].ln
// For logging only.
// TODO(bep) consolidate.
publishDir := f.c.Cfg.GetString("publishDir") publishDir := f.c.Cfg.GetString("publishDir")
publishDirStatic := f.c.Cfg.GetString("publishDirStatic")
workingDir := f.c.Cfg.GetString("workingDir")
if root != "" { if root != "" {
publishDir = filepath.Join(publishDir, root) publishDir = filepath.Join(publishDir, root)
publishDirStatic = filepath.Join(publishDirStatic, root)
} }
absPublishDir := paths.AbsPathify(workingDir, publishDir)
absPublishDir := f.c.hugo().PathSpec.AbsPathify(publishDir) absPublishDirStatic := paths.AbsPathify(workingDir, publishDirStatic)
jww.FEEDBACK.Printf("Environment: %q", f.c.hugo().Deps.Site.Hugo().Environment) jww.FEEDBACK.Printf("Environment: %q", f.c.hugo().Deps.Site.Hugo().Environment)
@ -344,14 +350,14 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
if f.s.renderToDisk { if f.s.renderToDisk {
jww.FEEDBACK.Println("Serving pages from " + absPublishDir) jww.FEEDBACK.Println("Serving pages from " + absPublishDir)
} else if f.s.renderStaticToDisk { } else if f.s.renderStaticToDisk {
jww.FEEDBACK.Println("Serving pages from memory and static files from " + absPublishDir) jww.FEEDBACK.Println("Serving pages from memory and static files from " + absPublishDirStatic)
} else { } else {
jww.FEEDBACK.Println("Serving pages from memory") jww.FEEDBACK.Println("Serving pages from memory")
} }
} }
httpFs := afero.NewHttpFs(f.c.destinationFs) httpFs := afero.NewHttpFs(f.c.publishDirServerFs)
fs := filesOnlyFs{httpFs.Dir(absPublishDir)} fs := filesOnlyFs{httpFs.Dir(path.Join("/", root))}
if i == 0 && f.c.fastRenderMode { if i == 0 && f.c.fastRenderMode {
jww.FEEDBACK.Println("Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender") jww.FEEDBACK.Println("Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender")

View file

@ -77,6 +77,9 @@ func TestServerFlags(t *testing.T) {
{"--renderToDisk", func(c *qt.C, r serverTestResult) { {"--renderToDisk", func(c *qt.C, r serverTestResult) {
assertPublic(c, r, true) assertPublic(c, r, true)
}}, }},
{"--renderStaticToDisk", func(c *qt.C, r serverTestResult) {
assertPublic(c, r, true)
}},
} { } {
c.Run(test.flag, func(c *qt.C) { c.Run(test.flag, func(c *qt.C) {
config := ` config := `
@ -105,9 +108,7 @@ type serverTestResult struct {
} }
func runServerTest(c *qt.C, getHome bool, config string, args ...string) (result serverTestResult) { func runServerTest(c *qt.C, getHome bool, config string, args ...string) (result serverTestResult) {
dir, clean, err := createSimpleTestSite(c, testSiteConfig{configTOML: config}) dir := createSimpleTestSite(c, testSiteConfig{configTOML: config})
defer clean()
c.Assert(err, qt.IsNil)
sp, err := helpers.FindAvailablePort() sp, err := helpers.FindAvailablePort()
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
@ -141,12 +142,15 @@ func runServerTest(c *qt.C, getHome bool, config string, args ...string) (result
time.Sleep(567 * time.Millisecond) time.Sleep(567 * time.Millisecond)
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port)) resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port))
c.Check(err, qt.IsNil) c.Check(err, qt.IsNil)
c.Check(resp.StatusCode, qt.Equals, http.StatusOK)
if err == nil { if err == nil {
defer resp.Body.Close() defer resp.Body.Close()
result.homeContent = helpers.ReaderToString(resp.Body) result.homeContent = helpers.ReaderToString(resp.Body)
} }
} }
time.Sleep(1 * time.Second)
select { select {
case <-stop: case <-stop:
case stop <- true: case stop <- true:
@ -191,7 +195,7 @@ func TestFixURL(t *testing.T) {
t.Run(test.TestName, func(t *testing.T) { t.Run(test.TestName, func(t *testing.T) {
b := newCommandsBuilder() b := newCommandsBuilder()
s := b.newServerCmd() s := b.newServerCmd()
v := config.New() v := config.NewWithTestDefaults()
baseURL := test.CLIBaseURL baseURL := test.CLIBaseURL
v.Set("baseURL", test.CfgBaseURL) v.Set("baseURL", test.CfgBaseURL)
s.serverAppend = test.AppendPort s.serverAppend = test.AppendPort

View file

@ -40,11 +40,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
c := s.c c := s.c
syncFn := func(sourceFs *filesystems.SourceFilesystem) (uint64, error) { syncFn := func(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
publishDir := c.hugo().PathSpec.PublishDir publishDir := helpers.FilePathSeparator
// If root, remove the second '/'
if publishDir == "//" {
publishDir = helpers.FilePathSeparator
}
if sourceFs.PublishFolder != "" { if sourceFs.PublishFolder != "" {
publishDir = filepath.Join(publishDir, sourceFs.PublishFolder) publishDir = filepath.Join(publishDir, sourceFs.PublishFolder)
@ -55,9 +51,9 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
syncer.NoChmod = c.Cfg.GetBool("noChmod") syncer.NoChmod = c.Cfg.GetBool("noChmod")
syncer.ChmodFilter = chmodFilter syncer.ChmodFilter = chmodFilter
syncer.SrcFs = sourceFs.Fs syncer.SrcFs = sourceFs.Fs
syncer.DestFs = c.Fs.Destination syncer.DestFs = c.Fs.PublishDir
if c.renderStaticToDisk { if c.renderStaticToDisk {
syncer.DestFs = c.Fs.DestinationStatic syncer.DestFs = c.Fs.PublishDirStatic
} }
// prevent spamming the log on changes // prevent spamming the log on changes
@ -101,19 +97,14 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
if ev.Op&fsnotify.Rename == fsnotify.Rename || ev.Op&fsnotify.Remove == fsnotify.Remove { if ev.Op&fsnotify.Rename == fsnotify.Rename || ev.Op&fsnotify.Remove == fsnotify.Remove {
if _, err := sourceFs.Fs.Stat(relPath); os.IsNotExist(err) { if _, err := sourceFs.Fs.Stat(relPath); os.IsNotExist(err) {
// If file doesn't exist in any static dir, remove it // 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", relPath)
_ = c.Fs.PublishDirStatic.RemoveAll(relPath)
logger.Println("File no longer exists in static dir, removing", toRemove)
if c.renderStaticToDisk {
_ = c.Fs.DestinationStatic.RemoveAll(toRemove)
} else {
_ = c.Fs.Destination.RemoveAll(toRemove)
}
} else if err == nil { } else if err == nil {
// If file still exists, sync it // If file still exists, sync it
logger.Println("Syncing", relPath, "to", publishDir) logger.Println("Syncing", relPath, "to", publishDir)
if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil { if err := syncer.Sync(relPath, relPath); err != nil {
c.logger.Errorln(err) c.logger.Errorln(err)
} }
} else { } else {

View file

@ -63,6 +63,15 @@ func (filepathBridge) Separator() string {
var fpb filepathBridge var fpb filepathBridge
// AbsPathify creates an absolute path if given a working dir and a relative path.
// If already absolute, the path is just cleaned.
func AbsPathify(workingDir, inPath string) string {
if filepath.IsAbs(inPath) {
return filepath.Clean(inPath)
}
return filepath.Join(workingDir, inPath)
}
// MakeTitle converts the path given to a suitable title, trimming whitespace // MakeTitle converts the path given to a suitable title, trimming whitespace
// and replacing hyphens with whitespace. // and replacing hyphens with whitespace.
func MakeTitle(inpath string) string { func MakeTitle(inpath string) string {

View file

@ -45,13 +45,23 @@ func GetStringSlicePreserveString(cfg Provider, key string) []string {
} }
// SetBaseTestDefaults provides some common config defaults used in tests. // SetBaseTestDefaults provides some common config defaults used in tests.
func SetBaseTestDefaults(cfg Provider) { func SetBaseTestDefaults(cfg Provider) Provider {
cfg.Set("resourceDir", "resources") setIfNotSet(cfg, "baseURL", "https://example.org")
cfg.Set("contentDir", "content") setIfNotSet(cfg, "resourceDir", "resources")
cfg.Set("dataDir", "data") setIfNotSet(cfg, "contentDir", "content")
cfg.Set("i18nDir", "i18n") setIfNotSet(cfg, "dataDir", "data")
cfg.Set("layoutDir", "layouts") setIfNotSet(cfg, "i18nDir", "i18n")
cfg.Set("assetDir", "assets") setIfNotSet(cfg, "layoutDir", "layouts")
cfg.Set("archetypeDir", "archetypes") setIfNotSet(cfg, "assetDir", "assets")
cfg.Set("publishDir", "public") setIfNotSet(cfg, "archetypeDir", "archetypes")
setIfNotSet(cfg, "publishDir", "public")
setIfNotSet(cfg, "workingDir", "")
setIfNotSet(cfg, "defaultContentLanguage", "en")
return cfg
}
func setIfNotSet(cfg Provider, key string, value any) {
if !cfg.IsSet(key) {
cfg.Set(key, value)
}
} }

View file

@ -75,6 +75,11 @@ func NewFrom(params maps.Params) Provider {
} }
} }
// NewWithTestDefaults is used in tests only.
func NewWithTestDefaults() Provider {
return SetBaseTestDefaults(New())
}
// defaultConfigProvider is a Provider backed by a map where all keys are lower case. // defaultConfigProvider is a Provider backed by a map where all keys are lower case.
// All methods are thread safe. // All methods are thread safe.
type defaultConfigProvider struct { type defaultConfigProvider struct {

View file

@ -54,7 +54,7 @@ disableInlineCSS = true
func TestUseSettingsFromRootIfSet(t *testing.T) { func TestUseSettingsFromRootIfSet(t *testing.T) {
c := qt.New(t) c := qt.New(t)
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("disqusShortname", "root_short") cfg.Set("disqusShortname", "root_short")
cfg.Set("googleAnalytics", "ga_root") cfg.Set("googleAnalytics", "ga_root")

3
go.mod
View file

@ -48,7 +48,7 @@ require (
github.com/russross/blackfriday v1.6.0 github.com/russross/blackfriday v1.6.0
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
github.com/sanity-io/litter v1.5.4 github.com/sanity-io/litter v1.5.4
github.com/spf13/afero v1.8.1 github.com/spf13/afero v1.8.2
github.com/spf13/cast v1.4.1 github.com/spf13/cast v1.4.1
github.com/spf13/cobra v1.4.0 github.com/spf13/cobra v1.4.0
github.com/spf13/fsync v0.9.0 github.com/spf13/fsync v0.9.0
@ -98,6 +98,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 // indirect
github.com/aws/smithy-go v1.11.2 // indirect github.com/aws/smithy-go v1.11.2 // indirect
github.com/bep/overlayfs v0.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect

4
go.sum
View file

@ -186,6 +186,8 @@ github.com/bep/golibsass v1.0.0 h1:gNguBMSDi5yZEZzVZP70YpuFQE3qogJIGUlrVILTmOw=
github.com/bep/golibsass v1.0.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA= github.com/bep/golibsass v1.0.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
github.com/bep/gowebp v0.1.0 h1:4/iQpfnxHyXs3x/aTxMMdOpLEQQhFmF6G7EieWPTQyo= github.com/bep/gowebp v0.1.0 h1:4/iQpfnxHyXs3x/aTxMMdOpLEQQhFmF6G7EieWPTQyo=
github.com/bep/gowebp v0.1.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI= github.com/bep/gowebp v0.1.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
github.com/bep/overlayfs v0.1.0 h1:1hOCrvS4E5Hf0qwxM7m+9oitqClD9mRjQ1d4pECsVcU=
github.com/bep/overlayfs v0.1.0/go.mod h1:NFjSmn3kCqG7KX2Lmz8qT8VhPPCwZap3UNogXawoQHM=
github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI= github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0= github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg= github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg=
@ -564,6 +566,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.8.1 h1:izYHOT71f9iZ7iq37Uqjael60/vYC6vMtzedudZ0zEk= github.com/spf13/afero v1.8.1 h1:izYHOT71f9iZ7iq37Uqjael60/vYC6vMtzedudZ0zEk=
github.com/spf13/afero v1.8.1/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/afero v1.8.1/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=

View file

@ -19,10 +19,10 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/gohugoio/hugo/config"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
) )
@ -102,7 +102,7 @@ func TestBytesToHTML(t *testing.T) {
} }
func TestNewContentSpec(t *testing.T) { func TestNewContentSpec(t *testing.T) {
cfg := config.New() cfg := config.NewWithTestDefaults()
c := qt.New(t) c := qt.New(t)
cfg.Set("summaryLength", 32) cfg.Set("summaryLength", 32)

View file

@ -20,9 +20,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
"github.com/spf13/afero" "github.com/spf13/afero"
@ -30,7 +29,7 @@ import (
func TestResolveMarkup(t *testing.T) { func TestResolveMarkup(t *testing.T) {
c := qt.New(t) c := qt.New(t)
cfg := config.New() cfg := config.NewWithTestDefaults()
spec, err := NewContentSpec(cfg, loggers.NewErrorLogger(), afero.NewMemMapFs(), nil) spec, err := NewContentSpec(cfg, loggers.NewErrorLogger(), afero.NewMemMapFs(), nil)
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)

View file

@ -459,9 +459,17 @@ func IsDir(path string, fs afero.Fs) (bool, error) {
return afero.IsDir(fs, path) return afero.IsDir(fs, path)
} }
// IsEmpty checks if a given path is empty. // IsEmpty checks if a given path is empty, meaning it doesn't contain any regular files.
func IsEmpty(path string, fs afero.Fs) (bool, error) { func IsEmpty(path string, fs afero.Fs) (bool, error) {
return afero.IsEmpty(fs, path) var hasFile bool
err := afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
hasFile = true
return filepath.SkipDir
})
return !hasFile, err
} }
// Exists checks if a file or directory exists. // Exists checks if a file or directory exists.

View file

@ -256,55 +256,6 @@ func TestIsDir(t *testing.T) {
} }
} }
func TestIsEmpty(t *testing.T) {
zeroSizedFile, _ := createZeroSizedFileInTempDir()
defer deleteFileInTempDir(zeroSizedFile)
nonZeroSizedFile, _ := createNonZeroSizedFileInTempDir()
defer deleteFileInTempDir(nonZeroSizedFile)
emptyDirectory, _ := createEmptyTempDir()
defer deleteTempDir(emptyDirectory)
nonEmptyZeroLengthFilesDirectory, _ := createTempDirWithZeroLengthFiles()
defer deleteTempDir(nonEmptyZeroLengthFilesDirectory)
nonEmptyNonZeroLengthFilesDirectory, _ := createTempDirWithNonZeroLengthFiles()
defer deleteTempDir(nonEmptyNonZeroLengthFilesDirectory)
nonExistentFile := os.TempDir() + "/this-file-does-not-exist.txt"
nonExistentDir := os.TempDir() + "/this/directory/does/not/exist/"
fileDoesNotExist := fmt.Errorf("%q path does not exist", nonExistentFile)
dirDoesNotExist := fmt.Errorf("%q path does not exist", nonExistentDir)
type test struct {
input string
expectedResult bool
expectedErr error
}
data := []test{
{zeroSizedFile.Name(), true, nil},
{nonZeroSizedFile.Name(), false, nil},
{emptyDirectory, true, nil},
{nonEmptyZeroLengthFilesDirectory, false, nil},
{nonEmptyNonZeroLengthFilesDirectory, false, nil},
{nonExistentFile, false, fileDoesNotExist},
{nonExistentDir, false, dirDoesNotExist},
}
for i, d := range data {
exists, err := IsEmpty(d.input, new(afero.OsFs))
if d.expectedResult != exists {
t.Errorf("Test %d failed. Expected result %t got %t", i, d.expectedResult, exists)
}
if d.expectedErr != nil {
if d.expectedErr.Error() != err.Error() {
t.Errorf("Test %d failed. Expected %q(%#v) got %q(%#v)", i, d.expectedErr, d.expectedErr, err, err)
}
} else {
if d.expectedErr != err {
t.Errorf("Test %d failed. Expected %q(%#v) got %q(%#v)", i, d.expectedErr, d.expectedErr, err, err)
}
}
}
}
func createZeroSizedFileInTempDir() (*os.File, error) { func createZeroSizedFileInTempDir() (*os.File, error) {
filePrefix := "_path_test_" filePrefix := "_path_test_"
f, e := ioutil.TempFile("", filePrefix) // dir is os.TempDir() f, e := ioutil.TempFile("", filePrefix) // dir is os.TempDir()
@ -346,51 +297,6 @@ func createEmptyTempDir() (string, error) {
return d, nil return d, nil
} }
func createTempDirWithZeroLengthFiles() (string, error) {
d, dirErr := createEmptyTempDir()
if dirErr != nil {
return "", dirErr
}
filePrefix := "_path_test_"
_, fileErr := ioutil.TempFile(d, filePrefix) // dir is os.TempDir()
if fileErr != nil {
// if there was an error no file was created.
// but we need to remove the directory to clean-up
deleteTempDir(d)
return "", fileErr
}
// the dir now has one, zero length file in it
return d, nil
}
func createTempDirWithNonZeroLengthFiles() (string, error) {
d, dirErr := createEmptyTempDir()
if dirErr != nil {
return "", dirErr
}
filePrefix := "_path_test_"
f, fileErr := ioutil.TempFile(d, filePrefix) // dir is os.TempDir()
if fileErr != nil {
// if there was an error no file was created.
// but we need to remove the directory to clean-up
deleteTempDir(d)
return "", fileErr
}
byteString := []byte("byteString")
fileErr = ioutil.WriteFile(f.Name(), byteString, 0644)
if fileErr != nil {
// delete the file
deleteFileInTempDir(f)
// also delete the directory
deleteTempDir(d)
return "", fileErr
}
// the dir now has one, zero length file in it
return d, nil
}
func deleteTempDir(d string) { func deleteTempDir(d string) {
_ = os.RemoveAll(d) _ = os.RemoveAll(d)
} }

View file

@ -17,9 +17,8 @@ func newTestPathSpec(fs *hugofs.Fs, v config.Provider) *PathSpec {
} }
func newTestDefaultPathSpec(configKeyValues ...any) *PathSpec { func newTestDefaultPathSpec(configKeyValues ...any) *PathSpec {
v := config.New()
fs := hugofs.NewMem(v)
cfg := newTestCfg() cfg := newTestCfg()
fs := hugofs.NewMem(cfg)
for i := 0; i < len(configKeyValues); i += 2 { for i := 0; i < len(configKeyValues); i += 2 {
cfg.Set(configKeyValues[i].(string), configKeyValues[i+1]) cfg.Set(configKeyValues[i].(string), configKeyValues[i+1])
@ -28,15 +27,7 @@ func newTestDefaultPathSpec(configKeyValues ...any) *PathSpec {
} }
func newTestCfg() config.Provider { func newTestCfg() config.Provider {
v := config.New() v := config.NewWithTestDefaults()
v.Set("contentDir", "content")
v.Set("dataDir", "data")
v.Set("i18nDir", "i18n")
v.Set("layoutDir", "layouts")
v.Set("assetDir", "assets")
v.Set("resourceDir", "resources")
v.Set("publishDir", "public")
v.Set("archetypeDir", "archetypes")
langs.LoadLanguageSettings(v, nil) langs.LoadLanguageSettings(v, nil)
langs.LoadLanguageSettings(v, nil) langs.LoadLanguageSettings(v, nil)
mod, err := modules.CreateProjectModule(v) mod, err := modules.CreateProjectModule(v)
@ -49,7 +40,7 @@ func newTestCfg() config.Provider {
} }
func newTestContentSpec() *ContentSpec { func newTestContentSpec() *ContentSpec {
v := config.New() v := config.NewWithTestDefaults()
spec, err := NewContentSpec(v, loggers.NewErrorLogger(), afero.NewMemMapFs(), nil) spec, err := NewContentSpec(v, loggers.NewErrorLogger(), afero.NewMemMapFs(), nil)
if err != nil { if err != nil {
panic(err) panic(err)

View file

@ -33,10 +33,18 @@ type DuplicatesReporter interface {
ReportDuplicates() string ReportDuplicates() string
} }
var (
_ FilesystemUnwrapper = (*createCountingFs)(nil)
)
func NewCreateCountingFs(fs afero.Fs) afero.Fs { func NewCreateCountingFs(fs afero.Fs) afero.Fs {
return &createCountingFs{Fs: fs, fileCount: make(map[string]int)} return &createCountingFs{Fs: fs, fileCount: make(map[string]int)}
} }
func (fs *createCountingFs) UnwrapFilesystem() afero.Fs {
return fs.Fs
}
// ReportDuplicates reports filenames written more than once. // ReportDuplicates reports filenames written more than once.
func (c *createCountingFs) ReportDuplicates() string { func (c *createCountingFs) ReportDuplicates() string {
c.mu.Lock() c.mu.Lock()

View file

@ -23,6 +23,10 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
) )
var (
_ FilesystemUnwrapper = (*baseFileDecoratorFs)(nil)
)
func decorateDirs(fs afero.Fs, meta *FileMeta) afero.Fs { func decorateDirs(fs afero.Fs, meta *FileMeta) afero.Fs {
ffs := &baseFileDecoratorFs{Fs: fs} ffs := &baseFileDecoratorFs{Fs: fs}
@ -151,6 +155,10 @@ type baseFileDecoratorFs struct {
decorate func(fi os.FileInfo, filename string) (os.FileInfo, error) decorate func(fi os.FileInfo, filename string) (os.FileInfo, error)
} }
func (fs *baseFileDecoratorFs) UnwrapFilesystem() afero.Fs {
return fs.Fs
}
func (fs *baseFileDecoratorFs) Stat(name string) (os.FileInfo, error) { func (fs *baseFileDecoratorFs) Stat(name string) (os.FileInfo, error) {
fi, err := fs.Fs.Stat(name) fi, err := fs.Fs.Stat(name)
if err != nil { if err != nil {

View file

@ -23,6 +23,10 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
) )
var (
_ FilesystemUnwrapper = (*filenameFilterFs)(nil)
)
func newFilenameFilterFs(fs afero.Fs, base string, filter *glob.FilenameFilter) afero.Fs { func newFilenameFilterFs(fs afero.Fs, base string, filter *glob.FilenameFilter) afero.Fs {
return &filenameFilterFs{ return &filenameFilterFs{
fs: fs, fs: fs,
@ -39,6 +43,10 @@ type filenameFilterFs struct {
filter *glob.FilenameFilter filter *glob.FilenameFilter
} }
func (fs *filenameFilterFs) UnwrapFilesystem() afero.Fs {
return fs.fs
}
func (fs *filenameFilterFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { func (fs *filenameFilterFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
fi, b, err := fs.fs.(afero.Lstater).LstatIfPossible(name) fi, b, err := fs.fs.(afero.Lstater).LstatIfPossible(name)
if err != nil { if err != nil {

View file

@ -121,6 +121,10 @@ func NewFilterFs(fs afero.Fs) (afero.Fs, error) {
return ffs, nil return ffs, nil
} }
var (
_ FilesystemUnwrapper = (*FilterFs)(nil)
)
// FilterFs is an ordered composite filesystem. // FilterFs is an ordered composite filesystem.
type FilterFs struct { type FilterFs struct {
fs afero.Fs fs afero.Fs
@ -141,6 +145,10 @@ func (fs *FilterFs) Chown(n string, uid, gid int) error {
return syscall.EPERM return syscall.EPERM
} }
func (fs *FilterFs) UnwrapFilesystem() afero.Fs {
return fs.fs
}
func (fs *FilterFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { func (fs *FilterFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
fi, b, err := lstatIfPossible(fs.fs, name) fi, b, err := lstatIfPossible(fs.fs, name)
if err != nil { if err != nil {

View file

@ -19,6 +19,8 @@ import (
"os" "os"
"strings" "strings"
"github.com/bep/overlayfs"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config"
"github.com/spf13/afero" "github.com/spf13/afero"
) )
@ -26,32 +28,43 @@ import (
// Os points to the (real) Os filesystem. // Os points to the (real) Os filesystem.
var Os = &afero.OsFs{} var Os = &afero.OsFs{}
// Fs abstracts the file system to separate source and destination file systems // Fs holds the core filesystems used by Hugo.
// and allows both to be mocked for testing.
type Fs struct { type Fs struct {
// Source is Hugo's source file system. // Source is Hugo's source file system.
// Note that this will always be a "plain" Afero filesystem:
// * afero.OsFs when running in production
// * afero.MemMapFs for many of the tests.
Source afero.Fs Source afero.Fs
// Destination is Hugo's destination file system. // PublishDir is where Hugo publishes its rendered content.
Destination afero.Fs // It's mounted inside publishDir (default /public).
PublishDir afero.Fs
// Destination used for `renderStaticToDisk` // PublishDirStatic is the file system used for static files when --renderStaticToDisk is set.
DestinationStatic afero.Fs // When this is set, PublishDir is set to write to memory.
PublishDirStatic afero.Fs
// PublishDirServer is the file system used for serving the public directory with Hugo's development server.
// This will typically be the same as PublishDir, but not if --renderStaticToDisk is set.
PublishDirServer afero.Fs
// Os is an OS file system. // Os is an OS file system.
// NOTE: Field is currently unused. // NOTE: Field is currently unused.
Os afero.Fs Os afero.Fs
// WorkingDir is a read-only file system // WorkingDirReadOnly is a read-only file system
// restricted to the project working dir. // restricted to the project working dir.
// TODO(bep) get rid of this (se BaseFs) WorkingDirReadOnly afero.Fs
WorkingDir *afero.BasePathFs
// WorkingDirWritable is a writable file system
// restricted to the project working dir.
WorkingDirWritable afero.Fs
} }
// NewDefault creates a new Fs with the OS file system // NewDefault creates a new Fs with the OS file system
// as source and destination file systems. // as source and destination file systems.
func NewDefault(cfg config.Provider) *Fs { func NewDefault(cfg config.Provider) *Fs {
fs := &afero.OsFs{} fs := Os
return newFs(fs, cfg) return newFs(fs, cfg)
} }
@ -71,23 +84,49 @@ func NewFrom(fs afero.Fs, cfg config.Provider) *Fs {
} }
func newFs(base afero.Fs, cfg config.Provider) *Fs { func newFs(base afero.Fs, cfg config.Provider) *Fs {
workingDir := cfg.GetString("workingDir")
publishDir := cfg.GetString("publishDir")
if publishDir == "" {
panic("publishDir is empty")
}
// Sanity check
if IsOsFs(base) && len(workingDir) < 2 {
panic("workingDir is too short")
}
absPublishDir := paths.AbsPathify(workingDir, publishDir)
// Make sure we always have the /public folder ready to use.
if err := base.MkdirAll(absPublishDir, 0777); err != nil && !os.IsExist(err) {
panic(err)
}
pubFs := afero.NewBasePathFs(base, absPublishDir)
return &Fs{ return &Fs{
Source: base, Source: base,
Destination: base, PublishDir: pubFs,
DestinationStatic: base, PublishDirServer: pubFs,
PublishDirStatic: pubFs,
Os: &afero.OsFs{}, Os: &afero.OsFs{},
WorkingDir: getWorkingDirFs(base, cfg), WorkingDirReadOnly: getWorkingDirFsReadOnly(base, workingDir),
WorkingDirWritable: getWorkingDirFsWritable(base, workingDir),
} }
} }
func getWorkingDirFs(base afero.Fs, cfg config.Provider) *afero.BasePathFs { func getWorkingDirFsReadOnly(base afero.Fs, workingDir string) afero.Fs {
workingDir := cfg.GetString("workingDir") if workingDir == "" {
return afero.NewReadOnlyFs(base)
if workingDir != "" { }
return afero.NewBasePathFs(afero.NewReadOnlyFs(base), workingDir).(*afero.BasePathFs) return afero.NewBasePathFs(afero.NewReadOnlyFs(base), workingDir)
} }
return nil func getWorkingDirFsWritable(base afero.Fs, workingDir string) afero.Fs {
if workingDir == "" {
return base
}
return afero.NewBasePathFs(base, workingDir)
} }
func isWrite(flag int) bool { func isWrite(flag int) bool {
@ -117,3 +156,64 @@ func MakeReadableAndRemoveAllModulePkgDir(fs afero.Fs, dir string) (int, error)
}) })
return counter, fs.RemoveAll(dir) return counter, fs.RemoveAll(dir)
} }
// HasOsFs returns whether fs is an OsFs or if it fs wraps an OsFs.
// TODO(bep) make this nore robust.
func IsOsFs(fs afero.Fs) bool {
var isOsFs bool
WalkFilesystems(fs, func(fs afero.Fs) bool {
switch base := fs.(type) {
case *afero.MemMapFs:
isOsFs = false
case *afero.OsFs:
isOsFs = true
case *afero.BasePathFs:
_, supportsLstat, _ := base.LstatIfPossible("asdfasdfasdf")
isOsFs = supportsLstat
}
return isOsFs
})
return isOsFs
}
// FilesystemsUnwrapper returns the underlying filesystems.
type FilesystemsUnwrapper interface {
UnwrapFilesystems() []afero.Fs
}
// FilesystemsProvider returns the underlying filesystem.
type FilesystemUnwrapper interface {
UnwrapFilesystem() afero.Fs
}
// WalkFn is the walk func for WalkFilesystems.
type WalkFn func(fs afero.Fs) bool
// WalkFilesystems walks fs recursively and calls fn.
// If fn returns true, walking is stopped.
func WalkFilesystems(fs afero.Fs, fn WalkFn) bool {
if fn(fs) {
return true
}
if afs, ok := fs.(FilesystemUnwrapper); ok {
if WalkFilesystems(afs.UnwrapFilesystem(), fn) {
return true
}
} else if bfs, ok := fs.(FilesystemsUnwrapper); ok {
for _, sf := range bfs.UnwrapFilesystems() {
if WalkFilesystems(sf, fn) {
return true
}
}
} else if cfs, ok := fs.(overlayfs.FilesystemIterator); ok {
for i := 0; i < cfs.NumFilesystems(); i++ {
if WalkFilesystems(cfs.Filesystem(i), fn) {
return true
}
}
}
return false
}

View file

@ -23,38 +23,46 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
) )
func TestIsOsFs(t *testing.T) {
c := qt.New(t)
c.Assert(IsOsFs(Os), qt.Equals, true)
c.Assert(IsOsFs(&afero.MemMapFs{}), qt.Equals, false)
c.Assert(IsOsFs(afero.NewBasePathFs(&afero.MemMapFs{}, "/public")), qt.Equals, false)
c.Assert(IsOsFs(afero.NewBasePathFs(Os, t.TempDir())), qt.Equals, true)
}
func TestNewDefault(t *testing.T) { func TestNewDefault(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
v.Set("workingDir", t.TempDir())
f := NewDefault(v) f := NewDefault(v)
c.Assert(f.Source, qt.Not(qt.IsNil)) c.Assert(f.Source, qt.IsNotNil)
c.Assert(f.Source, hqt.IsSameType, new(afero.OsFs)) c.Assert(f.Source, hqt.IsSameType, new(afero.OsFs))
c.Assert(f.Os, qt.Not(qt.IsNil)) c.Assert(f.Os, qt.IsNotNil)
c.Assert(f.WorkingDir, qt.IsNil) c.Assert(f.WorkingDirReadOnly, qt.IsNotNil)
c.Assert(f.WorkingDirReadOnly, hqt.IsSameType, new(afero.BasePathFs))
c.Assert(IsOsFs(f.Source), qt.IsTrue)
c.Assert(IsOsFs(f.WorkingDirReadOnly), qt.IsTrue)
c.Assert(IsOsFs(f.PublishDir), qt.IsTrue)
c.Assert(IsOsFs(f.Os), qt.IsTrue)
} }
func TestNewMem(t *testing.T) { func TestNewMem(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
f := NewMem(v) f := NewMem(v)
c.Assert(f.Source, qt.Not(qt.IsNil)) c.Assert(f.Source, qt.Not(qt.IsNil))
c.Assert(f.Source, hqt.IsSameType, new(afero.MemMapFs)) c.Assert(f.Source, hqt.IsSameType, new(afero.MemMapFs))
c.Assert(f.Destination, qt.Not(qt.IsNil)) c.Assert(f.PublishDir, qt.Not(qt.IsNil))
c.Assert(f.Destination, hqt.IsSameType, new(afero.MemMapFs)) c.Assert(f.PublishDir, hqt.IsSameType, new(afero.BasePathFs))
c.Assert(f.Os, hqt.IsSameType, new(afero.OsFs)) c.Assert(f.Os, hqt.IsSameType, new(afero.OsFs))
c.Assert(f.WorkingDir, qt.IsNil) c.Assert(f.WorkingDirReadOnly, qt.IsNotNil)
} c.Assert(IsOsFs(f.Source), qt.IsFalse)
c.Assert(IsOsFs(f.WorkingDirReadOnly), qt.IsFalse)
func TestWorkingDir(t *testing.T) { c.Assert(IsOsFs(f.PublishDir), qt.IsFalse)
c := qt.New(t) c.Assert(IsOsFs(f.Os), qt.IsTrue)
v := config.New()
v.Set("workingDir", "/a/b/")
f := NewMem(v)
c.Assert(f.WorkingDir, qt.Not(qt.IsNil))
c.Assert(f.WorkingDir, hqt.IsSameType, new(afero.BasePathFs))
} }

View file

@ -22,7 +22,10 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
) )
var _ afero.Fs = (*md5HashingFs)(nil) var (
_ afero.Fs = (*md5HashingFs)(nil)
_ FilesystemUnwrapper = (*md5HashingFs)(nil)
)
// FileHashReceiver will receive the filename an the content's MD5 sum on file close. // FileHashReceiver will receive the filename an the content's MD5 sum on file close.
type FileHashReceiver interface { type FileHashReceiver interface {
@ -45,6 +48,10 @@ func NewHashingFs(delegate afero.Fs, hashReceiver FileHashReceiver) afero.Fs {
return &md5HashingFs{Fs: delegate, hashReceiver: hashReceiver} return &md5HashingFs{Fs: delegate, hashReceiver: hashReceiver}
} }
func (fs *md5HashingFs) UnwrapFilesystem() afero.Fs {
return fs.Fs
}
func (fs *md5HashingFs) Create(name string) (afero.File, error) { func (fs *md5HashingFs) Create(name string) (afero.File, error) {
f, err := fs.Fs.Create(name) f, err := fs.Fs.Create(name)
if err == nil { if err == nil {

View file

@ -22,9 +22,12 @@ import (
var ( var (
_ afero.Fs = (*languageCompositeFs)(nil) _ afero.Fs = (*languageCompositeFs)(nil)
_ afero.Lstater = (*languageCompositeFs)(nil) _ afero.Lstater = (*languageCompositeFs)(nil)
_ FilesystemsUnwrapper = (*languageCompositeFs)(nil)
) )
type languageCompositeFs struct { type languageCompositeFs struct {
base afero.Fs
overlay afero.Fs
*afero.CopyOnWriteFs *afero.CopyOnWriteFs
} }
@ -33,7 +36,11 @@ type languageCompositeFs struct {
// to the target filesystem. This information is available in Readdir, Stat etc. via the // to the target filesystem. This information is available in Readdir, Stat etc. via the
// special LanguageFileInfo FileInfo implementation. // special LanguageFileInfo FileInfo implementation.
func NewLanguageCompositeFs(base, overlay afero.Fs) afero.Fs { func NewLanguageCompositeFs(base, overlay afero.Fs) afero.Fs {
return &languageCompositeFs{afero.NewCopyOnWriteFs(base, overlay).(*afero.CopyOnWriteFs)} return &languageCompositeFs{base, overlay, afero.NewCopyOnWriteFs(base, overlay).(*afero.CopyOnWriteFs)}
}
func (fs *languageCompositeFs) UnwrapFilesystems() []afero.Fs {
return []afero.Fs{fs.base, fs.overlay}
} }
// Open takes the full path to the file in the target filesystem. If it is a directory, it gets merged // Open takes the full path to the file in the target filesystem. If it is a directory, it gets merged

View file

@ -30,6 +30,10 @@ func NewNoSymlinkFs(fs afero.Fs, logger loggers.Logger, allowFiles bool) afero.F
return &noSymlinkFs{Fs: fs, logger: logger, allowFiles: allowFiles} return &noSymlinkFs{Fs: fs, logger: logger, allowFiles: allowFiles}
} }
var (
_ FilesystemUnwrapper = (*noSymlinkFs)(nil)
)
// noSymlinkFs is a filesystem that prevents symlinking. // noSymlinkFs is a filesystem that prevents symlinking.
type noSymlinkFs struct { type noSymlinkFs struct {
allowFiles bool // block dirs only allowFiles bool // block dirs only
@ -67,6 +71,10 @@ func (f *noSymlinkFile) Readdirnames(count int) ([]string, error) {
return fileInfosToNames(dirs), nil return fileInfosToNames(dirs), nil
} }
func (fs *noSymlinkFs) UnwrapFilesystem() afero.Fs {
return fs.Fs
}
func (fs *noSymlinkFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { func (fs *noSymlinkFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
return fs.stat(name) return fs.stat(name)
} }

View file

@ -151,6 +151,10 @@ func (r RootMapping) trimFrom(name string) string {
return strings.TrimPrefix(name, r.From) return strings.TrimPrefix(name, r.From)
} }
var (
_ FilesystemUnwrapper = (*RootMappingFs)(nil)
)
// A RootMappingFs maps several roots into one. Note that the root of this filesystem // A RootMappingFs maps several roots into one. Note that the root of this filesystem
// is directories only, and they will be returned in Readdir and Readdirnames // is directories only, and they will be returned in Readdir and Readdirnames
// in the order given. // in the order given.
@ -200,6 +204,10 @@ func (fs *RootMappingFs) Dirs(base string) ([]FileMetaInfo, error) {
return fss, nil return fss, nil
} }
func (fs *RootMappingFs) UnwrapFilesystem() afero.Fs {
return fs.Fs
}
// Filter creates a copy of this filesystem with only mappings matching a filter. // Filter creates a copy of this filesystem with only mappings matching a filter.
func (fs RootMappingFs) Filter(f func(m RootMapping) bool) *RootMappingFs { func (fs RootMappingFs) Filter(f func(m RootMapping) bool) *RootMappingFs {
rootMapToReal := radix.New() rootMapToReal := radix.New()

View file

@ -20,9 +20,8 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/gohugoio/hugo/hugofs/glob"
"github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs/glob"
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/htesting" "github.com/gohugoio/hugo/htesting"
@ -31,7 +30,7 @@ import (
func TestLanguageRootMapping(t *testing.T) { func TestLanguageRootMapping(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
v.Set("contentDir", "content") v.Set("contentDir", "content")
fs := NewBaseFileDecorator(afero.NewMemMapFs()) fs := NewBaseFileDecorator(afero.NewMemMapFs())

View file

@ -26,6 +26,7 @@ import (
var ( var (
_ afero.Fs = (*SliceFs)(nil) _ afero.Fs = (*SliceFs)(nil)
_ afero.Lstater = (*SliceFs)(nil) _ afero.Lstater = (*SliceFs)(nil)
_ FilesystemsUnwrapper = (*SliceFs)(nil)
_ afero.File = (*sliceDir)(nil) _ afero.File = (*sliceDir)(nil)
) )
@ -52,6 +53,14 @@ type SliceFs struct {
dirs []FileMetaInfo dirs []FileMetaInfo
} }
func (fs *SliceFs) UnwrapFilesystems() []afero.Fs {
var fss []afero.Fs
for _, dir := range fs.dirs {
fss = append(fss, dir.Meta().Fs)
}
return fss
}
func (fs *SliceFs) Chmod(n string, m os.FileMode) error { func (fs *SliceFs) Chmod(n string, m os.FileMode) error {
return syscall.EPERM return syscall.EPERM
} }

View file

@ -24,8 +24,11 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
) )
var (
// Make sure we don't accidentally use this in the real Hugo. // Make sure we don't accidentally use this in the real Hugo.
var _ types.DevMarker = (*stacktracerFs)(nil) _ types.DevMarker = (*stacktracerFs)(nil)
_ FilesystemUnwrapper = (*stacktracerFs)(nil)
)
// NewStacktracerFs wraps the given fs printing stack traces for file creates // NewStacktracerFs wraps the given fs printing stack traces for file creates
// matching the given regexp pattern. // matching the given regexp pattern.
@ -45,6 +48,10 @@ type stacktracerFs struct {
func (fs *stacktracerFs) DevOnly() { func (fs *stacktracerFs) DevOnly() {
} }
func (fs *stacktracerFs) UnwrapFilesystem() afero.Fs {
return fs.Fs
}
func (fs *stacktracerFs) onCreate(filename string) { func (fs *stacktracerFs) onCreate(filename string) {
if fs.re.MatchString(filename) { if fs.re.MatchString(filename) {
trace := make([]byte, 1500) trace := make([]byte, 1500)

View file

@ -35,7 +35,6 @@ import (
"github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/hugolib/paths"
"github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/modules" "github.com/gohugoio/hugo/modules"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -359,7 +358,7 @@ func (l configLoader) collectModules(modConfig modules.Config, v1 config.Provide
workingDir = v1.GetString("workingDir") workingDir = v1.GetString("workingDir")
} }
themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir")) themesDir := cpaths.AbsPathify(l.WorkingDir, v1.GetString("themesDir"))
var ignoreVendor glob.Glob var ignoreVendor glob.Glob
if s := v1.GetString("ignoreVendorPaths"); s != "" { if s := v1.GetString("ignoreVendorPaths"); s != "" {

View file

@ -38,8 +38,8 @@ import (
"github.com/gohugoio/hugo/modules" "github.com/gohugoio/hugo/modules"
hpaths "github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugolib/paths" "github.com/gohugoio/hugo/hugolib/paths"
"github.com/spf13/afero" "github.com/spf13/afero"
) )
@ -68,12 +68,12 @@ type BaseFs struct {
// This usually maps to /my-project/public. // This usually maps to /my-project/public.
PublishFs afero.Fs PublishFs afero.Fs
// A read-only filesystem starting from the project workDir.
WorkDir afero.Fs
// The filesystem used for renderStaticToDisk. // The filesystem used for renderStaticToDisk.
PublishFsStatic afero.Fs PublishFsStatic afero.Fs
// A read-only filesystem starting from the project workDir.
WorkDir afero.Fs
theBigFs *filesystemsCollector theBigFs *filesystemsCollector
// Locks. // Locks.
@ -434,21 +434,13 @@ func NewBase(p *paths.Paths, logger loggers.Logger, options ...func(*BaseFs) err
logger = loggers.NewWarningLogger() logger = loggers.NewWarningLogger()
} }
// Make sure we always have the /public folder ready to use. publishFs := hugofs.NewBaseFileDecorator(fs.PublishDir)
if err := fs.Destination.MkdirAll(p.AbsPublishDir, 0777); err != nil && !os.IsExist(err) {
return nil, err
}
publishFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Destination, p.AbsPublishDir))
sourceFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Source, p.WorkingDir)) sourceFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Source, p.WorkingDir))
publishFsStatic := afero.NewBasePathFs(fs.Source, p.AbsPublishDir) publishFsStatic := fs.PublishDirStatic
// Same as sourceFs, but no decoration. This is what's used by os.ReadDir etc.
workDir := afero.NewBasePathFs(afero.NewReadOnlyFs(fs.Source), p.WorkingDir)
b := &BaseFs{ b := &BaseFs{
SourceFs: sourceFs, SourceFs: sourceFs,
WorkDir: workDir, WorkDir: fs.WorkingDirReadOnly,
PublishFs: publishFs, PublishFs: publishFs,
PublishFsStatic: publishFsStatic, PublishFsStatic: publishFsStatic,
buildMu: lockedfile.MutexAt(filepath.Join(p.WorkingDir, lockFileBuild)), buildMu: lockedfile.MutexAt(filepath.Join(p.WorkingDir, lockFileBuild)),
@ -638,7 +630,7 @@ func (b *sourceFilesystemsBuilder) createModFs(
if filepath.IsAbs(path) { if filepath.IsAbs(path) {
return "", path return "", path
} }
return md.dir, paths.AbsPathify(md.dir, path) return md.dir, hpaths.AbsPathify(md.dir, path)
} }
for i, mount := range md.Mounts() { for i, mount := range md.Mounts() {

View file

@ -75,7 +75,7 @@ func initConfig(fs afero.Fs, cfg config.Provider) error {
func TestNewBaseFs(t *testing.T) { func TestNewBaseFs(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
fs := hugofs.NewMem(v) fs := hugofs.NewMem(v)
@ -181,7 +181,7 @@ theme = ["atheme"]
} }
func createConfig() config.Provider { func createConfig() config.Provider {
v := config.New() v := config.NewWithTestDefaults()
v.Set("contentDir", "mycontent") v.Set("contentDir", "mycontent")
v.Set("i18nDir", "myi18n") v.Set("i18nDir", "myi18n")
v.Set("staticDir", "mystatic") v.Set("staticDir", "mystatic")
@ -219,22 +219,19 @@ func TestNewBaseFsEmpty(t *testing.T) {
func TestRealDirs(t *testing.T) { func TestRealDirs(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := createConfig() v := createConfig()
root, themesDir := t.TempDir(), t.TempDir()
v.Set("workingDir", root)
v.Set("themesDir", themesDir)
v.Set("theme", "mytheme")
fs := hugofs.NewDefault(v) fs := hugofs.NewDefault(v)
sfs := fs.Source sfs := fs.Source
root, err := afero.TempDir(sfs, "", "realdir")
c.Assert(err, qt.IsNil)
themesDir, err := afero.TempDir(sfs, "", "themesDir")
c.Assert(err, qt.IsNil)
defer func() { defer func() {
os.RemoveAll(root) os.RemoveAll(root)
os.RemoveAll(themesDir) os.RemoveAll(themesDir)
}() }()
v.Set("workingDir", root)
v.Set("themesDir", themesDir)
v.Set("theme", "mytheme")
c.Assert(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf1"), 0755), qt.IsNil) c.Assert(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf1"), 0755), qt.IsNil)
c.Assert(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf2"), 0755), qt.IsNil) c.Assert(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf2"), 0755), qt.IsNil)
c.Assert(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf2"), 0755), qt.IsNil) c.Assert(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf2"), 0755), qt.IsNil)

View file

@ -59,13 +59,14 @@ path="github.com/gohugoio/hugoTestModule2"
return fmt.Sprintf(tomlConfig, workingDir, moduleOpts) return fmt.Sprintf(tomlConfig, workingDir, moduleOpts)
} }
newTestBuilder := func(t testing.TB, moduleOpts string) (*sitesBuilder, func()) { newTestBuilder := func(t testing.TB, moduleOpts string) *sitesBuilder {
b := newTestSitesBuilder(t) b := newTestSitesBuilder(t)
tempDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-modules-variants") tempDir := t.TempDir()
b.Assert(err, qt.IsNil)
workingDir := filepath.Join(tempDir, "myhugosite") workingDir := filepath.Join(tempDir, "myhugosite")
b.Assert(os.MkdirAll(workingDir, 0777), qt.IsNil) b.Assert(os.MkdirAll(workingDir, 0777), qt.IsNil)
b.Fs = hugofs.NewDefault(config.New()) cfg := config.NewWithTestDefaults()
cfg.Set("workingDir", workingDir)
b.Fs = hugofs.NewDefault(cfg)
b.WithWorkingDir(workingDir).WithConfigFile("toml", createConfig(workingDir, moduleOpts)) b.WithWorkingDir(workingDir).WithConfigFile("toml", createConfig(workingDir, moduleOpts))
b.WithTemplates( b.WithTemplates(
"index.html", ` "index.html", `
@ -92,22 +93,18 @@ github.com/gohugoio/hugoTestModule2 v0.0.0-20200131160637-9657d7697877 h1:WLM2bQ
github.com/gohugoio/hugoTestModule2 v0.0.0-20200131160637-9657d7697877/go.mod h1:CBFZS3khIAXKxReMwq0le8sEl/D8hcXmixlOHVv+Gd0= github.com/gohugoio/hugoTestModule2 v0.0.0-20200131160637-9657d7697877/go.mod h1:CBFZS3khIAXKxReMwq0le8sEl/D8hcXmixlOHVv+Gd0=
`) `)
return b, clean return b
} }
t.Run("Target in subfolder", func(t *testing.T) { t.Run("Target in subfolder", func(t *testing.T) {
b, clean := newTestBuilder(t, "ignoreImports=true") b := newTestBuilder(t, "ignoreImports=true")
defer clean()
b.Build(BuildCfg{}) b.Build(BuildCfg{})
b.AssertFileContent("public/p1/index.html", `<p>Page|https://bep.is|Title: |Text: A link|END</p>`) b.AssertFileContent("public/p1/index.html", `<p>Page|https://bep.is|Title: |Text: A link|END</p>`)
}) })
t.Run("Ignore config", func(t *testing.T) { t.Run("Ignore config", func(t *testing.T) {
b, clean := newTestBuilder(t, "ignoreConfig=true") b := newTestBuilder(t, "ignoreConfig=true")
defer clean()
b.Build(BuildCfg{}) b.Build(BuildCfg{})
b.AssertFileContent("public/index.html", ` b.AssertFileContent("public/index.html", `
@ -117,9 +114,7 @@ JS imported in module: |
}) })
t.Run("Ignore imports", func(t *testing.T) { t.Run("Ignore imports", func(t *testing.T) {
b, clean := newTestBuilder(t, "ignoreImports=true") b := newTestBuilder(t, "ignoreImports=true")
defer clean()
b.Build(BuildCfg{}) b.Build(BuildCfg{})
b.AssertFileContent("public/index.html", ` b.AssertFileContent("public/index.html", `
@ -129,8 +124,7 @@ JS imported in module: |
}) })
t.Run("Create package.json", func(t *testing.T) { t.Run("Create package.json", func(t *testing.T) {
b, clean := newTestBuilder(t, "") b := newTestBuilder(t, "")
defer clean()
b.WithSourceFile("package.json", `{ b.WithSourceFile("package.json", `{
"name": "mypack", "name": "mypack",
@ -205,8 +199,7 @@ JS imported in module: |
}) })
t.Run("Create package.json, no default", func(t *testing.T) { t.Run("Create package.json, no default", func(t *testing.T) {
b, clean := newTestBuilder(t, "") b := newTestBuilder(t, "")
defer clean()
const origPackageJSON = `{ const origPackageJSON = `{
"name": "mypack", "name": "mypack",
@ -268,8 +261,7 @@ JS imported in module: |
}) })
t.Run("Create package.json, no default, no package.json", func(t *testing.T) { t.Run("Create package.json, no default, no package.json", func(t *testing.T) {
b, clean := newTestBuilder(t, "") b := newTestBuilder(t, "")
defer clean()
b.Build(BuildCfg{}) b.Build(BuildCfg{})
b.Assert(npm.Pack(b.H.BaseFs.SourceFs, b.H.BaseFs.Assets.Dirs), qt.IsNil) b.Assert(npm.Pack(b.H.BaseFs.SourceFs, b.H.BaseFs.Assets.Dirs), qt.IsNil)
@ -333,12 +325,13 @@ func TestHugoModulesMatrix(t *testing.T) {
for _, m := range testmods[:2] { for _, m := range testmods[:2] {
c := qt.New(t) c := qt.New(t)
v := config.New()
workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-modules-test") workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-modules-test")
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
defer clean() defer clean()
v := config.NewWithTestDefaults()
v.Set("workingDir", workingDir)
configTemplate := ` configTemplate := `
baseURL = "https://example.com" baseURL = "https://example.com"
title = "My Modular Site" title = "My Modular Site"
@ -670,13 +663,14 @@ func TestModulesSymlinks(t *testing.T) {
}() }()
c := qt.New(t) c := qt.New(t)
// We need to use the OS fs for this. workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-mod-sym")
cfg := config.New()
fs := hugofs.NewFrom(hugofs.Os, cfg)
workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-mod-sym")
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
// We need to use the OS fs for this.
cfg := config.NewWithTestDefaults()
cfg.Set("workingDir", workingDir)
fs := hugofs.NewFrom(hugofs.Os, cfg)
defer clean() defer clean()
const homeTemplate = ` const homeTemplate = `
@ -694,9 +688,9 @@ Data: {{ .Site.Data }}
} }
// Create project dirs and files. // Create project dirs and files.
createDirsAndFiles(workDir) createDirsAndFiles(workingDir)
// Create one module inside the default themes folder. // Create one module inside the default themes folder.
themeDir := filepath.Join(workDir, "themes", "mymod") themeDir := filepath.Join(workingDir, "themes", "mymod")
createDirsAndFiles(themeDir) createDirsAndFiles(themeDir)
createSymlinks := func(baseDir, id string) { createSymlinks := func(baseDir, id string) {
@ -711,7 +705,7 @@ Data: {{ .Site.Data }}
} }
} }
createSymlinks(workDir, "project") createSymlinks(workingDir, "project")
createSymlinks(themeDir, "mod") createSymlinks(themeDir, "mod")
config := ` config := `
@ -729,12 +723,12 @@ weight = 2
` `
b := newTestSitesBuilder(t).WithNothingAdded().WithWorkingDir(workDir) b := newTestSitesBuilder(t).WithNothingAdded().WithWorkingDir(workingDir)
b.WithLogger(loggers.NewErrorLogger()) b.WithLogger(loggers.NewErrorLogger())
b.Fs = fs b.Fs = fs
b.WithConfigFile("toml", config) b.WithConfigFile("toml", config)
c.Assert(os.Chdir(workDir), qt.IsNil) c.Assert(os.Chdir(workingDir), qt.IsNil)
b.Build(BuildCfg{}) b.Build(BuildCfg{})
@ -846,7 +840,10 @@ workingDir = %q
b := newTestSitesBuilder(t).Running() b := newTestSitesBuilder(t).Running()
b.Fs = hugofs.NewDefault(config.New()) cfg := config.NewWithTestDefaults()
cfg.Set("workingDir", workingDir)
b.Fs = hugofs.NewDefault(cfg)
b.WithWorkingDir(workingDir).WithConfigFile("toml", tomlConfig) b.WithWorkingDir(workingDir).WithConfigFile("toml", tomlConfig)
b.WithTemplatesAdded("index.html", ` b.WithTemplatesAdded("index.html", `
@ -968,7 +965,9 @@ workingDir = %q
b := newTestSitesBuilder(c).Running() b := newTestSitesBuilder(c).Running()
b.Fs = hugofs.NewDefault(config.New()) cfg := config.NewWithTestDefaults()
cfg.Set("workingDir", workingDir)
b.Fs = hugofs.NewDefault(cfg)
os.MkdirAll(filepath.Join(workingDir, "content", "blog"), 0777) os.MkdirAll(filepath.Join(workingDir, "content", "blog"), 0777)
@ -1067,7 +1066,7 @@ func TestSiteWithGoModButNoModules(t *testing.T) {
workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-no-mod") workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-no-mod")
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("workingDir", workDir) cfg.Set("workingDir", workDir)
fs := hugofs.NewFrom(hugofs.Os, cfg) fs := hugofs.NewFrom(hugofs.Os, cfg)
@ -1093,7 +1092,7 @@ func TestModuleAbsMount(t *testing.T) {
absContentDir, clean2, err := htesting.CreateTempDir(hugofs.Os, "hugo-content") absContentDir, clean2, err := htesting.CreateTempDir(hugofs.Os, "hugo-content")
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("workingDir", workDir) cfg.Set("workingDir", workDir)
fs := hugofs.NewFrom(hugofs.Os, cfg) fs := hugofs.NewFrom(hugofs.Os, cfg)

View file

@ -597,7 +597,7 @@ func (h *HugoSites) reset(config *BuildCfg) {
if config.ResetState { if config.ResetState {
for i, s := range h.Sites { for i, s := range h.Sites {
h.Sites[i] = s.reset() h.Sites[i] = s.reset()
if r, ok := s.Fs.Destination.(hugofs.Reseter); ok { if r, ok := s.Fs.PublishDir.(hugofs.Reseter); ok {
r.Reset() r.Reset()
} }
} }

View file

@ -496,9 +496,9 @@ func (h *HugoSites) writeBuildStats() error {
return err return err
} }
// Write to the destination, too, if a mem fs is in play. // Write to the destination as well if it's a in-memory fs.
if h.Fs.Source != hugofs.Os { if !hugofs.IsOsFs(h.Fs.Source) {
if err := afero.WriteFile(h.Fs.Destination, filename, js, 0666); err != nil { if err := afero.WriteFile(h.Fs.WorkingDirWritable, filename, js, 0666); err != nil {
return err return err
} }
} }

View file

@ -489,7 +489,7 @@ func TestMultiSitesRebuild(t *testing.T) {
c.Assert(enSite.RegularPages()[0].Title(), qt.Equals, "new_en_2") c.Assert(enSite.RegularPages()[0].Title(), qt.Equals, "new_en_2")
c.Assert(enSite.RegularPages()[1].Title(), qt.Equals, "new_en_1") c.Assert(enSite.RegularPages()[1].Title(), qt.Equals, "new_en_1")
rendered := readDestination(t, fs, "public/en/new1/index.html") rendered := readWorkingDir(t, fs, "public/en/new1/index.html")
c.Assert(strings.Contains(rendered, "new_en_1"), qt.Equals, true) c.Assert(strings.Contains(rendered, "new_en_1"), qt.Equals, true)
}, },
}, },
@ -503,7 +503,7 @@ func TestMultiSitesRebuild(t *testing.T) {
[]fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc1.en.md"), Op: fsnotify.Write}}, []fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc1.en.md"), Op: fsnotify.Write}},
func(t *testing.T) { func(t *testing.T) {
c.Assert(len(enSite.RegularPages()), qt.Equals, 6) c.Assert(len(enSite.RegularPages()), qt.Equals, 6)
doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") doc1 := readWorkingDir(t, fs, "public/en/sect/doc1-slug/index.html")
c.Assert(strings.Contains(doc1, "CHANGED"), qt.Equals, true) c.Assert(strings.Contains(doc1, "CHANGED"), qt.Equals, true)
}, },
}, },
@ -521,7 +521,7 @@ func TestMultiSitesRebuild(t *testing.T) {
func(t *testing.T) { func(t *testing.T) {
c.Assert(len(enSite.RegularPages()), qt.Equals, 6, qt.Commentf("Rename")) c.Assert(len(enSite.RegularPages()), qt.Equals, 6, qt.Commentf("Rename"))
c.Assert(enSite.RegularPages()[1].Title(), qt.Equals, "new_en_1") c.Assert(enSite.RegularPages()[1].Title(), qt.Equals, "new_en_1")
rendered := readDestination(t, fs, "public/en/new1renamed/index.html") rendered := readWorkingDir(t, fs, "public/en/new1renamed/index.html")
c.Assert(rendered, qt.Contains, "new_en_1") c.Assert(rendered, qt.Contains, "new_en_1")
}, },
}, },
@ -538,7 +538,7 @@ func TestMultiSitesRebuild(t *testing.T) {
c.Assert(len(enSite.RegularPages()), qt.Equals, 6) c.Assert(len(enSite.RegularPages()), qt.Equals, 6)
c.Assert(len(enSite.AllPages()), qt.Equals, 34) c.Assert(len(enSite.AllPages()), qt.Equals, 34)
c.Assert(len(frSite.RegularPages()), qt.Equals, 5) c.Assert(len(frSite.RegularPages()), qt.Equals, 5)
doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") doc1 := readWorkingDir(t, fs, "public/en/sect/doc1-slug/index.html")
c.Assert(strings.Contains(doc1, "Template Changed"), qt.Equals, true) c.Assert(strings.Contains(doc1, "Template Changed"), qt.Equals, true)
}, },
}, },
@ -555,9 +555,9 @@ func TestMultiSitesRebuild(t *testing.T) {
c.Assert(len(enSite.RegularPages()), qt.Equals, 6) c.Assert(len(enSite.RegularPages()), qt.Equals, 6)
c.Assert(len(enSite.AllPages()), qt.Equals, 34) c.Assert(len(enSite.AllPages()), qt.Equals, 34)
c.Assert(len(frSite.RegularPages()), qt.Equals, 5) c.Assert(len(frSite.RegularPages()), qt.Equals, 5)
docEn := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") docEn := readWorkingDir(t, fs, "public/en/sect/doc1-slug/index.html")
c.Assert(strings.Contains(docEn, "Hello"), qt.Equals, true) c.Assert(strings.Contains(docEn, "Hello"), qt.Equals, true)
docFr := readDestination(t, fs, "public/fr/sect/doc1/index.html") docFr := readWorkingDir(t, fs, "public/fr/sect/doc1/index.html")
c.Assert(strings.Contains(docFr, "Salut"), qt.Equals, true) c.Assert(strings.Contains(docFr, "Salut"), qt.Equals, true)
homeEn := enSite.getPage(page.KindHome) homeEn := enSite.getPage(page.KindHome)
@ -700,7 +700,7 @@ END
func checkContent(s *sitesBuilder, filename string, matches ...string) { func checkContent(s *sitesBuilder, filename string, matches ...string) {
s.T.Helper() s.T.Helper()
content := readDestination(s.T, s.Fs, filename) content := readWorkingDir(s.T, s.Fs, filename)
for _, match := range matches { for _, match := range matches {
if !strings.Contains(content, match) { if !strings.Contains(content, match) {
s.Fatalf("No match for\n%q\nin content for %s\n%q\nDiff:\n%s", match, filename, content, htesting.DiffStrings(content, match)) s.Fatalf("No match for\n%q\nin content for %s\n%q\nDiff:\n%s", match, filename, content, htesting.DiffStrings(content, match))
@ -1170,13 +1170,13 @@ func writeToFs(t testing.TB, fs afero.Fs, filename, content string) {
} }
} }
func readDestination(t testing.TB, fs *hugofs.Fs, filename string) string { func readWorkingDir(t testing.TB, fs *hugofs.Fs, filename string) string {
t.Helper() t.Helper()
return readFileFromFs(t, fs.Destination, filename) return readFileFromFs(t, fs.WorkingDirReadOnly, filename)
} }
func destinationExists(fs *hugofs.Fs, filename string) bool { func workingDirExists(fs *hugofs.Fs, filename string) bool {
b, err := helpers.Exists(filename, fs.Destination) b, err := helpers.Exists(filename, fs.WorkingDirReadOnly)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -38,7 +38,7 @@ func TestImageOps(t *testing.T) {
defer clean() defer clean()
newBuilder := func(timeout any) *sitesBuilder { newBuilder := func(timeout any) *sitesBuilder {
v := config.New() v := config.NewWithTestDefaults()
v.Set("workingDir", workDir) v.Set("workingDir", workDir)
v.Set("baseURL", "https://example.org") v.Set("baseURL", "https://example.org")
v.Set("timeout", timeout) v.Set("timeout", timeout)
@ -141,7 +141,7 @@ IMG SHORTCODE: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_129x239_r
assertImages := func() { assertImages := func() {
b.Helper() b.Helper()
b.AssertFileContent(filepath.Join(workDir, "public/index.html"), imgExpect) b.AssertFileContent("public/index.html", imgExpect)
b.AssertImage(350, 219, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_350x0_resize_q75_box.a86fe88d894e5db613f6aa8a80538fefc25b20fa24ba0d782c057adcef616f56.jpg") b.AssertImage(350, 219, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_350x0_resize_q75_box.a86fe88d894e5db613f6aa8a80538fefc25b20fa24ba0d782c057adcef616f56.jpg")
b.AssertImage(129, 239, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_129x239_resize_q75_box.jpg") b.AssertImage(129, 239, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_129x239_resize_q75_box.jpg")
} }

View file

@ -47,6 +47,8 @@ func NewIntegrationTestBuilder(conf IntegrationTestConfig) *IntegrationTestBuild
if doClean { if doClean {
c.Cleanup(clean) c.Cleanup(clean)
} }
} else if conf.WorkingDir == "" {
conf.WorkingDir = helpers.FilePathSeparator
} }
return &IntegrationTestBuilder{ return &IntegrationTestBuilder{
@ -157,7 +159,7 @@ func (s *IntegrationTestBuilder) AssertDestinationExists(filename string, b bool
} }
func (s *IntegrationTestBuilder) destinationExists(filename string) bool { func (s *IntegrationTestBuilder) destinationExists(filename string) bool {
b, err := helpers.Exists(filename, s.fs.Destination) b, err := helpers.Exists(filename, s.fs.PublishDir)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -258,11 +260,7 @@ func (s *IntegrationTestBuilder) RenameFile(old, new string) *IntegrationTestBui
func (s *IntegrationTestBuilder) FileContent(filename string) string { func (s *IntegrationTestBuilder) FileContent(filename string) string {
s.Helper() s.Helper()
filename = filepath.FromSlash(filename) return s.readWorkingDir(s, s.fs, filepath.FromSlash(filename))
if !strings.HasPrefix(filename, s.Cfg.WorkingDir) {
filename = filepath.Join(s.Cfg.WorkingDir, filename)
}
return s.readDestination(s, s.fs, filename)
} }
func (s *IntegrationTestBuilder) initBuilder() { func (s *IntegrationTestBuilder) initBuilder() {
@ -280,8 +278,6 @@ func (s *IntegrationTestBuilder) initBuilder() {
logger := loggers.NewBasicLoggerForWriter(s.Cfg.LogLevel, &s.logBuff) logger := loggers.NewBasicLoggerForWriter(s.Cfg.LogLevel, &s.logBuff)
fs := hugofs.NewFrom(afs, config.New())
for _, f := range s.data.Files { for _, f := range s.data.Files {
filename := filepath.Join(s.Cfg.WorkingDir, f.Name) filename := filepath.Join(s.Cfg.WorkingDir, f.Name)
s.Assert(afs.MkdirAll(filepath.Dir(filename), 0777), qt.IsNil) s.Assert(afs.MkdirAll(filepath.Dir(filename), 0777), qt.IsNil)
@ -301,10 +297,12 @@ func (s *IntegrationTestBuilder) initBuilder() {
}, },
) )
s.Assert(err, qt.IsNil)
cfg.Set("workingDir", s.Cfg.WorkingDir) cfg.Set("workingDir", s.Cfg.WorkingDir)
fs := hugofs.NewFrom(afs, cfg)
s.Assert(err, qt.IsNil)
depsCfg := deps.DepsCfg{Cfg: cfg, Fs: fs, Running: s.Cfg.Running, Logger: logger} depsCfg := deps.DepsCfg{Cfg: cfg, Fs: fs, Running: s.Cfg.Running, Logger: logger}
sites, err := NewHugoSites(depsCfg) sites, err := NewHugoSites(depsCfg)
s.Assert(err, qt.IsNil) s.Assert(err, qt.IsNil)
@ -400,9 +398,9 @@ func (s *IntegrationTestBuilder) changeEvents() []fsnotify.Event {
return events return events
} }
func (s *IntegrationTestBuilder) readDestination(t testing.TB, fs *hugofs.Fs, filename string) string { func (s *IntegrationTestBuilder) readWorkingDir(t testing.TB, fs *hugofs.Fs, filename string) string {
t.Helper() t.Helper()
return s.readFileFromFs(t, fs.Destination, filename) return s.readFileFromFs(t, fs.WorkingDirReadOnly, filename)
} }
func (s *IntegrationTestBuilder) readFileFromFs(t testing.TB, fs afero.Fs, filename string) string { func (s *IntegrationTestBuilder) readFileFromFs(t testing.TB, fs afero.Fs, filename string) string {

View file

@ -224,8 +224,8 @@ Content.
nnSite := b.H.Sites[1] nnSite := b.H.Sites[1]
svSite := b.H.Sites[2] svSite := b.H.Sites[2]
b.AssertFileContent("/my/project/public/en/mystatic/file1.yaml", "en") b.AssertFileContent("public/en/mystatic/file1.yaml", "en")
b.AssertFileContent("/my/project/public/nn/mystatic/file1.yaml", "nn") b.AssertFileContent("public/nn/mystatic/file1.yaml", "nn")
// dumpPages(nnSite.RegularPages()...) // dumpPages(nnSite.RegularPages()...)
@ -300,16 +300,16 @@ Content.
c.Assert(len(bundleSv.Resources()), qt.Equals, 4) c.Assert(len(bundleSv.Resources()), qt.Equals, 4)
c.Assert(len(bundleEn.Resources()), qt.Equals, 4) c.Assert(len(bundleEn.Resources()), qt.Equals, 4)
b.AssertFileContent("/my/project/public/en/sect/mybundle/index.html", "image/png: /en/sect/mybundle/logo.png") b.AssertFileContent("public/en/sect/mybundle/index.html", "image/png: /en/sect/mybundle/logo.png")
b.AssertFileContent("/my/project/public/nn/sect/mybundle/index.html", "image/png: /nn/sect/mybundle/logo.png") b.AssertFileContent("public/nn/sect/mybundle/index.html", "image/png: /nn/sect/mybundle/logo.png")
b.AssertFileContent("/my/project/public/sv/sect/mybundle/index.html", "image/png: /sv/sect/mybundle/logo.png") b.AssertFileContent("public/sv/sect/mybundle/index.html", "image/png: /sv/sect/mybundle/logo.png")
b.AssertFileContent("/my/project/public/sv/sect/mybundle/featured.png", "PNG Data for sv") b.AssertFileContent("public/sv/sect/mybundle/featured.png", "PNG Data for sv")
b.AssertFileContent("/my/project/public/nn/sect/mybundle/featured.png", "PNG Data for nn") b.AssertFileContent("public/nn/sect/mybundle/featured.png", "PNG Data for nn")
b.AssertFileContent("/my/project/public/en/sect/mybundle/featured.png", "PNG Data for en") b.AssertFileContent("public/en/sect/mybundle/featured.png", "PNG Data for en")
b.AssertFileContent("/my/project/public/en/sect/mybundle/logo.png", "PNG Data") b.AssertFileContent("public/en/sect/mybundle/logo.png", "PNG Data")
b.AssertFileContent("/my/project/public/sv/sect/mybundle/logo.png", "PNG Data") b.AssertFileContent("public/sv/sect/mybundle/logo.png", "PNG Data")
b.AssertFileContent("/my/project/public/nn/sect/mybundle/logo.png", "PNG Data") b.AssertFileContent("public/nn/sect/mybundle/logo.png", "PNG Data")
nnSect := nnSite.getPage(page.KindSection, "sect") nnSect := nnSite.getPage(page.KindSection, "sect")
c.Assert(nnSect, qt.Not(qt.IsNil)) c.Assert(nnSect, qt.Not(qt.IsNil))

View file

@ -22,7 +22,7 @@ import (
func TestMinifyPublisher(t *testing.T) { func TestMinifyPublisher(t *testing.T) {
t.Parallel() t.Parallel()
v := config.New() v := config.NewWithTestDefaults()
v.Set("minify", true) v.Set("minify", true)
v.Set("baseURL", "https://example.org/") v.Set("baseURL", "https://example.org/")

View file

@ -101,13 +101,13 @@ Resources: {{ resources.Match "**.js" }}
assertExists := func(name string, shouldExist bool) { assertExists := func(name string, shouldExist bool) {
b.Helper() b.Helper()
b.Assert(b.CheckExists(filepath.Join(workingDir, name)), qt.Equals, shouldExist) b.Assert(b.CheckExists(name), qt.Equals, shouldExist)
} }
assertExists("public/a/b/p1/index.html", true) assertExists("public/a/b/p1/index.html", true)
assertExists("public/a/c/p2/index.html", false) assertExists("public/a/c/p2/index.html", false)
b.AssertFileContent(filepath.Join(workingDir, "public", "index.html"), ` b.AssertFileContent(filepath.Join("public", "index.html"), `
Data: map[mydata:map[b:map[b1:bval]]]:END Data: map[mydata:map[b:map[b1:bval]]]:END
Template: false Template: false
Resource1: js/include.js:END Resource1: js/include.js:END

View file

@ -23,7 +23,6 @@ import (
"time" "time"
"github.com/gohugoio/hugo/htesting" "github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/markup/asciidocext" "github.com/gohugoio/hugo/markup/asciidocext"
"github.com/gohugoio/hugo/markup/rst" "github.com/gohugoio/hugo/markup/rst"
@ -35,7 +34,6 @@ import (
"github.com/gohugoio/hugo/resources/page" "github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource" "github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/afero"
"github.com/spf13/jwalterweatherman" "github.com/spf13/jwalterweatherman"
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
@ -1031,14 +1029,14 @@ func TestPageWithLastmodFromGitInfo(t *testing.T) {
} }
c := qt.New(t) c := qt.New(t)
// We need to use the OS fs for this.
cfg := config.New()
fs := hugofs.NewFrom(hugofs.Os, cfg)
fs.Destination = &afero.MemMapFs{}
wd, err := os.Getwd() wd, err := os.Getwd()
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
// We need to use the OS fs for this.
cfg := config.NewWithTestDefaults()
cfg.Set("workingDir", filepath.Join(wd, "testsite"))
fs := hugofs.NewFrom(hugofs.Os, cfg)
cfg.Set("frontmatter", map[string]any{ cfg.Set("frontmatter", map[string]any{
"lastmod": []string{":git", "lastmod"}, "lastmod": []string{":git", "lastmod"},
}) })
@ -1060,8 +1058,6 @@ func TestPageWithLastmodFromGitInfo(t *testing.T) {
cfg.Set("languages", langConfig) cfg.Set("languages", langConfig)
cfg.Set("enableGitInfo", true) cfg.Set("enableGitInfo", true)
cfg.Set("workingDir", filepath.Join(wd, "testsite"))
b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded() b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded()
b.Build(BuildCfg{SkipRender: true}) b.Build(BuildCfg{SkipRender: true})
@ -1314,7 +1310,7 @@ func TestChompBOM(t *testing.T) {
func TestPageWithEmoji(t *testing.T) { func TestPageWithEmoji(t *testing.T) {
for _, enableEmoji := range []bool{true, false} { for _, enableEmoji := range []bool{true, false} {
v := config.New() v := config.NewWithTestDefaults()
v.Set("enableEmoji", enableEmoji) v.Set("enableEmoji", enableEmoji)
b := newTestSitesBuilder(t).WithViper(v) b := newTestSitesBuilder(t).WithViper(v)

View file

@ -127,22 +127,22 @@ func TestPageBundlerSiteRegular(t *testing.T) {
// Check both output formats // Check both output formats
rel, filename := relFilename("/a/1/", "index.html") rel, filename := relFilename("/a/1/", "index.html")
b.AssertFileContent(filepath.Join("/work/public", filename), b.AssertFileContent(filepath.Join("public", filename),
"TheContent", "TheContent",
"Single RelPermalink: "+rel, "Single RelPermalink: "+rel,
) )
rel, filename = relFilename("/cpath/a/1/", "cindex.html") rel, filename = relFilename("/cpath/a/1/", "cindex.html")
b.AssertFileContent(filepath.Join("/work/public", filename), b.AssertFileContent(filepath.Join("public", filename),
"TheContent", "TheContent",
"Single RelPermalink: "+rel, "Single RelPermalink: "+rel,
) )
b.AssertFileContent(filepath.FromSlash("/work/public/images/hugo-logo.png"), "content") b.AssertFileContent(filepath.FromSlash("public/images/hugo-logo.png"), "content")
// This should be just copied to destination. // This should be just copied to destination.
b.AssertFileContent(filepath.FromSlash("/work/public/assets/pic1.png"), "content") b.AssertFileContent(filepath.FromSlash("public/assets/pic1.png"), "content")
leafBundle1 := s.getPage(page.KindPage, "b/my-bundle/index.md") leafBundle1 := s.getPage(page.KindPage, "b/my-bundle/index.md")
c.Assert(leafBundle1, qt.Not(qt.IsNil)) c.Assert(leafBundle1, qt.Not(qt.IsNil))
@ -159,8 +159,8 @@ func TestPageBundlerSiteRegular(t *testing.T) {
c.Assert(rootBundle, qt.Not(qt.IsNil)) c.Assert(rootBundle, qt.Not(qt.IsNil))
c.Assert(rootBundle.Parent().IsHome(), qt.Equals, true) c.Assert(rootBundle.Parent().IsHome(), qt.Equals, true)
if !ugly { if !ugly {
b.AssertFileContent(filepath.FromSlash("/work/public/root/index.html"), "Single RelPermalink: "+relURLBase+"/root/") b.AssertFileContent(filepath.FromSlash("public/root/index.html"), "Single RelPermalink: "+relURLBase+"/root/")
b.AssertFileContent(filepath.FromSlash("/work/public/cpath/root/cindex.html"), "Single RelPermalink: "+relURLBase+"/cpath/root/") b.AssertFileContent(filepath.FromSlash("public/cpath/root/cindex.html"), "Single RelPermalink: "+relURLBase+"/cpath/root/")
} }
leafBundle2 := s.getPage(page.KindPage, "a/b/index.md") leafBundle2 := s.getPage(page.KindPage, "a/b/index.md")
@ -202,17 +202,17 @@ func TestPageBundlerSiteRegular(t *testing.T) {
} }
if ugly { if ugly {
b.AssertFileContent("/work/public/2017/pageslug.html", b.AssertFileContent("public/2017/pageslug.html",
relPermalinker("Single RelPermalink: %s/2017/pageslug.html"), relPermalinker("Single RelPermalink: %s/2017/pageslug.html"),
permalinker("Single Permalink: %s/2017/pageslug.html"), permalinker("Single Permalink: %s/2017/pageslug.html"),
relPermalinker("Sunset RelPermalink: %s/2017/pageslug/sunset1.jpg"), relPermalinker("Sunset RelPermalink: %s/2017/pageslug/sunset1.jpg"),
permalinker("Sunset Permalink: %s/2017/pageslug/sunset1.jpg")) permalinker("Sunset Permalink: %s/2017/pageslug/sunset1.jpg"))
} else { } else {
b.AssertFileContent("/work/public/2017/pageslug/index.html", b.AssertFileContent("public/2017/pageslug/index.html",
relPermalinker("Sunset RelPermalink: %s/2017/pageslug/sunset1.jpg"), relPermalinker("Sunset RelPermalink: %s/2017/pageslug/sunset1.jpg"),
permalinker("Sunset Permalink: %s/2017/pageslug/sunset1.jpg")) permalinker("Sunset Permalink: %s/2017/pageslug/sunset1.jpg"))
b.AssertFileContent("/work/public/cpath/2017/pageslug/cindex.html", b.AssertFileContent("public/cpath/2017/pageslug/cindex.html",
relPermalinker("Single RelPermalink: %s/cpath/2017/pageslug/"), relPermalinker("Single RelPermalink: %s/cpath/2017/pageslug/"),
relPermalinker("Short Sunset RelPermalink: %s/cpath/2017/pageslug/sunset2.jpg"), relPermalinker("Short Sunset RelPermalink: %s/cpath/2017/pageslug/sunset2.jpg"),
relPermalinker("Sunset RelPermalink: %s/cpath/2017/pageslug/sunset1.jpg"), relPermalinker("Sunset RelPermalink: %s/cpath/2017/pageslug/sunset1.jpg"),
@ -220,15 +220,15 @@ func TestPageBundlerSiteRegular(t *testing.T) {
) )
} }
b.AssertFileContent(filepath.FromSlash("/work/public/2017/pageslug/c/logo.png"), "content") b.AssertFileContent(filepath.FromSlash("public/2017/pageslug/c/logo.png"), "content")
b.AssertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug/c/logo.png"), "content") b.AssertFileContent(filepath.FromSlash("public/cpath/2017/pageslug/c/logo.png"), "content")
c.Assert(b.CheckExists("/work/public/cpath/cpath/2017/pageslug/c/logo.png"), qt.Equals, false) c.Assert(b.CheckExists("public/cpath/cpath/2017/pageslug/c/logo.png"), qt.Equals, false)
// Custom media type defined in site config. // Custom media type defined in site config.
c.Assert(len(leafBundle1.Resources().ByType("bepsays")), qt.Equals, 1) c.Assert(len(leafBundle1.Resources().ByType("bepsays")), qt.Equals, 1)
if ugly { if ugly {
b.AssertFileContent(filepath.FromSlash("/work/public/2017/pageslug.html"), b.AssertFileContent(filepath.FromSlash("public/2017/pageslug.html"),
"TheContent", "TheContent",
relPermalinker("Sunset RelPermalink: %s/2017/pageslug/sunset1.jpg"), relPermalinker("Sunset RelPermalink: %s/2017/pageslug/sunset1.jpg"),
permalinker("Sunset Permalink: %s/2017/pageslug/sunset1.jpg"), permalinker("Sunset Permalink: %s/2017/pageslug/sunset1.jpg"),
@ -247,18 +247,18 @@ func TestPageBundlerSiteRegular(t *testing.T) {
// https://github.com/gohugoio/hugo/issues/5882 // https://github.com/gohugoio/hugo/issues/5882
b.AssertFileContent( b.AssertFileContent(
filepath.FromSlash("/work/public/2017/pageslug.html"), "0: Page RelPermalink: |") filepath.FromSlash("public/2017/pageslug.html"), "0: Page RelPermalink: |")
b.AssertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug.html"), "TheContent") b.AssertFileContent(filepath.FromSlash("public/cpath/2017/pageslug.html"), "TheContent")
// 은행 // 은행
b.AssertFileContent(filepath.FromSlash("/work/public/c/은행/logo-은행.png"), "은행 PNG") b.AssertFileContent(filepath.FromSlash("public/c/은행/logo-은행.png"), "은행 PNG")
} else { } else {
b.AssertFileContent(filepath.FromSlash("/work/public/2017/pageslug/index.html"), "TheContent") b.AssertFileContent(filepath.FromSlash("public/2017/pageslug/index.html"), "TheContent")
b.AssertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug/cindex.html"), "TheContent") b.AssertFileContent(filepath.FromSlash("public/cpath/2017/pageslug/cindex.html"), "TheContent")
b.AssertFileContent(filepath.FromSlash("/work/public/2017/pageslug/index.html"), "Single Title") b.AssertFileContent(filepath.FromSlash("public/2017/pageslug/index.html"), "Single Title")
b.AssertFileContent(filepath.FromSlash("/work/public/root/index.html"), "Single Title") b.AssertFileContent(filepath.FromSlash("public/root/index.html"), "Single Title")
} }
}) })
@ -397,23 +397,24 @@ func TestPageBundlerSiteWitSymbolicLinksInContent(t *testing.T) {
}() }()
c := qt.New(t) c := qt.New(t)
// We need to use the OS fs for this.
cfg := config.New()
fs := hugofs.NewFrom(hugofs.Os, cfg)
workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugosym") // We need to use the OS fs for this.
workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugosym")
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
cfg := config.NewWithTestDefaults()
cfg.Set("workingDir", workingDir)
fs := hugofs.NewFrom(hugofs.Os, cfg)
contentDirName := "content" contentDirName := "content"
contentDir := filepath.Join(workDir, contentDirName) contentDir := filepath.Join(workingDir, contentDirName)
c.Assert(os.MkdirAll(filepath.Join(contentDir, "a"), 0777), qt.IsNil) c.Assert(os.MkdirAll(filepath.Join(contentDir, "a"), 0777), qt.IsNil)
for i := 1; i <= 3; i++ { for i := 1; i <= 3; i++ {
c.Assert(os.MkdirAll(filepath.Join(workDir, fmt.Sprintf("symcontent%d", i)), 0777), qt.IsNil) c.Assert(os.MkdirAll(filepath.Join(workingDir, fmt.Sprintf("symcontent%d", i)), 0777), qt.IsNil)
} }
c.Assert(os.MkdirAll(filepath.Join(workDir, "symcontent2", "a1"), 0777), qt.IsNil) c.Assert(os.MkdirAll(filepath.Join(workingDir, "symcontent2", "a1"), 0777), qt.IsNil)
// Symlinked sections inside content. // Symlinked sections inside content.
os.Chdir(contentDir) os.Chdir(contentDir)
@ -431,11 +432,11 @@ func TestPageBundlerSiteWitSymbolicLinksInContent(t *testing.T) {
// Create a circular symlink. Will print some warnings. // Create a circular symlink. Will print some warnings.
c.Assert(os.Symlink(filepath.Join("..", contentDirName), filepath.FromSlash("circus")), qt.IsNil) c.Assert(os.Symlink(filepath.Join("..", contentDirName), filepath.FromSlash("circus")), qt.IsNil)
c.Assert(os.Chdir(workDir), qt.IsNil) c.Assert(os.Chdir(workingDir), qt.IsNil)
defer clean() defer clean()
cfg.Set("workingDir", workDir) cfg.Set("workingDir", workingDir)
cfg.Set("contentDir", contentDirName) cfg.Set("contentDir", contentDirName)
cfg.Set("baseURL", "https://example.com") cfg.Set("baseURL", "https://example.com")
@ -488,9 +489,9 @@ TheContent.
c.Assert(len(a1Bundle.Resources()), qt.Equals, 2) c.Assert(len(a1Bundle.Resources()), qt.Equals, 2)
c.Assert(len(a1Bundle.Resources().ByType(pageResourceType)), qt.Equals, 1) c.Assert(len(a1Bundle.Resources().ByType(pageResourceType)), qt.Equals, 1)
b.AssertFileContent(filepath.FromSlash(workDir+"/public/a/page/index.html"), "TheContent") b.AssertFileContent(filepath.FromSlash("public/a/page/index.html"), "TheContent")
b.AssertFileContent(filepath.FromSlash(workDir+"/public/symbolic1/s1/index.html"), "TheContent") b.AssertFileContent(filepath.FromSlash("public/symbolic1/s1/index.html"), "TheContent")
b.AssertFileContent(filepath.FromSlash(workDir+"/public/symbolic2/a1/index.html"), "TheContent") b.AssertFileContent(filepath.FromSlash("public/symbolic2/a1/index.html"), "TheContent")
} }
func TestPageBundlerHeadless(t *testing.T) { func TestPageBundlerHeadless(t *testing.T) {
@ -563,12 +564,12 @@ HEADLESS {{< myShort >}}
th := newTestHelper(s.Cfg, s.Fs, t) th := newTestHelper(s.Cfg, s.Fs, t)
th.assertFileContent(filepath.FromSlash(workDir+"/public/s1/index.html"), "TheContent") th.assertFileContent(filepath.FromSlash("public/s1/index.html"), "TheContent")
th.assertFileContent(filepath.FromSlash(workDir+"/public/s1/l1.png"), "PNG") th.assertFileContent(filepath.FromSlash("public/s1/l1.png"), "PNG")
th.assertFileNotExist(workDir + "/public/s2/index.html") th.assertFileNotExist("public/s2/index.html")
// But the bundled resources needs to be published // But the bundled resources needs to be published
th.assertFileContent(filepath.FromSlash(workDir+"/public/s2/l1.png"), "PNG") th.assertFileContent(filepath.FromSlash("public/s2/l1.png"), "PNG")
// No headless bundles here, please. // No headless bundles here, please.
// https://github.com/gohugoio/hugo/issues/6492 // https://github.com/gohugoio/hugo/issues/6492
@ -1321,7 +1322,7 @@ func TestPageBundlerHome(t *testing.T) {
workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-bundler-home") workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-bundler-home")
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("workingDir", workDir) cfg.Set("workingDir", workDir)
fs := hugofs.NewFrom(hugofs.Os, cfg) fs := hugofs.NewFrom(hugofs.Os, cfg)

View file

@ -18,6 +18,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
hpaths "github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/modules" "github.com/gohugoio/hugo/modules"
@ -51,6 +53,7 @@ type Paths struct {
// pagination path handling // pagination path handling
PaginatePath string PaginatePath string
// TODO1 check usage
PublishDir string PublishDir string
// When in multihost mode, this returns a list of base paths below PublishDir // When in multihost mode, this returns a list of base paths below PublishDir
@ -123,7 +126,7 @@ func New(fs *hugofs.Fs, cfg config.Provider) (*Paths, error) {
languages = langs.Languages{&langs.Language{Lang: "en", Cfg: cfg, ContentDir: contentDir}} languages = langs.Languages{&langs.Language{Lang: "en", Cfg: cfg, ContentDir: contentDir}}
} }
absPublishDir := AbsPathify(workingDir, publishDir) absPublishDir := hpaths.AbsPathify(workingDir, publishDir)
if !strings.HasSuffix(absPublishDir, FilePathSeparator) { if !strings.HasSuffix(absPublishDir, FilePathSeparator) {
absPublishDir += FilePathSeparator absPublishDir += FilePathSeparator
} }
@ -131,7 +134,7 @@ func New(fs *hugofs.Fs, cfg config.Provider) (*Paths, error) {
if absPublishDir == "//" { if absPublishDir == "//" {
absPublishDir = FilePathSeparator absPublishDir = FilePathSeparator
} }
absResourcesDir := AbsPathify(workingDir, resourceDir) absResourcesDir := hpaths.AbsPathify(workingDir, resourceDir)
if !strings.HasSuffix(absResourcesDir, FilePathSeparator) { if !strings.HasSuffix(absResourcesDir, FilePathSeparator) {
absResourcesDir += FilePathSeparator absResourcesDir += FilePathSeparator
} }
@ -254,7 +257,7 @@ func (p *Paths) GetLangSubDir(lang string) string {
// AbsPathify creates an absolute path if given a relative path. If already // AbsPathify creates an absolute path if given a relative path. If already
// absolute, the path is just cleaned. // absolute, the path is just cleaned.
func (p *Paths) AbsPathify(inPath string) string { func (p *Paths) AbsPathify(inPath string) string {
return AbsPathify(p.WorkingDir, inPath) return hpaths.AbsPathify(p.WorkingDir, inPath)
} }
// RelPathify trims any WorkingDir prefix from the given filename. If // RelPathify trims any WorkingDir prefix from the given filename. If
@ -267,12 +270,3 @@ func (p *Paths) RelPathify(filename string) string {
return strings.TrimPrefix(strings.TrimPrefix(filename, p.WorkingDir), FilePathSeparator) return strings.TrimPrefix(strings.TrimPrefix(filename, p.WorkingDir), FilePathSeparator)
} }
// AbsPathify creates an absolute path if given a working dir and a relative path.
// If already absolute, the path is just cleaned.
func AbsPathify(workingDir, inPath string) string {
if filepath.IsAbs(inPath) {
return filepath.Clean(inPath)
}
return filepath.Join(workingDir, inPath)
}

View file

@ -25,7 +25,7 @@ import (
func TestNewPaths(t *testing.T) { func TestNewPaths(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
fs := hugofs.NewMem(v) fs := hugofs.NewMem(v)
v.Set("languages", map[string]any{ v.Set("languages", map[string]any{

View file

@ -137,7 +137,7 @@ Edited content.
`) `)
b.Assert(b.Fs.Destination.Remove("public"), qt.IsNil) b.Assert(b.Fs.WorkingDirWritable.Remove("public"), qt.IsNil)
b.H.ResourceSpec.ClearCaches() b.H.ResourceSpec.ClearCaches()
} }

View file

@ -28,7 +28,7 @@ const robotTxtTemplate = `User-agent: Googlebot
func TestRobotsTXTOutput(t *testing.T) { func TestRobotsTXTOutput(t *testing.T) {
t.Parallel() t.Parallel()
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("baseURL", "http://auth/bub/") cfg.Set("baseURL", "http://auth/bub/")
cfg.Set("enableRobotsTXT", true) cfg.Set("enableRobotsTXT", true)

View file

@ -50,7 +50,7 @@ func TestRSSOutput(t *testing.T) {
th.assertFileContent(filepath.Join("public", "categories", "hugo", rssURI), "<?xml", "rss version", "hugo on RSSTest") th.assertFileContent(filepath.Join("public", "categories", "hugo", rssURI), "<?xml", "rss version", "hugo on RSSTest")
// RSS Item Limit // RSS Item Limit
content := readDestination(t, fs, filepath.Join("public", rssURI)) content := readWorkingDir(t, fs, filepath.Join("public", rssURI))
c := strings.Count(content, "<item>") c := strings.Count(content, "<item>")
if c != rssLimit { if c != rssLimit {
t.Errorf("incorrect RSS item count: expected %d, got %d", rssLimit, c) t.Errorf("incorrect RSS item count: expected %d, got %d", rssLimit, c)

View file

@ -1212,7 +1212,7 @@ title: "Hugo Rocks!"
func TestShortcodeEmoji(t *testing.T) { func TestShortcodeEmoji(t *testing.T) {
t.Parallel() t.Parallel()
v := config.New() v := config.NewWithTestDefaults()
v.Set("enableEmoji", true) v.Set("enableEmoji", true)
builder := newTestSitesBuilder(t).WithViper(v) builder := newTestSitesBuilder(t).WithViper(v)
@ -1277,7 +1277,7 @@ func TestShortcodeRef(t *testing.T) {
t.Run(fmt.Sprintf("plainIDAnchors=%t", plainIDAnchors), func(t *testing.T) { t.Run(fmt.Sprintf("plainIDAnchors=%t", plainIDAnchors), func(t *testing.T) {
t.Parallel() t.Parallel()
v := config.New() v := config.NewWithTestDefaults()
v.Set("baseURL", "https://example.org") v.Set("baseURL", "https://example.org")
v.Set("blackfriday", map[string]any{ v.Set("blackfriday", map[string]any{
"plainIDAnchors": plainIDAnchors, "plainIDAnchors": plainIDAnchors,

View file

@ -363,7 +363,7 @@ func TestCreateSiteOutputFormats(t *testing.T) {
page.KindSection: []string{"JSON"}, page.KindSection: []string{"JSON"},
} }
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("outputs", outputsConfig) cfg.Set("outputs", outputsConfig)
outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false) outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
@ -388,7 +388,7 @@ func TestCreateSiteOutputFormats(t *testing.T) {
// Issue #4528 // Issue #4528
t.Run("Mixed case", func(t *testing.T) { t.Run("Mixed case", func(t *testing.T) {
c := qt.New(t) c := qt.New(t)
cfg := config.New() cfg := config.NewWithTestDefaults()
outputsConfig := map[string]any{ outputsConfig := map[string]any{
// Note that we in Hugo 0.53.0 renamed this Kind to "taxonomy", // Note that we in Hugo 0.53.0 renamed this Kind to "taxonomy",
@ -410,7 +410,7 @@ func TestCreateSiteOutputFormatsInvalidConfig(t *testing.T) {
page.KindHome: []string{"FOO", "JSON"}, page.KindHome: []string{"FOO", "JSON"},
} }
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("outputs", outputsConfig) cfg.Set("outputs", outputsConfig)
_, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false) _, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
@ -424,7 +424,7 @@ func TestCreateSiteOutputFormatsEmptyConfig(t *testing.T) {
page.KindHome: []string{}, page.KindHome: []string{},
} }
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("outputs", outputsConfig) cfg.Set("outputs", outputsConfig)
outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false) outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
@ -439,7 +439,7 @@ func TestCreateSiteOutputFormatsCustomFormats(t *testing.T) {
page.KindHome: []string{}, page.KindHome: []string{},
} }
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("outputs", outputsConfig) cfg.Set("outputs", outputsConfig)
var ( var (

View file

@ -336,7 +336,7 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
} }
for _, test := range tests { for _, test := range tests {
content := readDestination(t, fs, test.doc) content := readWorkingDir(t, fs, test.doc)
if content != test.expected { if content != test.expected {
t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content) t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content)
@ -362,7 +362,7 @@ func TestMainSections(t *testing.T) {
c := qt.New(t) c := qt.New(t)
for _, paramSet := range []bool{false, true} { for _, paramSet := range []bool{false, true} {
c.Run(fmt.Sprintf("param-%t", paramSet), func(c *qt.C) { c.Run(fmt.Sprintf("param-%t", paramSet), func(c *qt.C) {
v := config.New() v := config.NewWithTestDefaults()
if paramSet { if paramSet {
v.Set("params", map[string]any{ v.Set("params", map[string]any{
"mainSections": []string{"a1", "a2"}, "mainSections": []string{"a1", "a2"},

View file

@ -76,7 +76,7 @@ func TestPageCount(t *testing.T) {
writeSourcesToSource(t, "", fs, urlFakeSource...) writeSourcesToSource(t, "", fs, urlFakeSource...)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
_, err := s.Fs.Destination.Open("public/blue") _, err := s.Fs.WorkingDirReadOnly.Open("public/blue")
if err != nil { if err != nil {
t.Errorf("No indexed rendered.") t.Errorf("No indexed rendered.")
} }
@ -87,7 +87,7 @@ func TestPageCount(t *testing.T) {
"public/sd3/index.html", "public/sd3/index.html",
"public/sd4.html", "public/sd4.html",
} { } {
if _, err := s.Fs.Destination.Open(filepath.FromSlash(pth)); err != nil { if _, err := s.Fs.WorkingDirReadOnly.Open(filepath.FromSlash(pth)); err != nil {
t.Errorf("No alias rendered: %s", pth) t.Errorf("No alias rendered: %s", pth)
} }
} }

View file

@ -80,7 +80,7 @@ func doTestSitemapOutput(t *testing.T, internal bool) {
"<loc>http://auth/bub/categories/hugo/</loc>", "<loc>http://auth/bub/categories/hugo/</loc>",
) )
content := readDestination(th, th.Fs, outputSitemap) content := readWorkingDir(th, th.Fs, outputSitemap)
c.Assert(content, qt.Not(qt.Contains), "404") c.Assert(content, qt.Not(qt.Contains), "404")
c.Assert(content, qt.Not(qt.Contains), "<loc></loc>") c.Assert(content, qt.Not(qt.Contains), "<loc></loc>")
} }

View file

@ -114,7 +114,7 @@ type filenameContent struct {
} }
func newTestSitesBuilder(t testing.TB) *sitesBuilder { func newTestSitesBuilder(t testing.TB) *sitesBuilder {
v := config.New() v := config.NewWithTestDefaults()
fs := hugofs.NewMem(v) fs := hugofs.NewMem(v)
litterOptions := litter.Options{ litterOptions := litter.Options{
@ -475,6 +475,9 @@ func (s *sitesBuilder) CreateSites() *sitesBuilder {
s.Fatalf("Failed to create sites: %s", err) s.Fatalf("Failed to create sites: %s", err)
} }
s.Assert(s.Fs.PublishDir, qt.IsNotNil)
s.Assert(s.Fs.WorkingDirReadOnly, qt.IsNotNil)
return s return s
} }
@ -536,7 +539,7 @@ func (s *sitesBuilder) CreateSitesE() error {
return errors.Wrap(err, "failed to load config") return errors.Wrap(err, "failed to load config")
} }
s.Fs.Destination = hugofs.NewCreateCountingFs(s.Fs.Destination) s.Fs.PublishDir = hugofs.NewCreateCountingFs(s.Fs.PublishDir)
depsCfg := s.depsCfg depsCfg := s.depsCfg
depsCfg.Fs = s.Fs depsCfg.Fs = s.Fs
@ -759,8 +762,7 @@ func (s *sitesBuilder) AssertFileDoesNotExist(filename string) {
} }
func (s *sitesBuilder) AssertImage(width, height int, filename string) { func (s *sitesBuilder) AssertImage(width, height int, filename string) {
filename = filepath.Join(s.workingDir, filename) f, err := s.Fs.WorkingDirReadOnly.Open(filename)
f, err := s.Fs.Destination.Open(filename)
s.Assert(err, qt.IsNil) s.Assert(err, qt.IsNil)
defer f.Close() defer f.Close()
cfg, err := jpeg.DecodeConfig(f) cfg, err := jpeg.DecodeConfig(f)
@ -771,17 +773,14 @@ func (s *sitesBuilder) AssertImage(width, height int, filename string) {
func (s *sitesBuilder) AssertNoDuplicateWrites() { func (s *sitesBuilder) AssertNoDuplicateWrites() {
s.Helper() s.Helper()
d := s.Fs.Destination.(hugofs.DuplicatesReporter) d := s.Fs.PublishDir.(hugofs.DuplicatesReporter)
s.Assert(d.ReportDuplicates(), qt.Equals, "") s.Assert(d.ReportDuplicates(), qt.Equals, "")
} }
func (s *sitesBuilder) FileContent(filename string) string { func (s *sitesBuilder) FileContent(filename string) string {
s.T.Helper() s.Helper()
filename = filepath.FromSlash(filename) filename = filepath.FromSlash(filename)
if !strings.HasPrefix(filename, s.workingDir) { return readWorkingDir(s.T, s.Fs, filename)
filename = filepath.Join(s.workingDir, filename)
}
return readDestination(s.T, s.Fs, filename)
} }
func (s *sitesBuilder) AssertObject(expected string, object any) { func (s *sitesBuilder) AssertObject(expected string, object any) {
@ -797,7 +796,7 @@ func (s *sitesBuilder) AssertObject(expected string, object any) {
} }
func (s *sitesBuilder) AssertFileContentRe(filename string, matches ...string) { func (s *sitesBuilder) AssertFileContentRe(filename string, matches ...string) {
content := readDestination(s.T, s.Fs, filename) content := readWorkingDir(s.T, s.Fs, filename)
for _, match := range matches { for _, match := range matches {
r := regexp.MustCompile("(?s)" + match) r := regexp.MustCompile("(?s)" + match)
if !r.MatchString(content) { if !r.MatchString(content) {
@ -807,7 +806,7 @@ func (s *sitesBuilder) AssertFileContentRe(filename string, matches ...string) {
} }
func (s *sitesBuilder) CheckExists(filename string) bool { func (s *sitesBuilder) CheckExists(filename string) bool {
return destinationExists(s.Fs, filepath.Clean(filename)) return workingDirExists(s.Fs, filepath.Clean(filename))
} }
func (s *sitesBuilder) GetPage(ref string) page.Page { func (s *sitesBuilder) GetPage(ref string) page.Page {
@ -848,7 +847,7 @@ type testHelper struct {
func (th testHelper) assertFileContent(filename string, matches ...string) { func (th testHelper) assertFileContent(filename string, matches ...string) {
th.Helper() th.Helper()
filename = th.replaceDefaultContentLanguageValue(filename) filename = th.replaceDefaultContentLanguageValue(filename)
content := readDestination(th, th.Fs, filename) content := readWorkingDir(th, th.Fs, filename)
for _, match := range matches { for _, match := range matches {
match = th.replaceDefaultContentLanguageValue(match) match = th.replaceDefaultContentLanguageValue(match)
th.Assert(strings.Contains(content, match), qt.Equals, true, qt.Commentf(match+" not in: \n"+content)) th.Assert(strings.Contains(content, match), qt.Equals, true, qt.Commentf(match+" not in: \n"+content))
@ -857,7 +856,7 @@ func (th testHelper) assertFileContent(filename string, matches ...string) {
func (th testHelper) assertFileContentRegexp(filename string, matches ...string) { func (th testHelper) assertFileContentRegexp(filename string, matches ...string) {
filename = th.replaceDefaultContentLanguageValue(filename) filename = th.replaceDefaultContentLanguageValue(filename)
content := readDestination(th, th.Fs, filename) content := readWorkingDir(th, th.Fs, filename)
for _, match := range matches { for _, match := range matches {
match = th.replaceDefaultContentLanguageValue(match) match = th.replaceDefaultContentLanguageValue(match)
r := regexp.MustCompile(match) r := regexp.MustCompile(match)
@ -870,7 +869,7 @@ func (th testHelper) assertFileContentRegexp(filename string, matches ...string)
} }
func (th testHelper) assertFileNotExist(filename string) { func (th testHelper) assertFileNotExist(filename string) {
exists, err := helpers.Exists(filename, th.Fs.Destination) exists, err := helpers.Exists(filename, th.Fs.PublishDir)
th.Assert(err, qt.IsNil) th.Assert(err, qt.IsNil)
th.Assert(exists, qt.Equals, false) th.Assert(exists, qt.Equals, false)
} }
@ -892,7 +891,7 @@ func loadTestConfig(fs afero.Fs, withConfig ...func(cfg config.Provider) error)
func newTestCfgBasic() (config.Provider, *hugofs.Fs) { func newTestCfgBasic() (config.Provider, *hugofs.Fs) {
mm := afero.NewMemMapFs() mm := afero.NewMemMapFs()
v := config.New() v := config.NewWithTestDefaults()
v.Set("defaultContentLanguageInSubdir", true) v.Set("defaultContentLanguageInSubdir", true)
fs := hugofs.NewFrom(hugofs.NewBaseFileDecorator(mm), v) fs := hugofs.NewFrom(hugofs.NewBaseFileDecorator(mm), v)

View file

@ -500,16 +500,7 @@ func newDepsConfig(tp *TranslationProvider, cfg config.Provider, fs *hugofs.Fs)
} }
func getConfig() config.Provider { func getConfig() config.Provider {
v := config.New() v := config.NewWithTestDefaults()
v.Set("defaultContentLanguage", "en")
v.Set("contentDir", "content")
v.Set("dataDir", "data")
v.Set("i18nDir", "i18n")
v.Set("layoutDir", "layouts")
v.Set("archetypeDir", "archetypes")
v.Set("assetDir", "assets")
v.Set("resourceDir", "resources")
v.Set("publishDir", "public")
langs.LoadLanguageSettings(v, nil) langs.LoadLanguageSettings(v, nil)
mod, err := modules.CreateProjectModule(v) mod, err := modules.CreateProjectModule(v)
if err != nil { if err != nil {

View file

@ -16,14 +16,13 @@ package langs
import ( import (
"testing" "testing"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
) )
func TestGetGlobalOnlySetting(t *testing.T) { func TestGetGlobalOnlySetting(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
v.Set("defaultContentLanguageInSubdir", true) v.Set("defaultContentLanguageInSubdir", true)
v.Set("contentDir", "content") v.Set("contentDir", "content")
v.Set("paginatePath", "page") v.Set("paginatePath", "page")
@ -38,7 +37,7 @@ func TestGetGlobalOnlySetting(t *testing.T) {
func TestLanguageParams(t *testing.T) { func TestLanguageParams(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
v.Set("p1", "p1cfg") v.Set("p1", "p1cfg")
v.Set("contentDir", "content") v.Set("contentDir", "content")

View file

@ -262,7 +262,7 @@ Position: {{ .Position | safeHTML }}
}, },
).Build() ).Build()
b.AssertFileContent("public/p1/index.html", filepath.FromSlash("Position: \"content/p1.md:7:1\"")) b.AssertFileContent("public/p1/index.html", filepath.FromSlash("Position: \"/content/p1.md:7:1\""))
} }
// Issue 9571 // Issue 9571

View file

@ -16,14 +16,13 @@ package minifiers
import ( import (
"testing" "testing"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
) )
func TestConfig(t *testing.T) { func TestConfig(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
v.Set("minify", map[string]any{ v.Set("minify", map[string]any{
"disablexml": true, "disablexml": true,
@ -53,7 +52,7 @@ func TestConfig(t *testing.T) {
func TestConfigLegacy(t *testing.T) { func TestConfigLegacy(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
// This was a bool < Hugo v0.58. // This was a bool < Hugo v0.58.
v.Set("minify", true) v.Set("minify", true)

View file

@ -28,7 +28,7 @@ import (
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
m, _ := New(media.DefaultTypes, output.DefaultFormats, v) m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
var rawJS string var rawJS string
@ -76,7 +76,7 @@ func TestNew(t *testing.T) {
func TestConfigureMinify(t *testing.T) { func TestConfigureMinify(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
v.Set("minify", map[string]any{ v.Set("minify", map[string]any{
"disablexml": true, "disablexml": true,
"tdewolff": map[string]any{ "tdewolff": map[string]any{
@ -110,7 +110,7 @@ func TestConfigureMinify(t *testing.T) {
func TestJSONRoundTrip(t *testing.T) { func TestJSONRoundTrip(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
m, _ := New(media.DefaultTypes, output.DefaultFormats, v) m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
for _, test := range []string{`{ for _, test := range []string{`{
@ -148,7 +148,7 @@ func TestJSONRoundTrip(t *testing.T) {
func TestBugs(t *testing.T) { func TestBugs(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
m, _ := New(media.DefaultTypes, output.DefaultFormats, v) m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
for _, test := range []struct { for _, test := range []struct {
@ -171,7 +171,7 @@ func TestBugs(t *testing.T) {
// Renamed to Precision in v2.7.0. Check that we support both. // Renamed to Precision in v2.7.0. Check that we support both.
func TestDecodeConfigDecimalIsNowPrecision(t *testing.T) { func TestDecodeConfigDecimalIsNowPrecision(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
v.Set("minify", map[string]any{ v.Set("minify", map[string]any{
"disablexml": true, "disablexml": true,
"tdewolff": map[string]any{ "tdewolff": map[string]any{
@ -194,7 +194,7 @@ func TestDecodeConfigDecimalIsNowPrecision(t *testing.T) {
// Issue 9456 // Issue 9456
func TestDecodeConfigKeepWhitespace(t *testing.T) { func TestDecodeConfigKeepWhitespace(t *testing.T) {
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
v.Set("minify", map[string]any{ v.Set("minify", map[string]any{
"tdewolff": map[string]any{ "tdewolff": map[string]any{
"html": map[string]any{ "html": map[string]any{

View file

@ -23,7 +23,6 @@ import (
"time" "time"
"github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/media" "github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/minifiers" "github.com/gohugoio/hugo/minifiers"
"github.com/gohugoio/hugo/output" "github.com/gohugoio/hugo/output"
@ -139,7 +138,7 @@ func TestClassCollector(t *testing.T) {
if skipMinifyTest[test.name] { if skipMinifyTest[test.name] {
c.Skip("skip minify test") c.Skip("skip minify test")
} }
v := config.New() v := config.NewWithTestDefaults()
m, _ := minifiers.New(media.DefaultTypes, output.DefaultFormats, v) m, _ := minifiers.New(media.DefaultTypes, output.DefaultFormats, v)
m.Minify(media.HTMLType, w, strings.NewReader(test.html)) m.Minify(media.HTMLType, w, strings.NewReader(test.html))

View file

@ -27,9 +27,7 @@ import (
) )
func NewTestResourceSpec() (*resources.Spec, error) { func NewTestResourceSpec() (*resources.Spec, error) {
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("baseURL", "https://example.org")
cfg.Set("publishDir", "public")
imagingCfg := map[string]any{ imagingCfg := map[string]any{
"resampleFilter": "linear", "resampleFilter": "linear",

View file

@ -79,7 +79,7 @@ func newTestResourceSpec(desc specDescriptor) *Spec {
cfg.Set("imaging", imagingCfg) cfg.Set("imaging", imagingCfg)
fs := hugofs.NewFrom(afs, cfg) fs := hugofs.NewFrom(afs, cfg)
fs.Destination = hugofs.NewCreateCountingFs(fs.Destination) fs.PublishDir = hugofs.NewCreateCountingFs(fs.PublishDir)
s, err := helpers.NewPathSpec(fs, cfg, nil) s, err := helpers.NewPathSpec(fs, cfg, nil)
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
@ -118,7 +118,6 @@ func newTestResourceOsFs(c *qt.C) (*Spec, string) {
cfg.Set("workingDir", workDir) cfg.Set("workingDir", workDir)
fs := hugofs.NewFrom(hugofs.NewBaseFileDecorator(hugofs.Os), cfg) fs := hugofs.NewFrom(hugofs.NewBaseFileDecorator(hugofs.Os), cfg)
fs.Destination = &afero.MemMapFs{}
s, err := helpers.NewPathSpec(fs, cfg, nil) s, err := helpers.NewPathSpec(fs, cfg, nil)
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)

View file

@ -70,13 +70,13 @@ func TestTransform(t *testing.T) {
// Verify that we publish the same file once only. // Verify that we publish the same file once only.
assertNoDuplicateWrites := func(c *qt.C, spec *Spec) { assertNoDuplicateWrites := func(c *qt.C, spec *Spec) {
c.Helper() c.Helper()
d := spec.Fs.Destination.(hugofs.DuplicatesReporter) d := spec.Fs.PublishDir.(hugofs.DuplicatesReporter)
c.Assert(d.ReportDuplicates(), qt.Equals, "") c.Assert(d.ReportDuplicates(), qt.Equals, "")
} }
assertShouldExist := func(c *qt.C, spec *Spec, filename string, should bool) { assertShouldExist := func(c *qt.C, spec *Spec, filename string, should bool) {
c.Helper() c.Helper()
exists, _ := helpers.Exists(filepath.FromSlash(filename), spec.Fs.Destination) exists, _ := helpers.Exists(filepath.FromSlash(filename), spec.Fs.WorkingDirReadOnly)
c.Assert(exists, qt.Equals, should) c.Assert(exists, qt.Equals, should)
} }

View file

@ -77,15 +77,7 @@ func TestUnicodeNorm(t *testing.T) {
} }
func newTestConfig() config.Provider { func newTestConfig() config.Provider {
v := config.New() v := config.NewWithTestDefaults()
v.Set("contentDir", "content")
v.Set("dataDir", "data")
v.Set("i18nDir", "i18n")
v.Set("layoutDir", "layouts")
v.Set("archetypeDir", "archetypes")
v.Set("resourceDir", "resources")
v.Set("publishDir", "public")
v.Set("assetDir", "assets")
_, err := langs.LoadLanguageSettings(v, nil) _, err := langs.LoadLanguageSettings(v, nil)
if err != nil { if err != nil {
panic(err) panic(err)

View file

@ -985,7 +985,5 @@ func newDeps(cfg config.Provider) *deps.Deps {
} }
func newTestNs() *Namespace { func newTestNs() *Namespace {
v := config.New() return New(newDeps(config.NewWithTestDefaults()))
v.Set("contentDir", "content")
return New(newDeps(v))
} }

View file

@ -40,7 +40,7 @@ import (
func TestScpGetLocal(t *testing.T) { func TestScpGetLocal(t *testing.T) {
t.Parallel() t.Parallel()
v := config.New() v := config.NewWithTestDefaults()
fs := hugofs.NewMem(v) fs := hugofs.NewMem(v)
ps := helpers.FilePathSeparator ps := helpers.FilePathSeparator
@ -145,9 +145,8 @@ func TestScpGetRemoteParallel(t *testing.T) {
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
for _, ignoreCache := range []bool{false} { for _, ignoreCache := range []bool{false} {
cfg := config.New() cfg := config.NewWithTestDefaults()
cfg.Set("ignoreCache", ignoreCache) cfg.Set("ignoreCache", ignoreCache)
cfg.Set("contentDir", "content")
ns := New(newDeps(cfg)) ns := New(newDeps(cfg))
ns.client = cl ns.client = cl
@ -227,7 +226,5 @@ func newDeps(cfg config.Provider) *deps.Deps {
} }
func newTestNs() *Namespace { func newTestNs() *Namespace {
v := config.New() return New(newDeps(config.NewWithTestDefaults()))
v.Set("contentDir", "content")
return New(newDeps(v))
} }

View file

@ -74,7 +74,7 @@ func (ns *Namespace) Config(path any) (image.Config, error) {
return config, nil return config, nil
} }
f, err := ns.deps.Fs.WorkingDir.Open(filename) f, err := ns.deps.Fs.WorkingDirReadOnly.Open(filename)
if err != nil { if err != nil {
return image.Config{}, err return image.Config{}, err
} }

View file

@ -82,7 +82,7 @@ func TestNSConfig(t *testing.T) {
t.Parallel() t.Parallel()
c := qt.New(t) c := qt.New(t)
v := config.New() v := config.NewWithTestDefaults()
v.Set("workingDir", "/a/b") v.Set("workingDir", "/a/b")
ns := New(&deps.Deps{Fs: hugofs.NewMem(v)}) ns := New(&deps.Deps{Fs: hugofs.NewMem(v)})