Repository: gohugoio/hugo (language: go)

Issue:
Regression: readDir now reads inside of theme directory
There seems to be a change in the search base paths used by `readDir` between `hugo v0.92.2-CDF6A0D6` and `hugo v0.93.1-E9669FED`.

When I compare the outputs of `readDir .`, I observe that with the latest version `readDir` returns results from **both** `.` and `themes/my-theme`, whereas the old version only read from `.`.



Discussion / hints:
Ironically this happened as a result of me adding some missing tests.

I do understand that the `readDir` is convenient, but you can I suspect that the functions in the `resources` namespace would be better, as what you get there (more specific, the `Resource` interface) fits into the rest of what we call "Hugo Pipes": You can publish it (.RelPermalink), process it (minify, fingerprint, build JavaScript bundles etc.) or you can inline the content `.Content` etc.:

https://gohugo.io/hugo-pipes/introduction/#find-resources-in-assets
My take was that Resources would be the correct way to find files across theme and project, but that readDir would be very explicit in where it reads.

It's ironic, because looking at our readDir-using code (which led me to spot this change), it's clear that what we should be using resources instead, as you suggest. 
>My take was that Resources would be the correct way to find files across theme and project,

That is one take, but any folder below `/assets` is only a _composite_ if you make it so; if you put files in `/assets/nothemesfileshereplease` you should be safe.

Thanks, I grasp the underlying model better now: the asset folder is special, in that it is a blend, and readDir respects its special nature. 
I'm going to fix this particular issue by reverting to the old behaviour; also see #9604

Fix patch:
diff --git a/hugofs/fs.go b/hugofs/fs.go
index 2c57fe8b5a8..54d962553e7 100644
--- a/hugofs/fs.go
+++ b/hugofs/fs.go
@@ -41,6 +41,7 @@ type Fs struct {
 
 	// WorkingDir is a read-only file system
 	// restricted to the project working dir.
+	// TODO(bep) get rid of this (se BaseFs)
 	WorkingDir *afero.BasePathFs
 }
 
diff --git a/hugolib/filesystems/basefs.go b/hugolib/filesystems/basefs.go
index aae3613f247..04648c25f07 100644
--- a/hugolib/filesystems/basefs.go
+++ b/hugolib/filesystems/basefs.go
@@ -68,6 +68,9 @@ type BaseFs struct {
 	// This usually maps to /my-project/public.
 	PublishFs afero.Fs
 
+	// A read-only filesystem from the project workDir (no theme here).
+	WorkDir afero.Fs
+
 	theBigFs *filesystemsCollector
 
 	// Locks.
@@ -202,7 +205,7 @@ type SourceFilesystems struct {
 	// with any sub module's resource fs layered below.
 	ResourcesCache afero.Fs
 
-	// The project folder.
+	// The work folder (may be a composite of project and theme components).
 	Work afero.Fs
 
 	// When in multihost we have one static filesystem per language. The sync
@@ -435,9 +438,11 @@ func NewBase(p *paths.Paths, logger loggers.Logger, options ...func(*BaseFs) err
 
 	publishFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Destination, p.AbsPublishDir))
 	sourceFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Source, p.WorkingDir))
+	workDir := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(afero.NewReadOnlyFs(fs.Source), p.WorkingDir))
 
 	b := &BaseFs{
 		SourceFs:  sourceFs,
+		WorkDir:   workDir,
 		PublishFs: publishFs,
 		buildMu:   lockedfile.MutexAt(filepath.Join(p.WorkingDir, lockFileBuild)),
 	}
diff --git a/tpl/os/os.go b/tpl/os/os.go
index 2da792ac196..5abc03c68dc 100644
--- a/tpl/os/os.go
+++ b/tpl/os/os.go
@@ -33,7 +33,8 @@ func New(d *deps.Deps) *Namespace {
 	// The docshelper script does not have or need all the dependencies set up.
 	if d.PathSpec != nil {
 		readFileFs = afero.NewReadOnlyFs(afero.NewCopyOnWriteFs(d.PathSpec.BaseFs.Content.Fs, d.PathSpec.BaseFs.Work))
-		workFs = d.PathSpec.BaseFs.Work
+		// See #9599
+		workFs = d.PathSpec.BaseFs.WorkDir
 	}
 
 	return &Namespace{


Test patch:
diff --git a/tpl/os/integration_test.go b/tpl/os/integration_test.go
new file mode 100644
index 00000000000..fe1bb3d6e0b
--- /dev/null
+++ b/tpl/os/integration_test.go
@@ -0,0 +1,51 @@
+// Copyright 2022 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package os_test
+
+import (
+	"testing"
+
+	"github.com/gohugoio/hugo/hugolib"
+)
+
+// Issue 9599
+func TestReadDirWorkDir(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+theme = "mytheme"
+-- myproject.txt --
+Hello project!
+-- themes/mytheme/mytheme.txt --
+Hello theme!
+-- layouts/index.html --
+{{ $entries := (readDir ".") }}
+START:|{{ range $entry := $entries }}{{ if not $entry.IsDir }}{{ $entry.Name }}|{{ end }}{{ end }}:END:
+
+
+  `
+
+	b := hugolib.NewIntegrationTestBuilder(
+		hugolib.IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+			NeedsOsFS:   true,
+		},
+	).Build()
+
+	b.AssertFileContent("public/index.html", `
+START:|config.toml|myproject.txt|:END:
+`)
+}