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  }