ollie/fsedsl
Ollie Agent fbe8059cf5 maintainability: docs, contracts, structural cleanup
- doc/boot-sequence.md: init chain from main → fs.NewRoot → session.Init
- Event protocol: comprehensive table on Event struct (all roles, semantics)
- RPC server moved from cmd/ollie-remote into toolsrv/rpcserver.go
  (binary is now a 93-line thin wrapper)
- SetSessionEnv folded into NewAgent (no post-construction wiring needed)
- AgentCfg → AgentParams (distinguish from AgentConfig JSON schema)
- ToolResult/ToolResultContent shared types in toolsrv/rpcwire.go
- toolsrv/shell.go → toolsrv/exec.go (name matches purpose)
- Resume unified: single path, always through Keeper
- AGENTS.md: fix fs/builder.go → fsedsl/builder.go, handlers_*.go
- fsedsl/Tree: document dual-mode (EDSL lazy vs Manual Mount)
- Truncation stack documented at defaultToolResultMaxBytes
- Integration test rewritten to use srv.ServeRPC directly
2026-08-09 21:45:56 +02:00
..
LICENSE.md Extract EDSL to fsedsl/ library 2026-08-04 12:02:32 +02:00
README.md fsedsl: simplify README usage section 2026-08-04 12:48:39 +02:00
builder.go refactor: audit fsedsl exports — remove dead code, unexport internals 2026-08-09 12:34:40 +02:00
decl.go refactor: audit fsedsl exports — remove dead code, unexport internals 2026-08-09 12:34:40 +02:00
decl_test.go refactor: audit fsedsl exports — remove dead code, unexport internals 2026-08-09 12:34:40 +02:00
file.go refactor: audit fsedsl exports — remove dead code, unexport internals 2026-08-09 12:34:40 +02:00
go.mod Extract EDSL to fsedsl/ library 2026-08-04 12:02:32 +02:00
help.go Extract EDSL to fsedsl/ library 2026-08-04 12:02:32 +02:00
tree.go maintainability: docs, contracts, structural cleanup 2026-08-09 21:45:56 +02:00
tree_test.go refactor: audit fsedsl exports — remove dead code, unexport internals 2026-08-09 12:34:40 +02:00

README.md

fsedsl

A Go library providing an embedded domain-specific language (EDSL) for declaring synthetic filesystems. Define your namespace structure statically while populating it dynamically at runtime.

The library produces a generic Tree that can be used with any filesystem protocol (9P, FUSE, etc.) or as an in-memory filesystem for testing.

Features

  • Declarative filesystem specification: Define your namespace structure with DirNode, FileNode, and Each constructors
  • Generic context propagation: Type-safe context passing through the tree via Binding.Applier
  • Dynamic bindings: Each nodes iterate over runtime-determined collections
  • Auto-generated help: GenerateHelp produces documentation from Doc() annotations
  • Standard filesystem semantics: Implements read, write, stat, list, create, delete, rename

Installation

The library is currently embedded in the ollie repository at fsedsl/.

To use it in your project, add a replace directive to your go.mod:

# Clone or copy the fsedsl directory to your project, then:
go mod edit -replace=git.lneely.de/lkn/fsedsl=./fsedsl

Or reference it directly from the ollie repo:

go mod edit -replace=git.lneely.de/lkn/fsedsl=git.lneely.de/lkn/ollie/fsedsl@main

Quick Start

package main

import (
    "fmt"
    "git.lneely.de/lkn/fsedsl"
)

type Ctx struct {
    User *User
}

type User struct {
    ID   string
    Name string
}

var users = []*User{
    {ID: "1", Name: "alice"},
    {ID: "2", Name: "bob"},
}

func main() {
    // Declare the filesystem spec
    spec := fsedsl.DirNode[Ctx]("/",
        fsedsl.FileNode[Ctx]("version", 0444,
            fsedsl.Doc[Ctx]("API version"),
            fsedsl.Read(func(ctx Ctx) ([]byte, error) {
                return []byte("1.0\n"), nil
            }),
        ),
        fsedsl.Each[Ctx]("{user}", func(ctx Ctx) ([]fsedsl.Binding[Ctx], error) {
            var out []fsedsl.Binding[Ctx]
            for _, u := range users {
                user := u // capture
                out = append(out, fsedsl.Binding[Ctx]{
                    Name:    user.Name,
                    Aliases: []string{user.ID},
                    Applier: func(c Ctx) Ctx {
                        c.User = user
                        return c
                    },
                })
            }
            return out, nil
        },
            fsedsl.FileNode[Ctx]("id", 0444,
                fsedsl.Read(func(ctx Ctx) ([]byte, error) {
                    return []byte(ctx.User.ID + "\n"), nil
                }),
            ),
            fsedsl.FileNode[Ctx]("name", 0444,
                fsedsl.Read(func(ctx Ctx) ([]byte, error) {
                    return []byte(ctx.User.Name + "\n"), nil
                }),
            ),
        ),
    )

    // Build the tree
    tree := fsedsl.BuildTree(spec, Ctx{})

    // Use the tree
    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)
}

