ollie/virtfs
Levi Neely 0c30ec71d2 server: reduce statewait timeout from 5s to 500ms
When reading statewait with the same state value, the server now waits
500ms instead of 5 seconds before returning. This makes state polling
much more responsive.
2026-10-06 11:32:31 +02:00
..
LICENSE.md rename fsedsl → virtfs 2026-08-11 17:21:44 +02:00
README.md virtfs: rewrite README for closure-capture model 2026-08-11 17:22:39 +02:00
builder.go fix(virtfs): support Each() with non-template directory names 2026-08-21 13:11:46 +02:00
decl.go refactor: remove dead code from embedding/virtfs/skills (-108 lines) 2026-08-21 17:20:13 +02:00
decl_test.go refactor: remove dead code from embedding/virtfs/skills (-108 lines) 2026-08-21 17:20:13 +02:00
file.go server: reduce statewait timeout from 5s to 500ms 2026-10-06 11:32:31 +02:00
go.mod rename fsedsl → virtfs 2026-08-11 17:21:44 +02:00
help.go rename fsedsl → virtfs 2026-08-11 17:21:44 +02:00
tree.go refactor: remove dead code from embedding/virtfs/skills (-108 lines) 2026-08-21 17:20:13 +02:00
tree_test.go refactor: remove dead code from embedding/virtfs/skills (-108 lines) 2026-08-21 17:20:13 +02:00

README.md

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

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.

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

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

help := virtfs.GenerateHelp(spec)
// Produces formatted docs from Doc() annotations

License

GPLv3