From 3bdbd3be801d54c32b9ae91129389cbdac65fb17 Mon Sep 17 00:00:00 2001 From: MrMelon54 Date: Sun, 8 Oct 2023 15:32:36 +0100 Subject: [PATCH] Initial source code --- a/a.go | 6 ++++ a/hello.txt | 1 + a/just-a.txt | 1 + b/b.go | 6 ++++ b/hello.txt | 1 + b/just-b.txt | 1 + go.mod | 11 ++++++ go.sum | 10 ++++++ overlapfs.go | 89 +++++++++++++++++++++++++++++++++++++++++++++++ overlapfs_test.go | 31 +++++++++++++++++ 10 files changed, 157 insertions(+) create mode 100644 a/a.go create mode 100644 a/hello.txt create mode 100644 a/just-a.txt create mode 100644 b/b.go create mode 100644 b/hello.txt create mode 100644 b/just-b.txt create mode 100644 go.mod create mode 100644 go.sum create mode 100644 overlapfs.go create mode 100644 overlapfs_test.go diff --git a/a/a.go b/a/a.go new file mode 100644 index 0000000..ad096b5 --- /dev/null +++ b/a/a.go @@ -0,0 +1,6 @@ +package a + +import "embed" + +//go:embed * +var Embed embed.FS diff --git a/a/hello.txt b/a/hello.txt new file mode 100644 index 0000000..f721577 --- /dev/null +++ b/a/hello.txt @@ -0,0 +1 @@ +Empty \ No newline at end of file diff --git a/a/just-a.txt b/a/just-a.txt new file mode 100644 index 0000000..8c7e5a6 --- /dev/null +++ b/a/just-a.txt @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/b/b.go b/b/b.go new file mode 100644 index 0000000..cad74b1 --- /dev/null +++ b/b/b.go @@ -0,0 +1,6 @@ +package b + +import "embed" + +//go:embed * +var Embed embed.FS diff --git a/b/hello.txt b/b/hello.txt new file mode 100644 index 0000000..c57eff5 --- /dev/null +++ b/b/hello.txt @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git a/b/just-b.txt b/b/just-b.txt new file mode 100644 index 0000000..7371f47 --- /dev/null +++ b/b/just-b.txt @@ -0,0 +1 @@ +B \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5e5feea --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/1f349/overlapfs + +go 1.21.1 + +require github.com/stretchr/testify v1.8.4 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fa4b6e6 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/overlapfs.go b/overlapfs.go new file mode 100644 index 0000000..c176cef --- /dev/null +++ b/overlapfs.go @@ -0,0 +1,89 @@ +package overlapfs + +import ( + "errors" + "fmt" + "io/fs" +) + +// OverlapFS layers B on top of A +// +// Open and Stat reimplemented to show B unless ErrNotExist is returned then show A. +// +// Glob and ReadDir are reimplemented to uniquely merge the returned lists. +// +// For ReadFile and Sub use the standard fs.ReadFile and fs.Sub implementations +type OverlapFS struct { + A, B fs.FS +} + +var _ interface { + fs.FS + fs.StatFS + fs.GlobFS + fs.ReadDirFS +} = &OverlapFS{} + +func (o OverlapFS) Open(name string) (fs.File, error) { + fmt.Println("Open", name) + open, err := o.B.Open(name) + switch { + case err == nil: + return open, nil + case errors.Is(err, fs.ErrNotExist): + return o.A.Open(name) + } + return nil, err +} + +func (o OverlapFS) Stat(name string) (fs.FileInfo, error) { + stat, err := fs.Stat(o.B, name) + switch { + case err == nil: + return stat, nil + case errors.Is(err, fs.ErrNotExist): + return fs.Stat(o.A, name) + } + return nil, err +} + +func (o OverlapFS) Glob(pattern string) ([]string, error) { + aSlice, err := fs.Glob(o.B, pattern) + if err != nil { + return nil, err + } + bSlice, err := fs.Glob(o.A, pattern) + if err != nil { + return nil, err + } + + mergeUnique(aSlice, bSlice, func(s string) string { return s }) + return aSlice, nil +} + +func (o OverlapFS) ReadDir(name string) ([]fs.DirEntry, error) { + aSlice, err := fs.ReadDir(o.B, name) + if err != nil { + return nil, err + } + bSlice, err := fs.ReadDir(o.A, name) + if err != nil { + return nil, err + } + + mergeUnique(aSlice, bSlice, func(entry fs.DirEntry) string { return entry.Name() }) + return aSlice, nil +} + +// mergeUnique puts unique values from b into a +func mergeUnique[T any](a, b []T, key func(T) string) { + m := make(map[string]struct{}) + for _, i := range a { + m[key(i)] = struct{}{} + } + for _, i := range b { + if _, ok := m[key(i)]; !ok { + a = append(a, i) + } + } +} diff --git a/overlapfs_test.go b/overlapfs_test.go new file mode 100644 index 0000000..042bbd9 --- /dev/null +++ b/overlapfs_test.go @@ -0,0 +1,31 @@ +package overlapfs + +import ( + "github.com/1f349/overlapfs/a" + "github.com/1f349/overlapfs/b" + "github.com/stretchr/testify/assert" + "io" + "testing" +) + +func TestOverlapFS_Open(t *testing.T) { + o := OverlapFS{A: a.Embed, B: b.Embed} + open, err := o.Open("hello.txt") + assert.NoError(t, err) + stat, err := open.Stat() + assert.NoError(t, err) + assert.Equal(t, "hello.txt", stat.Name()) + bAll, err := io.ReadAll(open) + assert.Equal(t, "Hello World!", string(bAll)) +} + +func TestOverlapFS_Stat(t *testing.T) { + o := OverlapFS{A: a.Embed, B: b.Embed} + open, err := o.Stat("hello.txt") + assert.NoError(t, err) + stat, err := open.Stat() + assert.NoError(t, err) + assert.Equal(t, "hello.txt", stat.Name()) + bAll, err := io.ReadAll(open) + assert.Equal(t, "Hello World!", string(bAll)) +}