Usage

Define your context type, then create type and function aliases that bake it in:

package fs

import "git.lneely.de/lkn/fsedsl"

// Ctx carries runtime dependencies for handler functions.
type Ctx struct {
    DB   *Database
    User *User
}

// Type aliases for external consumers
type (
    FsNodeDecl = fsedsl.FsNodeDecl[Ctx]
    Binding    = fsedsl.Binding[Ctx]
    Tree       = fsedsl.Tree
)

// Function aliases for clean spec definitions
var (
    DirNode  = fsedsl.DirNode[Ctx]
    FileNode = fsedsl.FileNode[Ctx]
    Each     = fsedsl.Each[Ctx]
    Doc      = fsedsl.Doc[Ctx]
    Read     = fsedsl.Read[Ctx]
    Write    = fsedsl.Write[Ctx]
    // ... add others as needed
)

var Spec = DirNode("/",
    FileNode("version", 0444,
        Doc("API version"),
        Read(func(ctx Ctx) ([]byte, error) {
            return []byte("1.0.0\n"), nil
        }),
    ),
    Each("{user}", userBindings,
        FileNode("name", 0444,
            Read(func(ctx Ctx) ([]byte, error) {
                return []byte(ctx.User.Name + "\n"), nil
            }),
        ),
    ),
)

func userBindings(ctx Ctx) ([]Binding, error) {
    users, _ := ctx.DB.ListUsers()
    var out []Binding
    for _, u := range users {
        user := u
        out = append(out, Binding{
            Name:    user.Username,
            Aliases: []string{user.ID},
            Applier: func(c Ctx) Ctx { c.User = user; return c },
        })
    }
    return out, nil
}

Build and use:

tree := fsedsl.BuildTree(fs.Spec, fs.Ctx{DB: db})
entries, _ := tree.List()
f, _ := tree.Open("alice/name")
data, _ := f.Read()

API Reference

Constructors

Function Description
DirNode[Ctx](name, children/options...) Static directory
FileNode[Ctx](name, mode, options...) File with handlers
Each[Ctx](pattern, bindingsFn, children/options...) Dynamic directory template

Options

Option Description
Read(fn) Read handler: func(Ctx) ([]byte, error)
Write(fn) Write handler: func(Ctx, []byte) error
Stream(fn) Streaming read (blocks indefinitely)
BlockOnce(fn) Blocking read (returns once)
Request(fn) Request/response pattern (rdwr)
Doc(desc) Human-readable description
UID(uid) Set owner
GID(gid) Set group
Alias(names...) Alternate lookup names
StatOverride(fn) Custom stat handler
CreateFile(fn) File creation handler (directories)
RemoveNode(fn) Removal handler

Binding

fsedsl.Binding[Ctx]{
    Name:    "filename",           // Primary name
    Aliases: []string{"alt-name"}, // Alternate lookup names
    UID:     "owner",              // Owner (optional)
    GID:     "group",              // Group (optional)
    Applier: func(c Ctx) Ctx {     // Context mutation
        c.MyField = myValue
        return c
    },
    Remove: func() { ... },                    // Removal callback (optional)
    Rename: func(newName string) error { ... }, // Rename callback (optional)
}

Tree Operations

tree := fsedsl.BuildTree(spec, rootCtx)

tree.List()                    // List entries
tree.Stat(name)                // Get file info
tree.Open(name)                // Open file for read/write
tree.Create(name)              // Create file
tree.Delete(name)              // Delete file
tree.Rename(old, new)          // Rename file
tree.Readdir(path)             // List subdirectory

Help Generation

help := fsedsl.GenerateHelp(spec)
// Returns formatted documentation from Doc() annotations

License

GPLv3