GitRoot

craft your forge, build your project, grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5package fs
  6
  7import (
  8	"context"
  9	"errors"
 10	"io/fs"
 11	"os"
 12	"strings"
 13	"time"
 14
 15	"gitroot.dev/server/logger"
 16)
 17
 18var ErrFsNotFound = errors.New("fs not found")
 19
 20type multipleFs struct {
 21	logger *logger.Logger
 22	subFs  map[string]fs.FS
 23}
 24
 25func NewMultiple(ctx context.Context, subFs map[string]fs.FS) *multipleFs {
 26	return &multipleFs{
 27		logger: logger.NewLoggerCtx(logger.FS_PLUGIN, ctx).NewSubLogger("multiple"),
 28		subFs:  subFs,
 29	}
 30}
 31
 32func (m *multipleFs) splitFsFile(name string) (fs.FS, string, error) {
 33	parts := strings.SplitN(name, string(os.PathSeparator), 2)
 34	subFs, ok := m.subFs[parts[0]]
 35	if !ok || len(parts) < 2 {
 36		return nil, "", ErrFsNotFound
 37	}
 38	return subFs, parts[1], nil
 39}
 40
 41func (m *multipleFs) ReadFile(name string) ([]byte, error) {
 42	m.logger.Debug("read file", logger.NewLoggerPair("name", name))
 43	subFs, filepath, err := m.splitFsFile(name)
 44	if err != nil {
 45		return nil, err
 46	}
 47	return fs.ReadFile(subFs, filepath)
 48}
 49
 50func (m *multipleFs) ReadDir(name string) ([]fs.DirEntry, error) {
 51	m.logger.Debug("ReadDir", logger.NewLoggerPair("filepath", name))
 52	subFs, filepath, err := m.splitFsFile(name)
 53	if err != nil {
 54		return nil, err
 55	}
 56	return fs.ReadDir(subFs, filepath)
 57}
 58
 59func (m *multipleFs) Stat(name string) (fs.FileInfo, error) {
 60	m.logger.Debug("Stat")
 61	subFs, filepath, err := m.splitFsFile(name)
 62	if err != nil {
 63		return nil, err
 64	}
 65	return fs.Stat(subFs, filepath)
 66}
 67
 68func (m *multipleFs) Sub(dir string) (fs.FS, error) {
 69	m.logger.Debug("Sub")
 70	subFs, filepath, err := m.splitFsFile(dir)
 71	if err != nil {
 72		return nil, err
 73	}
 74	return fs.Sub(subFs, filepath)
 75}
 76
 77func (m *multipleFs) Open(name string) (fs.File, error) {
 78	m.logger.Debug("open file", logger.NewLoggerPair("name", name))
 79	if name == "." {
 80		return &fakeFile{
 81			name:  "/",
 82			subFs: m.subFs,
 83		}, nil
 84	}
 85	subFs, filepath, err := m.splitFsFile(name)
 86	if err != nil {
 87		return nil, err
 88	}
 89	return subFs.Open(filepath)
 90}
 91
 92type fakeFile struct {
 93	name  string
 94	subFs map[string]fs.FS
 95}
 96type fakeFileInfo struct {
 97	name string
 98}
 99
100func (m *fakeFile) Close() error {
101	return nil
102}
103
104func (m *fakeFile) Read(p []byte) (int, error) {
105	return 0, errors.New("can't read a dir")
106}
107
108func (m *fakeFile) Stat() (fs.FileInfo, error) {
109	return &fakeFileInfo{}, nil
110}
111
112func (m *fakeFile) ReadDir(n int) ([]fs.DirEntry, error) {
113	subDirs := []fs.FileInfo{}
114	for key := range m.subFs {
115		subDirs = append(subDirs, &fakeFileInfo{name: key})
116	}
117	return toDirEntry(subDirs), nil
118}
119
120func (f *fakeFileInfo) Name() string {
121	return f.name
122}
123func (f *fakeFileInfo) Size() int64 {
124	return 0
125}
126func (f *fakeFileInfo) Mode() fs.FileMode {
127	return fs.ModeDir
128}
129func (f *fakeFileInfo) ModTime() time.Time {
130	return time.Now()
131}
132func (f *fakeFileInfo) IsDir() bool {
133	return true
134}
135func (f *fakeFileInfo) Sys() any {
136	return nil
137}