overlapfs/overlapfs.go
2023-10-08 15:32:36 +01:00

90 lines
1.8 KiB
Go

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)
}
}
}