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}