ollie/virtfs/README.md

144 lines
4.2 KiB
Markdown

# virtfs
A Go library for declaring synthetic filesystems. Define your namespace structure statically, populate it dynamically at runtime with closures.
Produces a `*Tree` usable with any filesystem protocol (9P, FUSE, etc.) or as an in-memory filesystem for testing.
## Design
Handlers are plain closures that capture their state at binding time. There is no context type parameter — each handler already knows its target via closure capture.
Dynamic subtrees use `Each`: a bindings function returns `[]Binding`, where each binding carries pre-bound `Children` (fully-wired subtrees with their own closures).
## Quick Start
```go
package main
import (
"fmt"
"ollie/virtfs"
)
type User struct {
ID string
Name string
}
var users = []*User{
{ID: "1", Name: "alice"},
{ID: "2", Name: "bob"},
}
func main() {
spec := virtfs.DirNode("/",
virtfs.FileNode("version", 0444,
virtfs.Doc("API version"),
virtfs.Read(func() ([]byte, error) {
return []byte("1.0\n"), nil
}),
),
virtfs.Each("{user}", func() ([]virtfs.Binding, error) {
var out []virtfs.Binding
for _, u := range users {
user := u // capture
out = append(out, virtfs.Binding{
Name: user.Name,
Aliases: []string{user.ID},
Children: []virtfs.FsNodeDecl{
virtfs.FileNode("id", 0444,
virtfs.Read(func() ([]byte, error) {
return []byte(user.ID + "\n"), nil
}),
),
virtfs.FileNode("name", 0444,
virtfs.Read(func() ([]byte, error) {
return []byte(user.Name + "\n"), nil
}),
),
},
})
}
return out, nil
}),
)
tree := virtfs.BuildTree(spec)
entries, _ := tree.List()
for _, e := range entries {
fmt.Println(e.Name())
}
f, _ := tree.Open("alice/name")
data, _ := f.Read()
fmt.Printf("alice/name: %s", data)
}
```
## API
### Constructors
| Function | Description |
|----------|-------------|
| `DirNode(name, children/options...)` | Static directory |
| `FileNode(name, mode, options...)` | File with handlers |
| `Each(name, bindingsFn, options...)` | Dynamic directory — children come from bindings |
### Options (NodeOption)
| Option | Signature | Description |
|--------|-----------|-------------|
| `Read(fn)` | `func() ([]byte, error)` | Non-blocking read |
| `Write(fn)` | `func([]byte) error` | Write handler |
| `Stream(fn)` | `func(context.Context, string) ([]byte, string, error)` | Blocking streaming read |
| `BlockOnce(fn)` | `func(context.Context, string) ([]byte, string, error)` | Blocking read, returns once |
| `Request(fn)` | `func([]byte) ([]byte, error)` | Write-then-read (rdwr) |
| `Doc(desc)` | `string` | Human-readable description |
| `UID(uid)` | `string` | Set owner |
| `GID(gid)` | `string` | Set group |
| `StatOverride(fn)` | `func() os.FileInfo` | Custom stat |
| `RemoveNode(fn)` | `func() error` | Removal handler |
### Binding
Returned by the `Each` bindings function. Each binding represents one dynamic child.
```go
virtfs.Binding{
Name: "alice",
Aliases: []string{"user-id-123"},
Children: []virtfs.FsNodeDecl{ ... }, // pre-bound subtree
Remove: func() { ... }, // optional
Rename: func(newName string) error { ... }, // optional
}
```
Children are fully-wired — their handlers are closures that already captured the specific instance (no applier/context needed).
### Tree Operations
```go
tree := virtfs.BuildTree(spec)
tree.List() // list root entries
tree.Stat(name) // stat a path
tree.Open(name) // open file for read/write
tree.Create(name) // create file
tree.Delete(name) // remove file
tree.Rename(old, new) // rename file
tree.Readdir(path) // list subdirectory
```
### Help Generation
```go
help := virtfs.GenerateHelp(spec)
// Produces formatted docs from Doc() annotations
```
## License
GPLv3