1
+ // Package iofs provides an adapter from billy.Filesystem to a the
2
+ // standard library io.fs.FS interface.
3
+ package iofs
4
+
5
+ import (
6
+ "io"
7
+ "io/fs"
8
+ "path/filepath"
9
+
10
+ billyfs "github.com/go-git/go-billy/v5"
11
+ "github.com/go-git/go-billy/v5/helper/polyfill"
12
+ )
13
+
14
+ // Wrap adapts a billy.Filesystem to a io.fs.FS.
15
+ func Wrap (fs billyfs.Basic ) fs.FS {
16
+ return & adapterFs {fs : polyfill .New (fs )}
17
+ }
18
+
19
+ type adapterFs struct {
20
+ fs billyfs.Filesystem
21
+ }
22
+
23
+ var _ fs.FS = (* adapterFs )(nil )
24
+ var _ fs.ReadDirFS = (* adapterFs )(nil )
25
+ var _ fs.StatFS = (* adapterFs )(nil )
26
+ var _ fs.ReadFileFS = (* adapterFs )(nil )
27
+
28
+ // GlobFS would be harder, we don't implement for now.
29
+
30
+ // Open implements fs.FS.
31
+ func (a * adapterFs ) Open (name string ) (fs.File , error ) {
32
+ if name [0 ] == '/' || name != filepath .Clean (name ) {
33
+ // fstest.TestFS explicitly checks that these should return error
34
+ // MemFS is performs the clean internally, so we need to block that here for testing.
35
+ return nil , & fs.PathError {Op : "open" , Path : name , Err : fs .ErrInvalid }
36
+ }
37
+ stat , err := a .fs .Stat (name )
38
+ if err != nil {
39
+ return nil , err
40
+ }
41
+ if stat .IsDir () {
42
+ entries , err := a .ReadDir (name )
43
+ if err != nil {
44
+ return nil , err
45
+ }
46
+ return makeDir (stat , entries ), nil
47
+ }
48
+ file , err := a .fs .Open (name )
49
+ return & adapterFile {file : file , info : stat }, err
50
+ }
51
+
52
+ // ReadDir implements fs.ReadDirFS.
53
+ func (a * adapterFs ) ReadDir (name string ) ([]fs.DirEntry , error ) {
54
+ items , err := a .fs .ReadDir (name )
55
+ if err != nil {
56
+ return nil , err
57
+ }
58
+ entries := make ([]fs.DirEntry , len (items ))
59
+ for i , item := range items {
60
+ entries [i ] = fs .FileInfoToDirEntry (item )
61
+ }
62
+ return entries , nil
63
+ }
64
+
65
+ // Stat implements fs.StatFS.
66
+ func (a * adapterFs ) Stat (name string ) (fs.FileInfo , error ) {
67
+ return a .fs .Stat (name )
68
+ }
69
+
70
+ // ReadFile implements fs.ReadFileFS.
71
+ func (a * adapterFs ) ReadFile (name string ) ([]byte , error ) {
72
+ stat , err := a .fs .Stat (name )
73
+ if err != nil {
74
+ return nil , err
75
+ }
76
+ b := make ([]byte , stat .Size ())
77
+ file , err := a .Open (name )
78
+ if err != nil {
79
+ return nil , err
80
+ }
81
+ defer file .Close ()
82
+ _ , err = file .Read (b )
83
+ return b , err
84
+ }
85
+
86
+ type adapterFile struct {
87
+ file billyfs.File
88
+ info fs.FileInfo
89
+ }
90
+
91
+ var _ fs.File = (* adapterFile )(nil )
92
+
93
+ // Close implements fs.File.
94
+ func (a * adapterFile ) Close () error {
95
+ return a .file .Close ()
96
+ }
97
+
98
+ // Read implements fs.File.
99
+ func (a * adapterFile ) Read (b []byte ) (int , error ) {
100
+ return a .file .Read (b )
101
+ }
102
+
103
+ // Stat implements fs.File.
104
+ func (a * adapterFile ) Stat () (fs.FileInfo , error ) {
105
+ return a .info , nil
106
+ }
107
+
108
+ type adapterDirFile struct {
109
+ adapterFile
110
+ entries []fs.DirEntry
111
+ }
112
+
113
+ var _ fs.ReadDirFile = (* adapterDirFile )(nil )
114
+
115
+ func makeDir (stat fs.FileInfo , entries []fs.DirEntry ) * adapterDirFile {
116
+ return & adapterDirFile {
117
+ adapterFile : adapterFile {info : stat },
118
+ entries : entries ,
119
+ }
120
+ }
121
+
122
+ // Close implements fs.File.
123
+ // Subtle: note that this is shadowing adapterFile.Close.
124
+ func (a * adapterDirFile ) Close () error {
125
+ return nil
126
+ }
127
+
128
+ // ReadDir implements fs.ReadDirFile.
129
+ func (a * adapterDirFile ) ReadDir (n int ) ([]fs.DirEntry , error ) {
130
+ if len (a .entries ) == 0 && n > 0 {
131
+ return nil , io .EOF
132
+ }
133
+ if n <= 0 {
134
+ n = len (a .entries )
135
+ }
136
+ if n > len (a .entries ) {
137
+ n = len (a .entries )
138
+ }
139
+ entries := a .entries [:n ]
140
+ a .entries = a .entries [n :]
141
+ return entries , nil
142
+ }
0 commit comments