package fs import ( "context" "fmt" "os" ) // FileSpec describes a single synthetic file exposed by a Tree. type FileSpec struct { Name string Mode os.FileMode Read func() ([]byte, error) Write func([]byte) error // nil = read-only Wait func(ctx context.Context, base string) (content []byte, nextBase string, err error) // nil = not waitable Size func() int64 // optional; if nil, len(Read()) OneShot bool // true = yields data once per open Async bool // true = writes dispatched asynchronously } // NewSynth creates a Tree backed by a table of FileSpecs (synthetic files). func NewSynth(specs []FileSpec) *Tree { index := make(map[string]int, len(specs)) for i, s := range specs { index[s.Name] = i } lookup := func(name string) (*FileSpec, bool) { i, ok := index[name] if !ok { return nil, false } return &specs[i], true } return NewTree(nil, 0, WithLister(func(_ []string) ([]os.DirEntry, error) { entries := make([]os.DirEntry, len(specs)) for i, s := range specs { entries[i] = FileEntry(s.Name, s.Mode) } return entries, nil }), WithOpener(func(_ []string, name string) (File, error) { spec, ok := lookup(name) if !ok { return nil, fmt.Errorf("%s: not found", name) } writeFn := func([]byte) error { return fmt.Errorf("%s: read-only", name) } if spec.Write != nil { writeFn = spec.Write } waitFn := func(context.Context, string) ([]byte, string, error) { return nil, "", fmt.Errorf("%s: not a wait file", name) } if spec.Wait != nil { waitFn = spec.Wait } return &FileConfig{ StatFn: func() (os.FileInfo, error) { var size int64 if spec.Size != nil { size = spec.Size() } else if spec.Wait != nil { size = 4096 } else if data, err := spec.Read(); err == nil { size = int64(len(data)) } return &SyntheticFileInfo{Name_: name, Mode_: spec.Mode, Size_: size}, nil }, ReadFn: spec.Read, WriteFn: writeFn, BlockingReadFn: waitFn, OneShot_: spec.OneShot || spec.Wait != nil, IsBlocking_: spec.Wait != nil, Async_: spec.Async, }, nil }), ) }