Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tpl: modify file system in os functions #13189

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hugofs/fileinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ func (fi *dirNameOnlyFileInfo) Name() string {
}

func (fi *dirNameOnlyFileInfo) Size() int64 {
panic("not implemented")
return 0
}

func (fi *dirNameOnlyFileInfo) Mode() os.FileMode {
Expand Down
30 changes: 28 additions & 2 deletions hugofs/rootmapping_fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,12 +533,17 @@ func (fs *RootMappingFs) cleanName(name string) string {

func (rfs *RootMappingFs) collectDirEntries(prefix string) ([]iofs.DirEntry, error) {
prefix = filepathSeparator + rfs.cleanName(prefix)

var fis []iofs.DirEntry

seen := make(map[string]bool) // Prevent duplicate directories
level := strings.Count(prefix, filepathSeparator)

// special case requesting root of filesystem
if prefix == filepathSeparator {
prefix = ""
level = 0
}

collectDir := func(rm RootMapping, fi FileMetaInfo) error {
f, err := fi.Meta().Open()
if err != nil {
Expand Down Expand Up @@ -832,7 +837,11 @@ func (f *rootMappingDir) Stat() (iofs.FileInfo, error) {
}

func (f *rootMappingDir) Readdir(count int) ([]os.FileInfo, error) {
panic("not supported: use ReadDir")
dirs, err := f.ReadDir(count)
if err != nil {
return nil, err
}
return dirEntriesToFileInfo(dirs)
}

// Note that Readdirnames preserves the order of the underlying filesystem(s),
Expand All @@ -845,6 +854,23 @@ func (f *rootMappingDir) Readdirnames(count int) ([]string, error) {
return dirEntriesToNames(dirs), nil
}

func dirEntriesToFileInfo(fis []iofs.DirEntry) ([]os.FileInfo, error) {
fileInfos := make([]os.FileInfo, len(fis))
for i, d := range fis {
v, ok := d.(*dirEntryMeta)
if ok {
fileInfos[i] = v
} else {
fileInfo, err := d.Info()
fileInfos[i] = fileInfo
if err != nil {
return nil, err
}
}
}
return fileInfos, nil
}

func dirEntriesToNames(fis []iofs.DirEntry) []string {
names := make([]string, len(fis))
for i, d := range fis {
Expand Down
92 changes: 91 additions & 1 deletion hugofs/rootmapping_fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/htesting"
"github.com/google/go-cmp/cmp"
"github.com/spf13/afero"
)

Expand Down Expand Up @@ -162,11 +163,17 @@ func TestRootMappingFsDirnames(t *testing.T) {
fifm := fif.(FileMetaInfo).Meta()
c.Assert(fifm.Filename, qt.Equals, filepath.FromSlash("f2t/myfile.txt"))

root, err := rfs.Open("static")
root, err := rfs.Open(".")
c.Assert(err, qt.IsNil)

dirnames, err := root.Readdirnames(-1)
c.Assert(err, qt.IsNil)
c.Assert(dirnames, qt.DeepEquals, []string{"static"})

static, err := rfs.Open("static")
c.Assert(err, qt.IsNil)
dirnames, err = static.Readdirnames(-1)
c.Assert(err, qt.IsNil)
c.Assert(dirnames, qt.DeepEquals, []string{"af3", "bf1", "cf2"})
}

Expand Down Expand Up @@ -296,6 +303,88 @@ func TestRootMappingFsMount(t *testing.T) {
})
}

func TestReaddirRootMappingFsMountOverlap(t *testing.T) {
c := qt.New(t)
fs := NewBaseFileDecorator(afero.NewMemMapFs())

c.Assert(afero.WriteFile(fs, filepath.FromSlash("da/a.txt"), []byte("some no content"), 0o755), qt.IsNil)
c.Assert(afero.WriteFile(fs, filepath.FromSlash("db/b.txt"), []byte("some no content"), 0o755), qt.IsNil)
c.Assert(afero.WriteFile(fs, filepath.FromSlash("dc/c.txt"), []byte("some no content"), 0o755), qt.IsNil)
c.Assert(afero.WriteFile(fs, filepath.FromSlash("de/e.txt"), []byte("some no content"), 0o755), qt.IsNil)

rm := []RootMapping{
{
From: "static",
To: "da",
},
{
From: "static/b",
To: "db",
},
{
From: "static/b/c",
To: "dc",
},
{
From: "/static/e/",
To: "de",
},
{
From: "/static/file.txt",
To: "de/e.txt",
},
}

rfs, err := NewRootMappingFs(fs, rm...)
c.Assert(err, qt.IsNil)

checkBasicFileInfos := func(name string, expect []string) {
c.Helper()
name = filepath.FromSlash(name)
f, err := rfs.Open(name)
c.Assert(err, qt.IsNil)
defer f.Close()
infos, err := f.Readdir(-1)
c.Assert(err, qt.IsNil)
sortOrderFileInfo := func(i, j int) bool {
return infos[i].Name() < infos[j].Name()
}
sort.Slice(infos, sortOrderFileInfo)
actualNames := make([]string, len(infos))
for i, info := range infos {
actualNames[i] = info.Name()
}
expectNames := make([]string, len(expect))
expectInfos := make([]iofs.FileInfo, len(expect))
for i, name := range expect {
name = filepath.FromSlash(name)
fileInfo, err := rfs.Stat(name)
c.Assert(err, qt.IsNil)
expectInfos[i] = fileInfo

_, name = filepath.Split(name)
expectNames[i] = name
}

// check names
c.Assert(actualNames, qt.DeepEquals, expectNames, qt.Commentf(fmt.Sprintf("%#v", actualNames)))
// check other props
comp := func(x iofs.FileInfo, y iofs.FileInfo) bool {
if x == nil && y == nil {
return true
}
return x != nil && y != nil &&
x.Mode() == y.Mode() &&
x.ModTime() == y.ModTime() &&
x.Size() == y.Size() &&
x.Sys() == y.Sys()
}
c.Assert(infos, qt.CmpEquals(cmp.Comparer(comp)), expectInfos, qt.Commentf(fmt.Sprintf("%#v", infos)))
}
checkBasicFileInfos("static", []string{"static/a.txt", "static/b", "static/e", "static/file.txt"})
checkBasicFileInfos("", []string{"static"})
}

func TestRootMappingFsMountOverlap(t *testing.T) {
c := qt.New(t)
fs := NewBaseFileDecorator(afero.NewMemMapFs())
Expand Down Expand Up @@ -338,6 +427,7 @@ func TestRootMappingFsMountOverlap(t *testing.T) {
c.Assert(names, qt.DeepEquals, expect, qt.Commentf(fmt.Sprintf("%#v", names)))
}

checkDirnames(".", []string{"static"})
checkDirnames("static", []string{"a.txt", "b", "e"})
checkDirnames("static/b", []string{"b.txt", "c"})
checkDirnames("static/b/c", []string{"c.txt"})
Expand Down
4 changes: 4 additions & 0 deletions hugolib/filesystems/basefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ func (f *fakeLockfileMutex) Lock() (func(), error) {
return func() { f.mu.Unlock() }, nil
}

func (b *BaseFs) OverlayMountsFs() (*overlayfs.OverlayFs) {
return b.theBigFs.overlayMounts
}

// Tries to acquire a build lock.
func (b *BaseFs) LockBuild() (unlock func(), err error) {
return b.buildMu.Lock()
Expand Down
76 changes: 56 additions & 20 deletions tpl/os/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,36 @@ import (
"github.com/bep/overlayfs"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/spf13/afero"
"github.com/spf13/cast"
)

// New returns a new instance of the os-namespaced template functions.
func New(d *deps.Deps) *Namespace {
var readFileFs, workFs afero.Fs
var mountsFs afero.Fs

// The docshelper script does not have or need all the dependencies set up.
if d.PathSpec != nil {
readFileFs = overlayfs.New(overlayfs.Options{
mountsFs = overlayfs.New(overlayfs.Options{
Fss: []afero.Fs{
d.PathSpec.BaseFs.Work,
d.PathSpec.BaseFs.Content.Fs,
d.PathSpec.BaseFs.WorkDir,
d.PathSpec.BaseFs.OverlayMountsFs().WithDirsMerger(hugofs.AppendDirsMerger),
},
})
// See #9599
workFs = d.PathSpec.BaseFs.WorkDir
}

return &Namespace{
readFileFs: readFileFs,
workFs: workFs,
deps: d,
mountsFs: mountsFs,
deps: d,
}
}

// Namespace provides template functions for the "os" namespace.
type Namespace struct {
readFileFs afero.Fs
workFs afero.Fs
deps *deps.Deps
mountsFs afero.Fs
deps *deps.Deps
}

// Getenv retrieves the value of the environment variable named by the key.
Expand All @@ -80,7 +78,13 @@ func readFile(fs afero.Fs, filename string) (string, error) {
if filename == "" || filename == "." || filename == string(_os.PathSeparator) {
return "", errors.New("invalid filename")
}

stat, err := fs.Stat(filename)
if err != nil {
return "", err
}
if stat.IsDir() {
return "", errors.New("invalid filename")
}
b, err := afero.ReadFile(fs, filename)
if err != nil {
return "", err
Expand All @@ -93,16 +97,27 @@ func readFile(fs afero.Fs, filename string) (string, error) {
// It returns the contents as a string.
// There is an upper size limit set at 1 megabytes.
func (ns *Namespace) ReadFile(i any) (string, error) {
s, err := cast.ToStringE(i)
path, err := cast.ToStringE(i)
if err != nil {
return "", err
}

if ns.deps.PathSpec != nil {
s = ns.deps.PathSpec.RelPathify(s)
path = ns.deps.PathSpec.RelPathify(path)
}

s, err = readFile(ns.readFileFs, s)
s, err := readFile(ns.mountsFs, path)
if err != nil {
path = filepath.Join(files.ComponentFolderContent, path)
sContent, errContent := readFile(ns.mountsFs, path)
if errContent != nil && !herrors.IsNotExist(errContent) {
err = errContent
}
if errContent == nil {
err = nil
s = sContent
}
}
if err != nil && herrors.IsNotExist(err) {
return "", nil
}
Expand All @@ -116,7 +131,7 @@ func (ns *Namespace) ReadDir(i any) ([]_os.FileInfo, error) {
return nil, err
}

list, err := afero.ReadDir(ns.workFs, path)
list, err := afero.ReadDir(ns.mountsFs, path)
if err != nil {
return nil, fmt.Errorf("failed to read directory %q: %s", path, err)
}
Expand All @@ -134,8 +149,18 @@ func (ns *Namespace) FileExists(i any) (bool, error) {
if path == "" {
return false, errors.New("fileExists needs a path to a file")
}

status, err := afero.Exists(ns.readFileFs, path)
status, err := afero.Exists(ns.mountsFs, path)
if (err != nil) || (status == false) {
path = filepath.Join(files.ComponentFolderContent, path)
statusContent, errContent := afero.Exists(ns.mountsFs, path)
if errContent != nil && !herrors.IsNotExist(errContent) {
err = errContent
}
if errContent == nil {
err = nil
status = statusContent
}
}
if err != nil {
return false, err
}
Expand All @@ -153,8 +178,19 @@ func (ns *Namespace) Stat(i any) (_os.FileInfo, error) {
if path == "" {
return nil, errors.New("fileStat needs a path to a file")
}
r, err := ns.mountsFs.Stat(path)
if err != nil {
path = filepath.Join(files.ComponentFolderContent, path)
rContent, errContent := ns.mountsFs.Stat(path)
if errContent != nil && !herrors.IsNotExist(errContent) {
err = errContent
}
if errContent == nil {
err = nil
r = rContent
}

r, err := ns.readFileFs.Stat(path)
}
if err != nil {
return nil, err
}
Expand Down
Loading