ollie/virtfs
Levi Neely 4a037c0bbe refactor: remove dead code from embedding/virtfs/skills (-108 lines)
embedding/embedding.go:
- Remove EmbedBatch() (never called in production, test updated to use Embed)
- Remove padID field (written but never read)

embedding/index.go:
- Remove Index.mu mutex (Index is immutable after construction)

skills/skills.go:
- Remove Index.All() (never called)
- Remove Index.Reload() (never called)

virtfs/decl.go:
- Remove RemoveNode() NodeOption (never used)
- Remove RenameNode() NodeOption (never used)
- Remove Alias() NodeOption (aliases set directly on struct)

virtfs/tree.go:
- Remove Tree.Data field (never used)
- Remove Tree.Mount() (never called in production)
- Remove Tree.Child() (never called in production)
- Remove Tree.Children() (never called in production)
- Replace indexOf() with strings.IndexByte

Tests updated to directly manipulate internal children map where needed.
2026-08-21 17:20:13 +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 virtfs: rename Request to Rdwr 2026-08-14 14:42:19 +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