tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/engine/fs/makefs/makefs.go (about) 1 package makefs 2 3 import ( 4 "bytes" 5 "io" 6 "io/fs" 7 "log" 8 "path/filepath" 9 10 "golang.org/x/text/transform" 11 "tractor.dev/toolkit-go/engine/fs/fsutil" 12 "tractor.dev/toolkit-go/engine/fs/memfs" 13 "tractor.dev/toolkit-go/engine/fs/mountfs" 14 ) 15 16 // still wip/experimental 17 18 type Opener func(name string) fs.File 19 20 func MountOpener(fsys fs.FS, name string, opener Opener) *FS { 21 mfs := memfs.New() 22 mfs.MkdirAll(filepath.Dir(name), 0755) 23 f, err := mfs.Create(name) 24 if err != nil { 25 panic(err) 26 } 27 f.Close() 28 fsys = mountfs.New(fsys, "", mfs, mountfs.Union()) 29 return New(fsys, name, opener) 30 } 31 32 func New(fsys fs.FS, pattern string, opener Opener) *FS { 33 return &FS{FS: fsys, pattern: pattern, opener: opener} 34 } 35 36 type FS struct { 37 fs.FS 38 39 opener Opener 40 pattern string 41 } 42 43 func (m *FS) Open(name string) (fs.File, error) { 44 if ok, err := filepath.Match(m.pattern, name); ok && err == nil { 45 return m.opener(name), nil 46 } 47 return m.FS.Open(name) 48 } 49 50 func (m *FS) OpenFile(name string, flag int, perm fs.FileMode) (fs.File, error) { 51 if ok, err := filepath.Match(m.pattern, name); ok && err == nil { 52 return m.opener(name), nil 53 } 54 return fsutil.OpenFile(m.FS, name, flag, perm) 55 } 56 57 func (m *FS) Stat(name string) (fi fs.FileInfo, err error) { 58 if ok, err := filepath.Match(m.pattern, name); ok && err == nil { 59 return m.opener(name).Stat() 60 } 61 return fs.Stat(m.FS, name) 62 } 63 64 type OpenFile struct { 65 Path string 66 File fs.File 67 } 68 69 // MakeFunc takes open Files and does some operation returning bytes. 70 type MakeFunc func(files []OpenFile) ([]byte, error) 71 72 // MakeOpener returns an Opener that performs a MakeFunc using optional dependency filepaths. 73 // The idea is to replicate Make task semantics. 74 func MakeOpener(fsys fs.FS, fn MakeFunc, deps ...string) Opener { 75 return func(filename string) fs.File { 76 var files []OpenFile 77 if len(deps) > 0 { 78 fs.WalkDir(fsys, "", func(path string, info fs.DirEntry, err error) error { 79 if info.IsDir() { 80 return nil 81 } 82 for _, dep := range deps { 83 ok, err := filepath.Match(dep, path) 84 if !ok || err != nil { 85 continue 86 } 87 f, err := fsys.Open(path) 88 if err != nil { 89 log.Println(err) 90 continue 91 } 92 files = append(files, OpenFile{Path: path, File: f}) 93 } 94 return nil 95 }) 96 } 97 98 f := memfs.NewFileHandle(memfs.CreateFile(filepath.Base(filename))) 99 defer f.Seek(0, 0) 100 101 b, err := fn(files) 102 if err != nil { 103 log.Println(err) 104 f.Write([]byte(err.Error())) 105 return f 106 } 107 108 _, err = f.Write(b) 109 if err != nil { 110 log.Println(err) 111 f.Write([]byte(err.Error())) 112 } 113 return f 114 } 115 } 116 117 // TransformFrom returns an Opener that performs a transform on the given source file. 118 // It is a more specific API for MakeOpener focusing on a single file and the transform.Transformer interface. 119 func TransformFrom(fsys fs.FS, name string, xform transform.Transformer) Opener { 120 return MakeOpener(fsys, func(files []OpenFile) ([]byte, error) { 121 b, err := io.ReadAll(files[0].File) 122 if err != nil { 123 return []byte{}, err 124 } 125 var src io.Reader 126 src = bytes.NewBuffer(b) 127 if xform != nil { 128 src = transform.NewReader(src, xform) 129 } 130 var dst bytes.Buffer 131 if _, err := io.Copy(&dst, src); err != nil { 132 return []byte{}, err 133 } 134 return dst.Bytes(), nil 135 }, name) 136 }