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