mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-09-26 20:11:28 +08:00
internal/file: add VirtualFS.ReadDir and FileEntryFS.ReadDir
Closes #3084
This commit is contained in:
@@ -112,6 +112,33 @@ func (f *FileEntryFS) Open(name string) (fs.File, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FileEntryFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
if !fs.ValidPath(name) {
|
||||
return nil, &fs.PathError{
|
||||
Op: "readdir",
|
||||
Path: name,
|
||||
Err: fs.ErrNotExist,
|
||||
}
|
||||
}
|
||||
|
||||
ent, err := f.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = ent.Close()
|
||||
}()
|
||||
d, ok := ent.(*dir)
|
||||
if !ok {
|
||||
return nil, &fs.PathError{
|
||||
Op: "readdir",
|
||||
Path: name,
|
||||
Err: fs.ErrInvalid,
|
||||
}
|
||||
}
|
||||
return d.ReadDir(-1)
|
||||
}
|
||||
|
||||
type file struct {
|
||||
entry js.Value
|
||||
file js.Value
|
||||
|
@@ -54,6 +54,12 @@ func (v *VirtualFS) Open(name string) (fs.File, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// It is implicitly assumed that all the real paths are under the same directory.
|
||||
// For name == ".", return a special virtual root directory.
|
||||
// Unfortunately it is not possible to return a real directory here because
|
||||
// v.paths might not include some files in the same directory.
|
||||
// TODO: Calculate the common ancestor directory of v.paths and use it.
|
||||
|
||||
if name == "." {
|
||||
return v.newRootFS(), nil
|
||||
}
|
||||
@@ -76,6 +82,42 @@ func (v *VirtualFS) Open(name string) (fs.File, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VirtualFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
if !fs.ValidPath(name) {
|
||||
return nil, &fs.PathError{
|
||||
Op: "readdir",
|
||||
Path: name,
|
||||
Err: fs.ErrNotExist,
|
||||
}
|
||||
}
|
||||
|
||||
if name == "." {
|
||||
root := v.newRootFS()
|
||||
return root.ReadDir(-1)
|
||||
}
|
||||
|
||||
es := strings.Split(name, "/")
|
||||
for _, realPath := range v.paths {
|
||||
if filepath.Base(realPath) != es[0] {
|
||||
continue
|
||||
}
|
||||
f, err := os.Open(filepath.Join(append([]string{realPath}, es[1:]...)...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close
|
||||
}()
|
||||
return f.ReadDir(-1)
|
||||
}
|
||||
|
||||
return nil, &fs.PathError{
|
||||
Op: "readdir",
|
||||
Path: name,
|
||||
Err: fs.ErrNotExist,
|
||||
}
|
||||
}
|
||||
|
||||
type virtualFSRoot struct {
|
||||
realPaths []string
|
||||
offset int
|
||||
|
80
internal/file/file_test.go
Normal file
80
internal/file/file_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2025 The Ebitengine Authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
//go:build !js
|
||||
|
||||
package file_test
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/file"
|
||||
)
|
||||
|
||||
func TestFSReadDir(t *testing.T) {
|
||||
vfs := file.NewVirtualFS([]string{"testdata/foo.txt", "testdata/dir"})
|
||||
|
||||
rootEnts, err := fs.ReadDir(vfs, ".")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rootEnts) != 2 {
|
||||
t.Errorf("len(ents): got: %d, want: %d", len(rootEnts), 2)
|
||||
}
|
||||
slices.SortFunc(rootEnts, func(a, b fs.DirEntry) int {
|
||||
return strings.Compare(a.Name(), b.Name())
|
||||
})
|
||||
if got, want := rootEnts[0].Name(), "dir"; got != want {
|
||||
t.Errorf("ents[0].Name(): got: %s, want: %s", got, want)
|
||||
}
|
||||
if got, want := rootEnts[0].IsDir(), true; got != want {
|
||||
t.Errorf("ents[0].IsDir(): got: false, want: true")
|
||||
}
|
||||
if got, want := rootEnts[1].Name(), "foo.txt"; got != want {
|
||||
t.Errorf("ents[1].Name(): got: %s, want: %s", got, want)
|
||||
}
|
||||
if got, want := rootEnts[1].IsDir(), false; got != want {
|
||||
t.Errorf("ents[1].IsDir(): got: true, want: false")
|
||||
}
|
||||
|
||||
subEnts, err := fs.ReadDir(vfs, "dir")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(subEnts) != 2 {
|
||||
t.Errorf("len(ents): got: %d, want: %d", len(subEnts), 1)
|
||||
}
|
||||
slices.SortFunc(subEnts, func(a, b fs.DirEntry) int {
|
||||
return strings.Compare(a.Name(), b.Name())
|
||||
})
|
||||
if got, want := subEnts[0].Name(), "foo.txt"; got != want {
|
||||
t.Errorf("ents[0].Name(): got: %s, want: %s", got, want)
|
||||
}
|
||||
if got, want := subEnts[0].IsDir(), false; got != want {
|
||||
t.Errorf("ents[0].IsDir(): got: false, want: true")
|
||||
}
|
||||
if got, want := subEnts[1].Name(), "qux.txt"; got != want {
|
||||
t.Errorf("ents[1].Name(): got: %s, want: %s", got, want)
|
||||
}
|
||||
if got, want := subEnts[1].IsDir(), false; got != want {
|
||||
t.Errorf("ents[1].IsDir(): got: true, want: false")
|
||||
}
|
||||
|
||||
if _, err := fs.ReadDir(vfs, "baz.txt"); err == nil {
|
||||
t.Errorf("fs.ReadDir on a file must return an error")
|
||||
}
|
||||
}
|
0
internal/file/testdata/bar.txt
vendored
Normal file
0
internal/file/testdata/bar.txt
vendored
Normal file
0
internal/file/testdata/baz.txt
vendored
Normal file
0
internal/file/testdata/baz.txt
vendored
Normal file
0
internal/file/testdata/dir/foo.txt
vendored
Normal file
0
internal/file/testdata/dir/foo.txt
vendored
Normal file
0
internal/file/testdata/dir/qux.txt
vendored
Normal file
0
internal/file/testdata/dir/qux.txt
vendored
Normal file
0
internal/file/testdata/foo.txt
vendored
Normal file
0
internal/file/testdata/foo.txt
vendored
Normal file
Reference in New Issue
Block a user