diff --git a/cmd/ww/main.go b/cmd/ww/main.go index f495992..d70f3b0 100644 --- a/cmd/ww/main.go +++ b/cmd/ww/main.go @@ -15,6 +15,7 @@ import ( "github.com/wetware/go/cmd/ww/idgen" importcmd "github.com/wetware/go/cmd/ww/import" "github.com/wetware/go/cmd/ww/run" + "github.com/wetware/go/cmd/ww/shell" ) func main() { @@ -48,6 +49,7 @@ func main() { Commands: []*cli.Command{ idgen.Command(), run.Command(), + shell.Command(), export.Command(), importcmd.Command(), }, diff --git a/cmd/ww/run/run.go b/cmd/ww/run/run.go index 60ebbbe..b120fa5 100644 --- a/cmd/ww/run/run.go +++ b/cmd/ww/run/run.go @@ -3,11 +3,13 @@ package run import ( "context" "fmt" + "log/slog" "os" os_exec "os/exec" "capnproto.org/go/capnp/v3/rpc" "github.com/libp2p/go-libp2p/core/network" + "github.com/tetratelabs/wazero" "github.com/urfave/cli/v2" "github.com/wetware/go/cmd/internal/flags" "github.com/wetware/go/cmd/ww/args" @@ -45,6 +47,11 @@ func Command() *cli.Command { Category: "FILE DESCRIPTORS", Usage: "map existing parent fd to name (e.g., db=3). Use --with-fd multiple times for multiple fds.", }, + &cli.BoolFlag{ + Name: "wasm-debug", + Usage: "enable wasm debug info", + EnvVars: []string{"WW_WASM_DEBUG"}, + }, }, flags.CapabilityFlags()...), // Environment hooks. @@ -63,16 +70,15 @@ func Command() *cli.Command { } func Main(c *cli.Context) error { - ctx, cancel := context.WithCancel(c.Context) defer cancel() // Set up the RPC socket for the cell //// - host, guest, err := system.SocketConfig[system.Importer]{ - BootstrapClient: system.Importer_ServerToClient(&system.Membrane{ - // someServiceToken: someClient, - }), + host, guest, err := system.SocketConfig[system.Terminal]{ + BootstrapClient: system.TerminalConfig{ + Exec: exec(c), + }.New(), }.New(ctx) if err != nil { return err @@ -148,3 +154,19 @@ func Main(c *cli.Context) error { return cmd.Wait() } + +func exec(c *cli.Context) system.Executor { + if !c.Bool("with-exec") && !c.Bool("with-all") { + slog.DebugContext(c.Context, "returning null executor client", + "--with-exec", c.Bool("with-exec"), + "--with-all", c.Bool("with-all")) + return system.Executor{} // null client + } + + return system.ExecutorConfig{ + Host: env.Host, + Runtime: wazero.NewRuntimeConfig(). + WithDebugInfoEnabled(c.Bool("wasm-debug")). + WithCloseOnContextDone(true), + }.New(c.Context) +} diff --git a/cmd/ww/shell/README.md b/cmd/ww/shell/README.md new file mode 100644 index 0000000..ddf3cbb --- /dev/null +++ b/cmd/ww/shell/README.md @@ -0,0 +1,252 @@ +# Wetware Shell + +Production-grade REPL shell built with the Wetware framework and Slurp LISP toolkit. The shell operates in two modes: host mode (spawns a guest process) and guest mode (runs as a cell process). + +## Features + +- **Dual Mode Operation**: Host mode spawns guest processes, guest mode runs as cell processes +- **REPL**: Built using `github.com/spy16/slurp` for LISP interpretation +- **Readline**: Uses `github.com/chzyer/readline` for terminal experience +- **Wetware Integration**: Runs within Wetware cell environment with system capabilities +- **CLI Integration**: Full urfave/cli integration with configurable options +- **Function Registry**: Extensible set of built-in functions + +## Available Functions + +### Basic Values +- `nil` - null value +- `true` / `false` - boolean values +- `version` - wetware version string + +### Arithmetic +- `(+ a b ...)` - sum of numbers +- `(* a b ...)` - product of numbers +- `(/ a b)` - division (returns float) + +### Comparison +- `(= a b)` - equality comparison +- `(> a b)` - greater than +- `(< a b)` - less than + +### Utilities +- `help` - display help message +- `println expr` - print expression with newline +- `print expr` - print expression without newline +- `(send "peer-addr-or-id" "proc-id" data)` - send data to a peer process + +### IPFS Functions (requires `--with-ipfs` or `--with-all`) +- `(ipfs :cat /ipfs/QmHash/...)` - read IPFS file content as bytes +- `(ipfs :get /ipfs/QmHash/...)` - get IPFS node as file/directory object +- IPFS Path Syntax: `/ipfs/QmHash/...` and `/ipns/domain/...` are automatically parsed + +### Execution Functions (requires `--with-exec` or `--with-all`) +- `(exec /ipfs/QmHash/bytecode :timeout 15s)` - execute bytecode from IPFS path + +## Usage + +The shell is integrated into the main `ww` command: + +```bash +# Interactive shell (host mode - spawns guest process) +ww shell + +# Execute single command +ww shell -c "(+ 1 2 3)" + +# Customize prompt and history +ww shell --prompt "my-shell> " --history-file ~/.ww_history + +# Disable banner +ww shell --no-banner + +# Environment variable configuration +WW_SHELL_PROMPT="ww> " WW_SHELL_HISTORY="/tmp/ww.tmp" ww shell +``` + +### Command Line Options + +- `-c, --command`: Execute a single command and exit +- `--history-file`: Path to readline history file (default: `/tmp/ww-shell.tmp`) +- `--prompt`: Shell prompt string (default: `"ww> "`) +- `--no-banner`: Disable welcome banner +- `--ipfs`: IPFS API endpoint (default: `/dns4/localhost/tcp/5001/http`) + +### Capability Flags + +- `--with-ipfs`: Grant IPFS capability (enables `ipfs` functions and path syntax) +- `--with-exec`: Grant process execution capability (enables `exec` function) +- `--with-console`: Grant console output capability +- `--with-all`: Grant all capabilities (equivalent to all above flags) + +### Environment Variables + +- `WW_SHELL_HISTORY`: Override history file path +- `WW_SHELL_PROMPT`: Override prompt string +- `WW_SHELL_NO_BANNER`: Disable banner (set to any value) +- `WW_IPFS`: Override IPFS API endpoint +- `WW_WITH_IPFS`: Enable IPFS capability +- `WW_WITH_EXEC`: Enable execution capability +- `WW_WITH_CONSOLE`: Enable console capability +- `WW_WITH_ALL`: Enable all capabilities + +## Example Session + +### Basic Usage +``` +Welcome to Wetware Shell! Type 'help' for available commands. +ww> help +Wetware Shell - Available commands: + help - Show this help message + version - Show wetware version + (+ a b ...) - Sum numbers + (* a b ...) - Multiply numbers + (= a b) - Compare equality + (> a b) - Greater than + (< a b) - Less than + (println expr) - Print expression with newline + (print expr) - Print expression without newline + (send "peer-addr-or-id" "proc-id" data) - Send data to a peer process + +ww> (+ 1 2 3 4) +10 +ww> (* 2 3 4) +24 +ww> (> 10 5) +true +ww> (println "Hello, Wetware!") +Hello, Wetware! +ww> +``` + +### With IPFS Capability +```bash +# Start shell with IPFS capability +ww shell --with-ipfs +``` + +``` +ww> /ipfs/QmHash/example.txt +Path: /ipfs/QmHash/example.txt +ww> (ipfs :cat /ipfs/QmHash/example.txt) +# Returns file content as bytes +ww> (ipfs :get /ipfs/QmHash/example.txt) + +ww> (ipfs :get /ipfs/QmHash/example.txt :read-string) +"Hello from IPFS!" +ww> +``` + +### With Execution Capability +```bash +# Start shell with execution capability +ww shell --with-exec +``` + +``` +ww> (exec /ipfs/QmHash/bytecode.wasm :timeout 30s) +/protocol/identifier +ww> +``` + +### With All Capabilities +```bash +# Start shell with all capabilities +ww shell --with-all +``` + +``` +ww> help +# Shows all available functions including ipfs and exec +ww> (send "12D3KooW..." "my-process" "Hello, peer!") +# Sends data to peer process +ww> +``` + +## Architecture + +The shell operates in two distinct modes with capability-based function loading: + +### Host Mode +- **Detection**: Runs when `WW_CELL` environment variable is not set +- **Process Spawning**: Uses `ww run -env WW_CELL=true ww -- shell` to spawn guest process +- **Flag Forwarding**: Passes all shell-specific flags and capability flags to the guest process +- **Stdio Forwarding**: Forwards stdin, stdout, and stderr to guest process +- **IPFS Environment**: Initializes IPFS environment before spawning guest process + +### Guest Mode (Cell Process) +- **Detection**: Runs when `WW_CELL=true` environment variable is set +- **RPC Connection**: Uses file descriptor 3 for RPC communication with host +- **Wetware Integration**: Connects to Wetware cell system for capabilities +- **Slurp Interpreter**: Provides LISP evaluation engine with IPFS path support +- **Custom REPL**: Production-grade read-eval-print loop with error handling +- **Readline Integration**: Enhanced terminal input with history and completion +- **Function Registry**: Capability-based function loading (base + session-specific) +- **IPFS Integration**: Direct IPFS API access when `--with-ipfs` is enabled +- **Execution Support**: Process execution capability when `--with-exec` is enabled + +### Capability System +- **Base Globals**: Always available (arithmetic, comparison, utilities, send) +- **IPFS Capability**: Loads `ipfs` object and enables IPFS path syntax when `--with-ipfs` is set +- **Execution Capability**: Loads `exec` function when `--with-exec` is set +- **Console Capability**: Enables console output when `--with-console` is set +- **All Capabilities**: `--with-all` enables all capabilities at once + +### Process Flow +1. `ww shell` (host) → `ww run` (process isolation) → `ww shell` (guest/cell) +2. Host mode initializes IPFS environment and delegates to `ww run` for proper file descriptor management +3. Guest mode runs as a cell process with capability-based function loading +4. Functions are loaded based on capability flags passed from host mode + +## Extending + +### Adding Base Functions +To add functions that are always available, modify the `globals` map in `globals.go`: + +```go +"my-function": slurp.Func("my-function", func(args ...core.Any) { + // Implementation + return result +}), +``` + +### Adding Capability-Based Functions +To add functions that require specific capabilities, modify the `getBaseGlobals()` function in `shell.go`: + +```go +// Add IPFS capability function +if c.Bool("with-ipfs") || c.Bool("with-all") { + gs["my-ipfs-function"] = &MyIPFSFunction{CoreAPI: env.IPFS} +} + +// Add execution capability function +if c.Bool("with-exec") || c.Bool("with-all") { + gs["my-exec-function"] = &MyExecFunction{Session: session} +} +``` + +### Adding CLI Flags +To add new CLI flags, modify the `Command()` function in `shell.go`: + +```go +&cli.StringFlag{ + Name: "my-flag", + Usage: "description of my flag", + EnvVars: []string{"WW_MY_FLAG"}, +}, +``` + +### Adding New Capabilities +To add a new capability system: + +1. Add the capability flag to `flags.go` +2. Add capability check in `getBaseGlobals()` or `NewSessionGlobals()` +3. Implement capability-specific functions +4. Update help message and documentation + +## Dependencies + +- `github.com/spy16/slurp` - LISP toolkit +- `github.com/chzyer/readline` - Terminal readline support +- `github.com/urfave/cli/v2` - CLI framework +- `capnproto.org/go/capnp/v3` - Cap'n Proto RPC +- `github.com/wetware/go` - Wetware framework diff --git a/cmd/ww/shell/executor.go b/cmd/ww/shell/executor.go new file mode 100644 index 0000000..cc578aa --- /dev/null +++ b/cmd/ww/shell/executor.go @@ -0,0 +1,179 @@ +package shell + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "strings" + "time" + + "github.com/ipfs/boxo/files" + "github.com/ipfs/boxo/path" + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + ma "github.com/multiformats/go-multiaddr" + "github.com/spy16/slurp/builtin" + "github.com/spy16/slurp/core" + "github.com/wetware/go/system" +) + +var _ core.Invokable = (*Exec)(nil) + +type Exec struct { + Session interface { + Exec() system.Executor + } +} + +// (exec +// :timeout 15s) +func (e Exec) Invoke(args ...core.Any) (core.Any, error) { + if len(args) == 0 { + return nil, fmt.Errorf("exec requires at least one argument (bytecode or reader)") + } + + p, ok := args[0].(path.Path) + if !ok { + return nil, fmt.Errorf("exec expects a path, got %T", args[0]) + } + + // Process remaining args as key-value pairs + opts := make(map[builtin.Keyword]core.Any) + for i := 1; i < len(args); i += 2 { + key, ok := args[i].(builtin.Keyword) + if !ok { + return nil, fmt.Errorf("option key must be a keyword, got %T", args[i]) + } + + if i+1 >= len(args) { + return nil, fmt.Errorf("missing value for option %s", key) + } + + opts[key] = args[i+1] + } + ctx, cancel := e.NewContext(opts) + defer cancel() + + n, err := env.IPFS.Unixfs().Get(ctx, p) + if err != nil { + return nil, fmt.Errorf("failed to resolve node %v: %w", p, err) + } + + switch node := n.(type) { + case files.File: + bytecode, err := io.ReadAll(node) + if err != nil { + return nil, fmt.Errorf("failed to read bytecode: %w", err) + } + + protocol, err := e.ExecBytes(ctx, bytecode) + if err != nil { + return nil, fmt.Errorf("failed to execute bytecode: %w", err) + } + return protocol, nil + + case files.Directory: + return nil, errors.New("TODO: directory support") + default: + return nil, fmt.Errorf("unexpected node type: %T", node) + } +} + +func (e Exec) ExecBytes(ctx context.Context, bytecode []byte) (protocol.ID, error) { + f, release := e.Session.Exec().Exec(ctx, func(p system.Executor_exec_Params) error { + return p.SetBytecode(bytecode) + }) + defer release() + + // Wait for the protocol setup to complete + result, err := f.Struct() + if err != nil { + return "", err + } + + proto, err := result.Protocol() + return protocol.ID(proto), err +} + +func (e Exec) NewContext(opts map[builtin.Keyword]core.Any) (context.Context, context.CancelFunc) { + // TODO: add support for parsing durations like 15s, 15m, 15h, 15d + // if timeout, ok := opts["timeout"].(time.Duration); ok { + // return context.WithTimeout(context.Background(), timeout) + // } + + return context.WithTimeout(context.Background(), time.Second*15) +} + +// SendToPeer sends data to a specific peer and process +func SendToPeer(peerAddr, procIdStr string, data interface{}) error { + ctx := context.TODO() + + // Create a new libp2p host for this connection + host, err := libp2p.New() + if err != nil { + return fmt.Errorf("failed to create libp2p host: %w", err) + } + defer host.Close() + + var peerInfo *peer.AddrInfo + + // Try to parse as peer ID first + peerId, err := peer.Decode(peerAddr) + if err == nil { + // Successfully parsed as peer ID + peerInfo = &peer.AddrInfo{ + ID: peerId, + // Note: In a real implementation, you'd need peer discovery + // or provide addresses as additional parameters + } + } else { + // Fall back to treating as multiaddr + addr, err := ma.NewMultiaddr(peerAddr) + if err != nil { + return fmt.Errorf("invalid peer address or ID: %w", err) + } + peerInfo, err = peer.AddrInfoFromP2pAddr(addr) + if err != nil { + return fmt.Errorf("failed to parse peer info from multiaddr: %w", err) + } + } + + // Connect to the peer + if err := host.Connect(ctx, *peerInfo); err != nil { + return fmt.Errorf("failed to connect to peer: %w", err) + } + + // Create protocol ID from process ID + protocolID := protocol.ID("/ww/0.1.0/" + procIdStr) + + // Open stream to the peer + stream, err := host.NewStream(ctx, peerInfo.ID, protocolID) + if err != nil { + return fmt.Errorf("failed to open stream: %w", err) + } + defer stream.Close() + + // Convert data to io.Reader based on type + var reader io.Reader + switch v := data.(type) { + case io.Reader: + reader = v + case []byte: + reader = bytes.NewReader(v) + case string: + reader = strings.NewReader(v) + default: + return fmt.Errorf("unsupported data type: %T, expected io.Reader, []byte, or string", data) + } + + // Send the data atomically + _, err = io.Copy(stream, reader) + if err != nil { + return fmt.Errorf("failed to send data: %w", err) + } + + return nil +} diff --git a/cmd/ww/shell/globals.go b/cmd/ww/shell/globals.go new file mode 100644 index 0000000..c5051ca --- /dev/null +++ b/cmd/ww/shell/globals.go @@ -0,0 +1,78 @@ +package shell + +import ( + "fmt" + + "github.com/spy16/slurp" + "github.com/spy16/slurp/builtin" + "github.com/spy16/slurp/core" +) + +const helpMessage = `Wetware Shell - Available commands: +help - Show this help message +version - Show wetware version +(+ a b ...) - Sum numbers +(* a b ...) - Multiply numbers +(= a b) - Compare equality +(> a b) - Greater than +(< a b) - Less than +(println expr) - Print expression with newline +(print expr) - Print expression without newline +(send "peer-addr-or-id" "proc-id" data) - Send data to a peer process (data: string, []byte, or io.Reader) +(import "module") - Import a module (stubbed) + +IPFS Path Syntax: +/ipfs/QmHash/... - Direct IPFS path +/ipns/domain/... - IPNS path` + +var globals = map[string]core.Any{ + // Basic values + "nil": builtin.Nil{}, + "true": builtin.Bool(true), + "false": builtin.Bool(false), + "version": builtin.String("wetware-0.1.0"), + + // Basic operations + "=": slurp.Func("=", core.Eq), + "+": slurp.Func("sum", func(a ...int) int { + sum := 0 + for _, item := range a { + sum += item + } + return sum + }), + ">": slurp.Func(">", func(a, b builtin.Int64) bool { + return a > b + }), + "<": slurp.Func("<", func(a, b builtin.Int64) bool { + return a < b + }), + "*": slurp.Func("*", func(a ...int) int { + product := 1 + for _, item := range a { + product *= item + } + return product + }), + "/": slurp.Func("/", func(a, b builtin.Int64) float64 { + return float64(a) / float64(b) + }), + + // Wetware-specific functions + "help": slurp.Func("help", func() string { + return helpMessage + }), + "println": slurp.Func("println", func(args ...core.Any) { + for _, arg := range args { + fmt.Println(arg) + } + }), + "print": slurp.Func("print", func(args ...core.Any) { + for _, arg := range args { + fmt.Print(arg) + } + }), + "send": slurp.Func("send", func(peerAddr, procId string, data interface{}) error { + return SendToPeer(peerAddr, procId, data) + }), +} diff --git a/cmd/ww/shell/globals_test.go b/cmd/ww/shell/globals_test.go new file mode 100644 index 0000000..3b0b5dc --- /dev/null +++ b/cmd/ww/shell/globals_test.go @@ -0,0 +1,152 @@ +package shell + +import ( + "context" + "flag" + "testing" + + "github.com/spy16/slurp/builtin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" +) + +func TestGetBaseGlobals(t *testing.T) { + t.Parallel() + + // Create a mock CLI context + app := &cli.App{} + app.Flags = []cli.Flag{} + flagSet := &flag.FlagSet{} + flagSet.Bool("with-console", true, "Enable console capability") + flagSet.Bool("with-ipfs", false, "Enable IPFS capability") // Set to false for tests + flagSet.Bool("with-exec", true, "Enable exec capability") + flagSet.Bool("with-all", false, "Enable all capabilities") + c := cli.NewContext(app, flagSet, nil) + c.Context = context.Background() + baseGlobals := getBaseGlobals(c) + + // Test that all expected base globals are present + expectedGlobals := []string{ + "nil", "true", "false", "version", + "=", "+", ">", "<", "*", "/", + "help", "println", "print", + } + + for _, expected := range expectedGlobals { + assert.Contains(t, baseGlobals, expected, "Base globals should contain %s", expected) + } + + // Test specific values + assert.Equal(t, builtin.Nil{}, baseGlobals["nil"]) + assert.Equal(t, builtin.Bool(true), baseGlobals["true"]) + assert.Equal(t, builtin.Bool(false), baseGlobals["false"]) + assert.Equal(t, builtin.String("wetware-0.1.0"), baseGlobals["version"]) +} + +func TestGlobalsFromGlobalsGo(t *testing.T) { + t.Parallel() + + // Test that the globals map from globals.go has expected content + expectedGlobals := []string{ + "nil", "true", "false", "version", + "=", "+", ">", "<", "*", "/", + "help", "println", "print", + } + + for _, expected := range expectedGlobals { + assert.Contains(t, globals, expected, "globals map should contain %s", expected) + } + + // Test specific values + assert.Equal(t, builtin.Nil{}, globals["nil"]) + assert.Equal(t, builtin.Bool(true), globals["true"]) + assert.Equal(t, builtin.Bool(false), globals["false"]) + assert.Equal(t, builtin.String("wetware-0.1.0"), globals["version"]) +} + +func TestArithmeticFunctions(t *testing.T) { + t.Parallel() + + // Test that arithmetic functions are present + addFunc, exists := globals["+"] + require.True(t, exists, "Addition function should be present") + require.NotNil(t, addFunc, "Addition function should not be nil") + + mulFunc, exists := globals["*"] + require.True(t, exists, "Multiplication function should be present") + require.NotNil(t, mulFunc, "Multiplication function should not be nil") + + divFunc, exists := globals["/"] + require.True(t, exists, "Division function should be present") + require.NotNil(t, divFunc, "Division function should not be nil") +} + +func TestComparisonFunctions(t *testing.T) { + t.Parallel() + + // Test that comparison functions are present + gtFunc, exists := globals[">"] + require.True(t, exists, "Greater than function should be present") + require.NotNil(t, gtFunc, "Greater than function should not be nil") + + ltFunc, exists := globals["<"] + require.True(t, exists, "Less than function should be present") + require.NotNil(t, ltFunc, "Less than function should not be nil") +} + +func TestEqualityFunction(t *testing.T) { + t.Parallel() + + // Test that equality function is present + eqFunc, exists := globals["="] + require.True(t, exists, "Equality function should be present") + require.NotNil(t, eqFunc, "Equality function should not be nil") +} + +func TestHelpFunction(t *testing.T) { + t.Parallel() + + // Test that help function is present + helpFunc, exists := globals["help"] + require.True(t, exists, "Help function should be present") + require.NotNil(t, helpFunc, "Help function should not be nil") +} + +func TestPrintFunctions(t *testing.T) { + t.Parallel() + + // Test that println function exists + printlnFunc, exists := globals["println"] + assert.True(t, exists, "Println function should exist") + assert.NotNil(t, printlnFunc, "Println function should not be nil") + + // Test that print function exists + printFunc, exists := globals["print"] + assert.True(t, exists, "Print function should exist") + assert.NotNil(t, printFunc, "Print function should not be nil") +} + +func TestGlobalsConsistency(t *testing.T) { + t.Parallel() + + // Test that getBaseGlobals returns a copy, not the original + app := &cli.App{} + app.Flags = []cli.Flag{} + flagSet := &flag.FlagSet{} + flagSet.Bool("with-ipfs", false, "Enable IPFS capability") // Set to false for tests + flagSet.Bool("with-exec", true, "Enable exec capability") + flagSet.Bool("with-console", true, "Enable console capability") + flagSet.Bool("with-all", false, "Enable all capabilities") + c := cli.NewContext(app, flagSet, nil) + c.Context = context.Background() + baseGlobals1 := getBaseGlobals(c) + baseGlobals2 := getBaseGlobals(c) + + // They should have the same content initially + assert.Equal(t, baseGlobals1, baseGlobals2) + + // Modifying one shouldn't affect the other + baseGlobals1["test"] = "modified" + assert.NotContains(t, baseGlobals2, "test") +} diff --git a/cmd/ww/shell/ipfs.go b/cmd/ww/shell/ipfs.go new file mode 100644 index 0000000..622ee06 --- /dev/null +++ b/cmd/ww/shell/ipfs.go @@ -0,0 +1,265 @@ +package shell + +import ( + "context" + "fmt" + "io" + "strings" + "time" + + "github.com/ipfs/boxo/files" + "github.com/ipfs/boxo/path" + iface "github.com/ipfs/kubo/core/coreiface" + "github.com/spy16/slurp/builtin" + "github.com/spy16/slurp/core" +) + +var _ core.Invokable = (*IPFS)(nil) + +type IPFS struct { + iface.CoreAPI +} + +// IPFS methods: (ipfs :cat /ipfs/Qm...) or (ipfs :get /ipfs/Qm...) +func (i IPFS) Invoke(args ...core.Any) (core.Any, error) { + if len(args) < 2 { + return nil, fmt.Errorf("ipfs requires at least 2 arguments: (ipfs :method path)") + } + + // First argument should be a keyword (:cat or :get) + method, ok := args[0].(builtin.Keyword) + if !ok { + return nil, fmt.Errorf("ipfs method must be a keyword, got %T", args[0]) + } + + src, ok := args[1].(path.Path) + if !ok { + return nil, fmt.Errorf("ipfs path must be a Path object, got %T", args[1]) + } + + // Create context with timeout + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + switch method { + case "cat": + return i.Cat(ctx, src) + case "get": + return i.Get(ctx, src) + default: + return nil, fmt.Errorf("unknown ipfs method: %s (supported: :cat, :get)", method) + } +} + +// Cat returns the content of an IPFS file as []byte +func (i IPFS) Cat(ctx context.Context, p path.Path) (core.Any, error) { + if i.CoreAPI == nil || i.CoreAPI.Unixfs() == nil { + return nil, fmt.Errorf("IPFS client not initialized") + } + + // Get the node from IPFS + node, err := i.Unixfs().Get(ctx, p) + if err != nil { + return nil, fmt.Errorf("failed to get IPFS path: %w", err) + } + + // Handle different node types + switch node := node.(type) { + case files.File: + // For files, read the content into a byte slice + content, err := io.ReadAll(node) + if err != nil { + return nil, fmt.Errorf("failed to read file content: %w", err) + } + return content, nil + case files.Directory: + return nil, fmt.Errorf("path is a directory, use :get instead of :cat") + default: + return nil, fmt.Errorf("unexpected node type: %T", node) + } +} + +// Get returns the IPFS node as a file-like object (io.Reader) that can be used with eval +func (i IPFS) Get(ctx context.Context, p path.Path) (core.Any, error) { + if i.CoreAPI == nil || i.CoreAPI.Unixfs() == nil { + return nil, fmt.Errorf("IPFS client not initialized") + } + + // Get the node from IPFS + node, err := i.Unixfs().Get(ctx, p) + if err != nil { + return nil, fmt.Errorf("failed to get IPFS path: %w", err) + } + + switch node := node.(type) { + case files.File: + return File{File: node}, nil + case files.Directory: + return Directory{Directory: node}, nil + default: + return Node{Node: node}, nil + } +} + +type Node struct { + files.Node +} + +func (n Node) String() string { + return fmt.Sprintf("", n.Type()) +} + +func (n Node) Type() string { + switch n.Node.(type) { + case files.File: + return "file" + case files.Directory: + return "directory" + default: + return "unknown" + } +} + +// Implement core.Invokable for Node +func (n Node) Invoke(args ...core.Any) (core.Any, error) { + if len(args) == 0 { + return n.String(), nil + } + + method, ok := args[0].(builtin.Keyword) + if !ok { + return nil, fmt.Errorf("node method must be a keyword, got %T", args[0]) + } + + switch method { + case "type": + return builtin.String(n.Type()), nil + case "size": + size, err := n.Size() + if err != nil { + return nil, fmt.Errorf("failed to get size: %w", err) + } + return builtin.Int64(size), nil + case "is-file": + return builtin.Bool(n.Type() == "file"), nil + case "is-directory": + return builtin.Bool(n.Type() == "directory"), nil + default: + return nil, fmt.Errorf("unknown node method: %s", method) + } +} + +type File struct { + files.File +} + +func (f File) String() string { + size, err := f.Size() + if err != nil { + return "" + } + return fmt.Sprintf("", size) +} + +// Implement core.Invokable for File +func (f File) Invoke(args ...core.Any) (core.Any, error) { + if len(args) == 0 { + return f.String(), nil + } + + method, ok := args[0].(builtin.Keyword) + if !ok { + return nil, fmt.Errorf("file method must be a keyword, got %T", args[0]) + } + + switch method { + case "read": + // Read all content + content, err := io.ReadAll(f.File) + if err != nil { + return nil, fmt.Errorf("failed to read file: %w", err) + } + return content, nil + case "read-string": + // Read content as string + content, err := io.ReadAll(f.File) + if err != nil { + return nil, fmt.Errorf("failed to read file: %w", err) + } + return builtin.String(string(content)), nil + case "size": + size, err := f.Size() + if err != nil { + return nil, fmt.Errorf("failed to get size: %w", err) + } + return builtin.Int64(size), nil + case "type": + return builtin.String("file"), nil + default: + return nil, fmt.Errorf("unknown file method: %s", method) + } +} + +type Directory struct { + files.Directory +} + +func (d Directory) String() string { + // Try to get directory entries for a more informative string + entries := make([]string, 0) + it := d.Entries() + for it.Next() { + entries = append(entries, it.Name()) + } + + if len(entries) == 0 { + return "" + } + + if len(entries) <= 3 { + return fmt.Sprintf("", strings.Join(entries, ", ")) + } + + return fmt.Sprintf("", strings.Join(entries[:3], ", "), len(entries)-3) +} + +// Implement core.Invokable for Directory +func (d Directory) Invoke(args ...core.Any) (core.Any, error) { + if len(args) == 0 { + return d.String(), nil + } + + method, ok := args[0].(builtin.Keyword) + if !ok { + return nil, fmt.Errorf("directory method must be a keyword, got %T", args[0]) + } + + switch method { + case "list": + // List directory entries + entries := make([]core.Any, 0) + it := d.Entries() + for it.Next() { + entries = append(entries, builtin.String(it.Name())) + } + return builtin.NewList(entries...), nil + case "entries": + // Return directory entries as a list of strings + entries := make([]core.Any, 0) + it := d.Entries() + for it.Next() { + entries = append(entries, builtin.String(it.Name())) + } + return builtin.NewList(entries...), nil + case "size": + size, err := d.Size() + if err != nil { + return nil, fmt.Errorf("failed to get size: %w", err) + } + return builtin.Int64(size), nil + case "type": + return builtin.String("directory"), nil + default: + return nil, fmt.Errorf("unknown directory method: %s", method) + } +} diff --git a/cmd/ww/shell/ipfs_test.go b/cmd/ww/shell/ipfs_test.go new file mode 100644 index 0000000..e402a0f --- /dev/null +++ b/cmd/ww/shell/ipfs_test.go @@ -0,0 +1,334 @@ +package shell_test + +import ( + "testing" + + "github.com/ipfs/boxo/files" + "github.com/spy16/slurp/builtin" + "github.com/spy16/slurp/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/wetware/go/cmd/ww/shell" +) + +func TestFile_String(t *testing.T) { + t.Parallel() + + // Create a mock file with known size + content := "Hello, World!" + file := files.NewBytesFile([]byte(content)) + + f := shell.File{File: file} + + // Test String method + result := f.String() + assert.Contains(t, result, "IPFS File:") + assert.Contains(t, result, "bytes") +} + +func TestFile_Invoke(t *testing.T) { + t.Parallel() + + // Create a mock file + content := "Hello, World!" + file := files.NewBytesFile([]byte(content)) + f := shell.File{File: file} + + tests := []struct { + name string + args []core.Any + expected interface{} + wantErr bool + }{ + { + name: "no args - returns string representation", + args: []core.Any{}, + expected: "IPFS File:", + wantErr: false, + }, + { + name: "type method", + args: []core.Any{builtin.Keyword("type")}, + expected: builtin.String("file"), + wantErr: false, + }, + { + name: "size method", + args: []core.Any{builtin.Keyword("size")}, + expected: builtin.Int64(len(content)), + wantErr: false, + }, + { + name: "read-string method", + args: []core.Any{builtin.Keyword("read-string")}, + expected: builtin.String(content), + wantErr: false, + }, + { + name: "invalid method", + args: []core.Any{builtin.Keyword("invalid")}, + expected: nil, + wantErr: true, + }, + { + name: "non-keyword argument", + args: []core.Any{builtin.String("not-a-keyword")}, + expected: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := f.Invoke(tt.args...) + + if tt.wantErr { + assert.Error(t, err) + return + } + + require.NoError(t, err) + + if tt.expected != nil { + switch expected := tt.expected.(type) { + case string: + if expected == "IPFS File:" { + assert.Contains(t, result.(string), expected) + } else { + assert.Equal(t, expected, result) + } + case int64: + assert.Equal(t, expected, result) + } + } + }) + } +} + +func TestDirectory_String(t *testing.T) { + t.Parallel() + + // Create a mock directory + dir := files.NewMapDirectory(map[string]files.Node{ + "file1.txt": files.NewBytesFile([]byte("content1")), + "file2.txt": files.NewBytesFile([]byte("content2")), + }) + + d := shell.Directory{Directory: dir} + + // Test String method + result := d.String() + assert.Contains(t, result, "IPFS Directory:") + assert.Contains(t, result, "file1.txt") + assert.Contains(t, result, "file2.txt") +} + +func TestDirectory_Invoke(t *testing.T) { + t.Parallel() + + // Create a mock directory + dir := files.NewMapDirectory(map[string]files.Node{ + "file1.txt": files.NewBytesFile([]byte("content1")), + "file2.txt": files.NewBytesFile([]byte("content2")), + }) + + d := shell.Directory{Directory: dir} + + tests := []struct { + name string + args []core.Any + expected interface{} + wantErr bool + }{ + { + name: "no args - returns string representation", + args: []core.Any{}, + expected: "IPFS Directory:", + wantErr: false, + }, + { + name: "type method", + args: []core.Any{builtin.Keyword("type")}, + expected: builtin.String("directory"), + wantErr: false, + }, + { + name: "list method", + args: []core.Any{builtin.Keyword("list")}, + expected: nil, // Will be a list, we'll check it's not nil + wantErr: false, + }, + { + name: "entries method", + args: []core.Any{builtin.Keyword("entries")}, + expected: nil, // Will be a list, we'll check it's not nil + wantErr: false, + }, + { + name: "invalid method", + args: []core.Any{builtin.Keyword("invalid")}, + expected: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := d.Invoke(tt.args...) + + if tt.wantErr { + assert.Error(t, err) + return + } + + require.NoError(t, err) + + if tt.expected != nil { + if tt.expected == "IPFS Directory:" { + assert.Contains(t, result.(string), tt.expected) + } else { + assert.Equal(t, tt.expected, result) + } + } else { + // For list methods, just check it's not nil + assert.NotNil(t, result) + } + }) + } +} + +func TestNode_String(t *testing.T) { + t.Parallel() + + // Test with a file node + content := "Hello, World!" + file := files.NewBytesFile([]byte(content)) + n := shell.Node{Node: file} + + result := n.String() + assert.Contains(t, result, "IPFS Node:") + assert.Contains(t, result, "file") +} + +func TestNode_Type(t *testing.T) { + t.Parallel() + + // Test with a file node + content := "Hello, World!" + file := files.NewBytesFile([]byte(content)) + n := shell.Node{Node: file} + + assert.Equal(t, "file", n.Type()) + + // Test with a directory node + dir := files.NewMapDirectory(map[string]files.Node{}) + nDir := shell.Node{Node: dir} + assert.Equal(t, "directory", nDir.Type()) +} + +func TestNode_Invoke(t *testing.T) { + t.Parallel() + + // Create a mock file node + content := "Hello, World!" + file := files.NewBytesFile([]byte(content)) + n := shell.Node{Node: file} + + tests := []struct { + name string + args []core.Any + expected interface{} + wantErr bool + }{ + { + name: "no args - returns string representation", + args: []core.Any{}, + expected: "IPFS Node:", + wantErr: false, + }, + { + name: "type method", + args: []core.Any{builtin.Keyword("type")}, + expected: builtin.String("file"), + wantErr: false, + }, + { + name: "is-file method", + args: []core.Any{builtin.Keyword("is-file")}, + expected: builtin.Bool(true), + wantErr: false, + }, + { + name: "is-directory method", + args: []core.Any{builtin.Keyword("is-directory")}, + expected: builtin.Bool(false), + wantErr: false, + }, + { + name: "size method", + args: []core.Any{builtin.Keyword("size")}, + expected: builtin.Int64(len(content)), + wantErr: false, + }, + { + name: "invalid method", + args: []core.Any{builtin.Keyword("invalid")}, + expected: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := n.Invoke(tt.args...) + + if tt.wantErr { + assert.Error(t, err) + return + } + + require.NoError(t, err) + + if tt.expected != nil { + if tt.expected == "IPFS Node:" { + assert.Contains(t, result.(string), tt.expected) + } else { + assert.Equal(t, tt.expected, result) + } + } + }) + } +} + +// TestFile_ReadContent tests that file content can be read multiple times +func TestFile_ReadContent(t *testing.T) { + t.Parallel() + + content := "Hello, World!" + file := files.NewBytesFile([]byte(content)) + f := shell.File{File: file} + + result, err := f.Invoke(builtin.Keyword("read-string")) + require.NoError(t, err) + assert.Equal(t, builtin.String(content), result) +} + +// TestDirectory_Empty tests empty directory handling +func TestDirectory_Empty(t *testing.T) { + t.Parallel() + + // Create an empty directory + dir := files.NewMapDirectory(map[string]files.Node{}) + d := shell.Directory{Directory: dir} + + // Test string representation + result := d.String() + assert.Contains(t, result, "empty") + + // Test list method + _, err := d.Invoke(builtin.Keyword("list")) + require.NoError(t, err) + // For empty directory, result might be nil or empty list - both are acceptable + // We just verify the method doesn't error +} diff --git a/cmd/ww/shell/path.go b/cmd/ww/shell/path.go new file mode 100644 index 0000000..c1b55d8 --- /dev/null +++ b/cmd/ww/shell/path.go @@ -0,0 +1,69 @@ +package shell + +import ( + "fmt" + "io" + "strings" + "unicode" + + "github.com/ipfs/boxo/path" + iface "github.com/ipfs/kubo/core/coreiface" + "github.com/spy16/slurp/core" + "github.com/spy16/slurp/reader" +) + +// DefaultReaderFactory creates readers with IPFS path support +type DefaultReaderFactory struct { + IPFS iface.CoreAPI +} + +func (f DefaultReaderFactory) NewReader(r io.Reader) *reader.Reader { + rd := reader.New(r) + rd.SetMacro('/', false, NewPathReader(f.IPFS)) + return rd +} + +// NewPathReader is a ReaderMacro that handles IPFS and IPNS paths +func NewPathReader(ipfs iface.CoreAPI) func(*reader.Reader, rune) (core.Any, error) { + return func(rd *reader.Reader, init rune) (core.Any, error) { + beginPos := rd.Position() + + // Read the full path manually by reading runes until we hit whitespace or a delimiter + var b strings.Builder + b.WriteRune(init) // Start with the '/' character + + for { + r, err := rd.NextRune() + if err != nil { + if err == io.EOF { + break + } + return nil, &reader.Error{ + Cause: err, + Begin: beginPos, + End: beginPos, + } + } + + // Check if this rune should terminate the path + // Stop on whitespace, closing parentheses, and other delimiters + if unicode.IsSpace(r) || r == ')' || r == ']' || r == '}' { + rd.Unread(r) + break + } + + b.WriteRune(r) + } + + p, err := path.NewPath(b.String()) + if err != nil { + return nil, &reader.Error{ + Cause: fmt.Errorf("invalid IPFS/IPNS path: %s", err), + Begin: beginPos, + End: beginPos, + } + } + + return p, nil + } +} diff --git a/examples/shell/path_test.go b/cmd/ww/shell/path_test.go similarity index 90% rename from examples/shell/path_test.go rename to cmd/ww/shell/path_test.go index 71e86e4..b7d3584 100644 --- a/examples/shell/path_test.go +++ b/cmd/ww/shell/path_test.go @@ -1,10 +1,12 @@ -package main +package shell_test import ( "strings" "testing" + "github.com/ipfs/boxo/path" "github.com/spy16/slurp/reader" + "github.com/wetware/go/cmd/ww/shell" ) func TestIPFSPathReader(t *testing.T) { @@ -53,7 +55,7 @@ func TestIPFSPathReader(t *testing.T) { rd := reader.New(strings.NewReader(tt.input)) // Call the IPFS path reader - result, err := IPFSPathReader(rd, '/') + result, err := shell.NewPathReader(nil)(rd, '/') // Check error expectations if tt.wantErr { @@ -70,7 +72,7 @@ func TestIPFSPathReader(t *testing.T) { // Check result type if tt.expected == "Path" { - if _, ok := result.(Path); !ok { + if _, ok := result.(path.Path); !ok { t.Errorf("IPFSPathReader() expected Path type, got %T", result) } } @@ -93,13 +95,13 @@ func TestIPFSPathReaderWithValidPaths(t *testing.T) { t.Run("valid_"+pathStr, func(t *testing.T) { rd := reader.New(strings.NewReader(pathStr)) - result, err := IPFSPathReader(rd, '/') + result, err := shell.NewPathReader(nil)(rd, '/') if err != nil { t.Errorf("IPFSPathReader() failed for valid path %s: %v", pathStr, err) return } - pathObj, ok := result.(Path) + pathObj, ok := result.(path.Path) if !ok { t.Errorf("IPFSPathReader() returned wrong type for %s: %T", pathStr, result) return @@ -127,7 +129,7 @@ func TestIPFSPathReaderWithInvalidPaths(t *testing.T) { t.Run("invalid_"+pathStr, func(t *testing.T) { rd := reader.New(strings.NewReader(pathStr)) - result, err := IPFSPathReader(rd, '/') + result, err := shell.NewPathReader(nil)(rd, '/') if err == nil { t.Errorf("IPFSPathReader() should have failed for invalid path %s, got result: %v", pathStr, result) } @@ -178,7 +180,7 @@ func TestIPFSPathReaderEdgeCases(t *testing.T) { t.Run(tt.name, func(t *testing.T) { rd := reader.New(strings.NewReader(tt.input)) - result, err := IPFSPathReader(rd, '/') + result, err := shell.NewPathReader(nil)(rd, '/') if tt.wantErr { if err == nil { t.Errorf("IPFSPathReader() expected error for %s but got none", tt.input) @@ -191,7 +193,7 @@ func TestIPFSPathReaderEdgeCases(t *testing.T) { // If no error, result should be a Path if err == nil { - if _, ok := result.(Path); !ok { + if _, ok := result.(path.Path); !ok { t.Errorf("IPFSPathReader() returned wrong type for %s: %T", tt.input, result) } } diff --git a/cmd/ww/shell/shell b/cmd/ww/shell/shell new file mode 100755 index 0000000..0436546 Binary files /dev/null and b/cmd/ww/shell/shell differ diff --git a/cmd/ww/shell/shell.capnp b/cmd/ww/shell/shell.capnp new file mode 100644 index 0000000..22158d7 --- /dev/null +++ b/cmd/ww/shell/shell.capnp @@ -0,0 +1,29 @@ +using Go = import "/go.capnp"; + +@0xead48f650c32a806; + +$Go.package("main"); +$Go.import("github.com/wetware/go/shell"); + + +interface EventLoop { + addActor @0 (handler :Text) -> (mailbox :Mailbox); +} + +interface Mailbox { + newBuffer @0 () -> (buffer :Buffer); +} + +interface Buffer { + write @0 (input :Data) -> (status :Status); + writeString @1 (input :Text) -> (status :Status); + read @2 (count :UInt64) -> (output :Data, status :Status); + flush @3 (); + struct Status { + union { + ok @0 :Void; + eof @1 :Void; + error @2 :Text; + } + } +} diff --git a/cmd/ww/shell/shell.capnp.go b/cmd/ww/shell/shell.capnp.go new file mode 100644 index 0000000..370bf4b --- /dev/null +++ b/cmd/ww/shell/shell.capnp.go @@ -0,0 +1,1824 @@ +// Code generated by capnpc-go. DO NOT EDIT. + +package shell + +import ( + context "context" + strconv "strconv" + + capnp "capnproto.org/go/capnp/v3" + text "capnproto.org/go/capnp/v3/encoding/text" + fc "capnproto.org/go/capnp/v3/flowcontrol" + schemas "capnproto.org/go/capnp/v3/schemas" + server "capnproto.org/go/capnp/v3/server" +) + +type EventLoop capnp.Client + +// EventLoop_TypeID is the unique identifier for the type EventLoop. +const EventLoop_TypeID = 0xef0707db310901e8 + +func (c EventLoop) AddActor(ctx context.Context, params func(EventLoop_addActor_Params) error) (EventLoop_addActor_Results_Future, capnp.ReleaseFunc) { + + s := capnp.Send{ + Method: capnp.Method{ + InterfaceID: 0xef0707db310901e8, + MethodID: 0, + InterfaceName: "shell.capnp:EventLoop", + MethodName: "addActor", + }, + } + if params != nil { + s.ArgsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1} + s.PlaceArgs = func(s capnp.Struct) error { return params(EventLoop_addActor_Params(s)) } + } + + ans, release := capnp.Client(c).SendCall(ctx, s) + return EventLoop_addActor_Results_Future{Future: ans.Future()}, release + +} + +func (c EventLoop) WaitStreaming() error { + return capnp.Client(c).WaitStreaming() +} + +// String returns a string that identifies this capability for debugging +// purposes. Its format should not be depended on: in particular, it +// should not be used to compare clients. Use IsSame to compare clients +// for equality. +func (c EventLoop) String() string { + return "EventLoop(" + capnp.Client(c).String() + ")" +} + +// AddRef creates a new Client that refers to the same capability as c. +// If c is nil or has resolved to null, then AddRef returns nil. +func (c EventLoop) AddRef() EventLoop { + return EventLoop(capnp.Client(c).AddRef()) +} + +// Release releases a capability reference. If this is the last +// reference to the capability, then the underlying resources associated +// with the capability will be released. +// +// Release will panic if c has already been released, but not if c is +// nil or resolved to null. +func (c EventLoop) Release() { + capnp.Client(c).Release() +} + +// Resolve blocks until the capability is fully resolved or the Context +// expires. +func (c EventLoop) Resolve(ctx context.Context) error { + return capnp.Client(c).Resolve(ctx) +} + +func (c EventLoop) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Client(c).EncodeAsPtr(seg) +} + +func (EventLoop) DecodeFromPtr(p capnp.Ptr) EventLoop { + return EventLoop(capnp.Client{}.DecodeFromPtr(p)) +} + +// IsValid reports whether c is a valid reference to a capability. +// A reference is invalid if it is nil, has resolved to null, or has +// been released. +func (c EventLoop) IsValid() bool { + return capnp.Client(c).IsValid() +} + +// IsSame reports whether c and other refer to a capability created by the +// same call to NewClient. This can return false negatives if c or other +// are not fully resolved: use Resolve if this is an issue. If either +// c or other are released, then IsSame panics. +func (c EventLoop) IsSame(other EventLoop) bool { + return capnp.Client(c).IsSame(capnp.Client(other)) +} + +// Update the flowcontrol.FlowLimiter used to manage flow control for +// this client. This affects all future calls, but not calls already +// waiting to send. Passing nil sets the value to flowcontrol.NopLimiter, +// which is also the default. +func (c EventLoop) SetFlowLimiter(lim fc.FlowLimiter) { + capnp.Client(c).SetFlowLimiter(lim) +} + +// Get the current flowcontrol.FlowLimiter used to manage flow control +// for this client. +func (c EventLoop) GetFlowLimiter() fc.FlowLimiter { + return capnp.Client(c).GetFlowLimiter() +} + +// A EventLoop_Server is a EventLoop with a local implementation. +type EventLoop_Server interface { + AddActor(context.Context, EventLoop_addActor) error +} + +// EventLoop_NewServer creates a new Server from an implementation of EventLoop_Server. +func EventLoop_NewServer(s EventLoop_Server) *server.Server { + c, _ := s.(server.Shutdowner) + return server.New(EventLoop_Methods(nil, s), s, c) +} + +// EventLoop_ServerToClient creates a new Client from an implementation of EventLoop_Server. +// The caller is responsible for calling Release on the returned Client. +func EventLoop_ServerToClient(s EventLoop_Server) EventLoop { + return EventLoop(capnp.NewClient(EventLoop_NewServer(s))) +} + +// EventLoop_Methods appends Methods to a slice that invoke the methods on s. +// This can be used to create a more complicated Server. +func EventLoop_Methods(methods []server.Method, s EventLoop_Server) []server.Method { + if cap(methods) == 0 { + methods = make([]server.Method, 0, 1) + } + + methods = append(methods, server.Method{ + Method: capnp.Method{ + InterfaceID: 0xef0707db310901e8, + MethodID: 0, + InterfaceName: "shell.capnp:EventLoop", + MethodName: "addActor", + }, + Impl: func(ctx context.Context, call *server.Call) error { + return s.AddActor(ctx, EventLoop_addActor{call}) + }, + }) + + return methods +} + +// EventLoop_addActor holds the state for a server call to EventLoop.addActor. +// See server.Call for documentation. +type EventLoop_addActor struct { + *server.Call +} + +// Args returns the call's arguments. +func (c EventLoop_addActor) Args() EventLoop_addActor_Params { + return EventLoop_addActor_Params(c.Call.Args()) +} + +// AllocResults allocates the results struct. +func (c EventLoop_addActor) AllocResults() (EventLoop_addActor_Results, error) { + r, err := c.Call.AllocResults(capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return EventLoop_addActor_Results(r), err +} + +// EventLoop_List is a list of EventLoop. +type EventLoop_List = capnp.CapList[EventLoop] + +// NewEventLoop creates a new list of EventLoop. +func NewEventLoop_List(s *capnp.Segment, sz int32) (EventLoop_List, error) { + l, err := capnp.NewPointerList(s, sz) + return capnp.CapList[EventLoop](l), err +} + +type EventLoop_addActor_Params capnp.Struct + +// EventLoop_addActor_Params_TypeID is the unique identifier for the type EventLoop_addActor_Params. +const EventLoop_addActor_Params_TypeID = 0xd91363a073d0485a + +func NewEventLoop_addActor_Params(s *capnp.Segment) (EventLoop_addActor_Params, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return EventLoop_addActor_Params(st), err +} + +func NewRootEventLoop_addActor_Params(s *capnp.Segment) (EventLoop_addActor_Params, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return EventLoop_addActor_Params(st), err +} + +func ReadRootEventLoop_addActor_Params(msg *capnp.Message) (EventLoop_addActor_Params, error) { + root, err := msg.Root() + return EventLoop_addActor_Params(root.Struct()), err +} + +func (s EventLoop_addActor_Params) String() string { + str, _ := text.Marshal(0xd91363a073d0485a, capnp.Struct(s)) + return str +} + +func (s EventLoop_addActor_Params) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (EventLoop_addActor_Params) DecodeFromPtr(p capnp.Ptr) EventLoop_addActor_Params { + return EventLoop_addActor_Params(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s EventLoop_addActor_Params) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s EventLoop_addActor_Params) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s EventLoop_addActor_Params) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s EventLoop_addActor_Params) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} +func (s EventLoop_addActor_Params) Handler() (string, error) { + p, err := capnp.Struct(s).Ptr(0) + return p.Text(), err +} + +func (s EventLoop_addActor_Params) HasHandler() bool { + return capnp.Struct(s).HasPtr(0) +} + +func (s EventLoop_addActor_Params) HandlerBytes() ([]byte, error) { + p, err := capnp.Struct(s).Ptr(0) + return p.TextBytes(), err +} + +func (s EventLoop_addActor_Params) SetHandler(v string) error { + return capnp.Struct(s).SetText(0, v) +} + +// EventLoop_addActor_Params_List is a list of EventLoop_addActor_Params. +type EventLoop_addActor_Params_List = capnp.StructList[EventLoop_addActor_Params] + +// NewEventLoop_addActor_Params creates a new list of EventLoop_addActor_Params. +func NewEventLoop_addActor_Params_List(s *capnp.Segment, sz int32) (EventLoop_addActor_Params_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz) + return capnp.StructList[EventLoop_addActor_Params](l), err +} + +// EventLoop_addActor_Params_Future is a wrapper for a EventLoop_addActor_Params promised by a client call. +type EventLoop_addActor_Params_Future struct{ *capnp.Future } + +func (f EventLoop_addActor_Params_Future) Struct() (EventLoop_addActor_Params, error) { + p, err := f.Future.Ptr() + return EventLoop_addActor_Params(p.Struct()), err +} + +type EventLoop_addActor_Results capnp.Struct + +// EventLoop_addActor_Results_TypeID is the unique identifier for the type EventLoop_addActor_Results. +const EventLoop_addActor_Results_TypeID = 0xbbafeb3e01e71170 + +func NewEventLoop_addActor_Results(s *capnp.Segment) (EventLoop_addActor_Results, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return EventLoop_addActor_Results(st), err +} + +func NewRootEventLoop_addActor_Results(s *capnp.Segment) (EventLoop_addActor_Results, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return EventLoop_addActor_Results(st), err +} + +func ReadRootEventLoop_addActor_Results(msg *capnp.Message) (EventLoop_addActor_Results, error) { + root, err := msg.Root() + return EventLoop_addActor_Results(root.Struct()), err +} + +func (s EventLoop_addActor_Results) String() string { + str, _ := text.Marshal(0xbbafeb3e01e71170, capnp.Struct(s)) + return str +} + +func (s EventLoop_addActor_Results) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (EventLoop_addActor_Results) DecodeFromPtr(p capnp.Ptr) EventLoop_addActor_Results { + return EventLoop_addActor_Results(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s EventLoop_addActor_Results) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s EventLoop_addActor_Results) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s EventLoop_addActor_Results) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s EventLoop_addActor_Results) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} +func (s EventLoop_addActor_Results) Mailbox() Mailbox { + p, _ := capnp.Struct(s).Ptr(0) + return Mailbox(p.Interface().Client()) +} + +func (s EventLoop_addActor_Results) HasMailbox() bool { + return capnp.Struct(s).HasPtr(0) +} + +func (s EventLoop_addActor_Results) SetMailbox(v Mailbox) error { + if !v.IsValid() { + return capnp.Struct(s).SetPtr(0, capnp.Ptr{}) + } + seg := s.Segment() + in := capnp.NewInterface(seg, seg.Message().CapTable().Add(capnp.Client(v))) + return capnp.Struct(s).SetPtr(0, in.ToPtr()) +} + +// EventLoop_addActor_Results_List is a list of EventLoop_addActor_Results. +type EventLoop_addActor_Results_List = capnp.StructList[EventLoop_addActor_Results] + +// NewEventLoop_addActor_Results creates a new list of EventLoop_addActor_Results. +func NewEventLoop_addActor_Results_List(s *capnp.Segment, sz int32) (EventLoop_addActor_Results_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz) + return capnp.StructList[EventLoop_addActor_Results](l), err +} + +// EventLoop_addActor_Results_Future is a wrapper for a EventLoop_addActor_Results promised by a client call. +type EventLoop_addActor_Results_Future struct{ *capnp.Future } + +func (f EventLoop_addActor_Results_Future) Struct() (EventLoop_addActor_Results, error) { + p, err := f.Future.Ptr() + return EventLoop_addActor_Results(p.Struct()), err +} +func (p EventLoop_addActor_Results_Future) Mailbox() Mailbox { + return Mailbox(p.Future.Field(0, nil).Client()) +} + +type Mailbox capnp.Client + +// Mailbox_TypeID is the unique identifier for the type Mailbox. +const Mailbox_TypeID = 0xbd4bc3d338b68790 + +func (c Mailbox) NewBuffer(ctx context.Context, params func(Mailbox_newBuffer_Params) error) (Mailbox_newBuffer_Results_Future, capnp.ReleaseFunc) { + + s := capnp.Send{ + Method: capnp.Method{ + InterfaceID: 0xbd4bc3d338b68790, + MethodID: 0, + InterfaceName: "shell.capnp:Mailbox", + MethodName: "newBuffer", + }, + } + if params != nil { + s.ArgsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 0} + s.PlaceArgs = func(s capnp.Struct) error { return params(Mailbox_newBuffer_Params(s)) } + } + + ans, release := capnp.Client(c).SendCall(ctx, s) + return Mailbox_newBuffer_Results_Future{Future: ans.Future()}, release + +} + +func (c Mailbox) WaitStreaming() error { + return capnp.Client(c).WaitStreaming() +} + +// String returns a string that identifies this capability for debugging +// purposes. Its format should not be depended on: in particular, it +// should not be used to compare clients. Use IsSame to compare clients +// for equality. +func (c Mailbox) String() string { + return "Mailbox(" + capnp.Client(c).String() + ")" +} + +// AddRef creates a new Client that refers to the same capability as c. +// If c is nil or has resolved to null, then AddRef returns nil. +func (c Mailbox) AddRef() Mailbox { + return Mailbox(capnp.Client(c).AddRef()) +} + +// Release releases a capability reference. If this is the last +// reference to the capability, then the underlying resources associated +// with the capability will be released. +// +// Release will panic if c has already been released, but not if c is +// nil or resolved to null. +func (c Mailbox) Release() { + capnp.Client(c).Release() +} + +// Resolve blocks until the capability is fully resolved or the Context +// expires. +func (c Mailbox) Resolve(ctx context.Context) error { + return capnp.Client(c).Resolve(ctx) +} + +func (c Mailbox) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Client(c).EncodeAsPtr(seg) +} + +func (Mailbox) DecodeFromPtr(p capnp.Ptr) Mailbox { + return Mailbox(capnp.Client{}.DecodeFromPtr(p)) +} + +// IsValid reports whether c is a valid reference to a capability. +// A reference is invalid if it is nil, has resolved to null, or has +// been released. +func (c Mailbox) IsValid() bool { + return capnp.Client(c).IsValid() +} + +// IsSame reports whether c and other refer to a capability created by the +// same call to NewClient. This can return false negatives if c or other +// are not fully resolved: use Resolve if this is an issue. If either +// c or other are released, then IsSame panics. +func (c Mailbox) IsSame(other Mailbox) bool { + return capnp.Client(c).IsSame(capnp.Client(other)) +} + +// Update the flowcontrol.FlowLimiter used to manage flow control for +// this client. This affects all future calls, but not calls already +// waiting to send. Passing nil sets the value to flowcontrol.NopLimiter, +// which is also the default. +func (c Mailbox) SetFlowLimiter(lim fc.FlowLimiter) { + capnp.Client(c).SetFlowLimiter(lim) +} + +// Get the current flowcontrol.FlowLimiter used to manage flow control +// for this client. +func (c Mailbox) GetFlowLimiter() fc.FlowLimiter { + return capnp.Client(c).GetFlowLimiter() +} + +// A Mailbox_Server is a Mailbox with a local implementation. +type Mailbox_Server interface { + NewBuffer(context.Context, Mailbox_newBuffer) error +} + +// Mailbox_NewServer creates a new Server from an implementation of Mailbox_Server. +func Mailbox_NewServer(s Mailbox_Server) *server.Server { + c, _ := s.(server.Shutdowner) + return server.New(Mailbox_Methods(nil, s), s, c) +} + +// Mailbox_ServerToClient creates a new Client from an implementation of Mailbox_Server. +// The caller is responsible for calling Release on the returned Client. +func Mailbox_ServerToClient(s Mailbox_Server) Mailbox { + return Mailbox(capnp.NewClient(Mailbox_NewServer(s))) +} + +// Mailbox_Methods appends Methods to a slice that invoke the methods on s. +// This can be used to create a more complicated Server. +func Mailbox_Methods(methods []server.Method, s Mailbox_Server) []server.Method { + if cap(methods) == 0 { + methods = make([]server.Method, 0, 1) + } + + methods = append(methods, server.Method{ + Method: capnp.Method{ + InterfaceID: 0xbd4bc3d338b68790, + MethodID: 0, + InterfaceName: "shell.capnp:Mailbox", + MethodName: "newBuffer", + }, + Impl: func(ctx context.Context, call *server.Call) error { + return s.NewBuffer(ctx, Mailbox_newBuffer{call}) + }, + }) + + return methods +} + +// Mailbox_newBuffer holds the state for a server call to Mailbox.newBuffer. +// See server.Call for documentation. +type Mailbox_newBuffer struct { + *server.Call +} + +// Args returns the call's arguments. +func (c Mailbox_newBuffer) Args() Mailbox_newBuffer_Params { + return Mailbox_newBuffer_Params(c.Call.Args()) +} + +// AllocResults allocates the results struct. +func (c Mailbox_newBuffer) AllocResults() (Mailbox_newBuffer_Results, error) { + r, err := c.Call.AllocResults(capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Mailbox_newBuffer_Results(r), err +} + +// Mailbox_List is a list of Mailbox. +type Mailbox_List = capnp.CapList[Mailbox] + +// NewMailbox creates a new list of Mailbox. +func NewMailbox_List(s *capnp.Segment, sz int32) (Mailbox_List, error) { + l, err := capnp.NewPointerList(s, sz) + return capnp.CapList[Mailbox](l), err +} + +type Mailbox_newBuffer_Params capnp.Struct + +// Mailbox_newBuffer_Params_TypeID is the unique identifier for the type Mailbox_newBuffer_Params. +const Mailbox_newBuffer_Params_TypeID = 0xbd4ee25c19bae4a0 + +func NewMailbox_newBuffer_Params(s *capnp.Segment) (Mailbox_newBuffer_Params, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}) + return Mailbox_newBuffer_Params(st), err +} + +func NewRootMailbox_newBuffer_Params(s *capnp.Segment) (Mailbox_newBuffer_Params, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}) + return Mailbox_newBuffer_Params(st), err +} + +func ReadRootMailbox_newBuffer_Params(msg *capnp.Message) (Mailbox_newBuffer_Params, error) { + root, err := msg.Root() + return Mailbox_newBuffer_Params(root.Struct()), err +} + +func (s Mailbox_newBuffer_Params) String() string { + str, _ := text.Marshal(0xbd4ee25c19bae4a0, capnp.Struct(s)) + return str +} + +func (s Mailbox_newBuffer_Params) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Mailbox_newBuffer_Params) DecodeFromPtr(p capnp.Ptr) Mailbox_newBuffer_Params { + return Mailbox_newBuffer_Params(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Mailbox_newBuffer_Params) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Mailbox_newBuffer_Params) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Mailbox_newBuffer_Params) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Mailbox_newBuffer_Params) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} + +// Mailbox_newBuffer_Params_List is a list of Mailbox_newBuffer_Params. +type Mailbox_newBuffer_Params_List = capnp.StructList[Mailbox_newBuffer_Params] + +// NewMailbox_newBuffer_Params creates a new list of Mailbox_newBuffer_Params. +func NewMailbox_newBuffer_Params_List(s *capnp.Segment, sz int32) (Mailbox_newBuffer_Params_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}, sz) + return capnp.StructList[Mailbox_newBuffer_Params](l), err +} + +// Mailbox_newBuffer_Params_Future is a wrapper for a Mailbox_newBuffer_Params promised by a client call. +type Mailbox_newBuffer_Params_Future struct{ *capnp.Future } + +func (f Mailbox_newBuffer_Params_Future) Struct() (Mailbox_newBuffer_Params, error) { + p, err := f.Future.Ptr() + return Mailbox_newBuffer_Params(p.Struct()), err +} + +type Mailbox_newBuffer_Results capnp.Struct + +// Mailbox_newBuffer_Results_TypeID is the unique identifier for the type Mailbox_newBuffer_Results. +const Mailbox_newBuffer_Results_TypeID = 0x846071f72bde13bb + +func NewMailbox_newBuffer_Results(s *capnp.Segment) (Mailbox_newBuffer_Results, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Mailbox_newBuffer_Results(st), err +} + +func NewRootMailbox_newBuffer_Results(s *capnp.Segment) (Mailbox_newBuffer_Results, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Mailbox_newBuffer_Results(st), err +} + +func ReadRootMailbox_newBuffer_Results(msg *capnp.Message) (Mailbox_newBuffer_Results, error) { + root, err := msg.Root() + return Mailbox_newBuffer_Results(root.Struct()), err +} + +func (s Mailbox_newBuffer_Results) String() string { + str, _ := text.Marshal(0x846071f72bde13bb, capnp.Struct(s)) + return str +} + +func (s Mailbox_newBuffer_Results) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Mailbox_newBuffer_Results) DecodeFromPtr(p capnp.Ptr) Mailbox_newBuffer_Results { + return Mailbox_newBuffer_Results(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Mailbox_newBuffer_Results) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Mailbox_newBuffer_Results) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Mailbox_newBuffer_Results) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Mailbox_newBuffer_Results) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} +func (s Mailbox_newBuffer_Results) Buffer() Buffer { + p, _ := capnp.Struct(s).Ptr(0) + return Buffer(p.Interface().Client()) +} + +func (s Mailbox_newBuffer_Results) HasBuffer() bool { + return capnp.Struct(s).HasPtr(0) +} + +func (s Mailbox_newBuffer_Results) SetBuffer(v Buffer) error { + if !v.IsValid() { + return capnp.Struct(s).SetPtr(0, capnp.Ptr{}) + } + seg := s.Segment() + in := capnp.NewInterface(seg, seg.Message().CapTable().Add(capnp.Client(v))) + return capnp.Struct(s).SetPtr(0, in.ToPtr()) +} + +// Mailbox_newBuffer_Results_List is a list of Mailbox_newBuffer_Results. +type Mailbox_newBuffer_Results_List = capnp.StructList[Mailbox_newBuffer_Results] + +// NewMailbox_newBuffer_Results creates a new list of Mailbox_newBuffer_Results. +func NewMailbox_newBuffer_Results_List(s *capnp.Segment, sz int32) (Mailbox_newBuffer_Results_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz) + return capnp.StructList[Mailbox_newBuffer_Results](l), err +} + +// Mailbox_newBuffer_Results_Future is a wrapper for a Mailbox_newBuffer_Results promised by a client call. +type Mailbox_newBuffer_Results_Future struct{ *capnp.Future } + +func (f Mailbox_newBuffer_Results_Future) Struct() (Mailbox_newBuffer_Results, error) { + p, err := f.Future.Ptr() + return Mailbox_newBuffer_Results(p.Struct()), err +} +func (p Mailbox_newBuffer_Results_Future) Buffer() Buffer { + return Buffer(p.Future.Field(0, nil).Client()) +} + +type Buffer capnp.Client + +// Buffer_TypeID is the unique identifier for the type Buffer. +const Buffer_TypeID = 0xc385438ec56c4e58 + +func (c Buffer) Write(ctx context.Context, params func(Buffer_write_Params) error) (Buffer_write_Results_Future, capnp.ReleaseFunc) { + + s := capnp.Send{ + Method: capnp.Method{ + InterfaceID: 0xc385438ec56c4e58, + MethodID: 0, + InterfaceName: "shell.capnp:Buffer", + MethodName: "write", + }, + } + if params != nil { + s.ArgsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1} + s.PlaceArgs = func(s capnp.Struct) error { return params(Buffer_write_Params(s)) } + } + + ans, release := capnp.Client(c).SendCall(ctx, s) + return Buffer_write_Results_Future{Future: ans.Future()}, release + +} + +func (c Buffer) WriteString(ctx context.Context, params func(Buffer_writeString_Params) error) (Buffer_writeString_Results_Future, capnp.ReleaseFunc) { + + s := capnp.Send{ + Method: capnp.Method{ + InterfaceID: 0xc385438ec56c4e58, + MethodID: 1, + InterfaceName: "shell.capnp:Buffer", + MethodName: "writeString", + }, + } + if params != nil { + s.ArgsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1} + s.PlaceArgs = func(s capnp.Struct) error { return params(Buffer_writeString_Params(s)) } + } + + ans, release := capnp.Client(c).SendCall(ctx, s) + return Buffer_writeString_Results_Future{Future: ans.Future()}, release + +} + +func (c Buffer) Read(ctx context.Context, params func(Buffer_read_Params) error) (Buffer_read_Results_Future, capnp.ReleaseFunc) { + + s := capnp.Send{ + Method: capnp.Method{ + InterfaceID: 0xc385438ec56c4e58, + MethodID: 2, + InterfaceName: "shell.capnp:Buffer", + MethodName: "read", + }, + } + if params != nil { + s.ArgsSize = capnp.ObjectSize{DataSize: 8, PointerCount: 0} + s.PlaceArgs = func(s capnp.Struct) error { return params(Buffer_read_Params(s)) } + } + + ans, release := capnp.Client(c).SendCall(ctx, s) + return Buffer_read_Results_Future{Future: ans.Future()}, release + +} + +func (c Buffer) Flush(ctx context.Context, params func(Buffer_flush_Params) error) (Buffer_flush_Results_Future, capnp.ReleaseFunc) { + + s := capnp.Send{ + Method: capnp.Method{ + InterfaceID: 0xc385438ec56c4e58, + MethodID: 3, + InterfaceName: "shell.capnp:Buffer", + MethodName: "flush", + }, + } + if params != nil { + s.ArgsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 0} + s.PlaceArgs = func(s capnp.Struct) error { return params(Buffer_flush_Params(s)) } + } + + ans, release := capnp.Client(c).SendCall(ctx, s) + return Buffer_flush_Results_Future{Future: ans.Future()}, release + +} + +func (c Buffer) WaitStreaming() error { + return capnp.Client(c).WaitStreaming() +} + +// String returns a string that identifies this capability for debugging +// purposes. Its format should not be depended on: in particular, it +// should not be used to compare clients. Use IsSame to compare clients +// for equality. +func (c Buffer) String() string { + return "Buffer(" + capnp.Client(c).String() + ")" +} + +// AddRef creates a new Client that refers to the same capability as c. +// If c is nil or has resolved to null, then AddRef returns nil. +func (c Buffer) AddRef() Buffer { + return Buffer(capnp.Client(c).AddRef()) +} + +// Release releases a capability reference. If this is the last +// reference to the capability, then the underlying resources associated +// with the capability will be released. +// +// Release will panic if c has already been released, but not if c is +// nil or resolved to null. +func (c Buffer) Release() { + capnp.Client(c).Release() +} + +// Resolve blocks until the capability is fully resolved or the Context +// expires. +func (c Buffer) Resolve(ctx context.Context) error { + return capnp.Client(c).Resolve(ctx) +} + +func (c Buffer) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Client(c).EncodeAsPtr(seg) +} + +func (Buffer) DecodeFromPtr(p capnp.Ptr) Buffer { + return Buffer(capnp.Client{}.DecodeFromPtr(p)) +} + +// IsValid reports whether c is a valid reference to a capability. +// A reference is invalid if it is nil, has resolved to null, or has +// been released. +func (c Buffer) IsValid() bool { + return capnp.Client(c).IsValid() +} + +// IsSame reports whether c and other refer to a capability created by the +// same call to NewClient. This can return false negatives if c or other +// are not fully resolved: use Resolve if this is an issue. If either +// c or other are released, then IsSame panics. +func (c Buffer) IsSame(other Buffer) bool { + return capnp.Client(c).IsSame(capnp.Client(other)) +} + +// Update the flowcontrol.FlowLimiter used to manage flow control for +// this client. This affects all future calls, but not calls already +// waiting to send. Passing nil sets the value to flowcontrol.NopLimiter, +// which is also the default. +func (c Buffer) SetFlowLimiter(lim fc.FlowLimiter) { + capnp.Client(c).SetFlowLimiter(lim) +} + +// Get the current flowcontrol.FlowLimiter used to manage flow control +// for this client. +func (c Buffer) GetFlowLimiter() fc.FlowLimiter { + return capnp.Client(c).GetFlowLimiter() +} + +// A Buffer_Server is a Buffer with a local implementation. +type Buffer_Server interface { + Write(context.Context, Buffer_write) error + + WriteString(context.Context, Buffer_writeString) error + + Read(context.Context, Buffer_read) error + + Flush(context.Context, Buffer_flush) error +} + +// Buffer_NewServer creates a new Server from an implementation of Buffer_Server. +func Buffer_NewServer(s Buffer_Server) *server.Server { + c, _ := s.(server.Shutdowner) + return server.New(Buffer_Methods(nil, s), s, c) +} + +// Buffer_ServerToClient creates a new Client from an implementation of Buffer_Server. +// The caller is responsible for calling Release on the returned Client. +func Buffer_ServerToClient(s Buffer_Server) Buffer { + return Buffer(capnp.NewClient(Buffer_NewServer(s))) +} + +// Buffer_Methods appends Methods to a slice that invoke the methods on s. +// This can be used to create a more complicated Server. +func Buffer_Methods(methods []server.Method, s Buffer_Server) []server.Method { + if cap(methods) == 0 { + methods = make([]server.Method, 0, 4) + } + + methods = append(methods, server.Method{ + Method: capnp.Method{ + InterfaceID: 0xc385438ec56c4e58, + MethodID: 0, + InterfaceName: "shell.capnp:Buffer", + MethodName: "write", + }, + Impl: func(ctx context.Context, call *server.Call) error { + return s.Write(ctx, Buffer_write{call}) + }, + }) + + methods = append(methods, server.Method{ + Method: capnp.Method{ + InterfaceID: 0xc385438ec56c4e58, + MethodID: 1, + InterfaceName: "shell.capnp:Buffer", + MethodName: "writeString", + }, + Impl: func(ctx context.Context, call *server.Call) error { + return s.WriteString(ctx, Buffer_writeString{call}) + }, + }) + + methods = append(methods, server.Method{ + Method: capnp.Method{ + InterfaceID: 0xc385438ec56c4e58, + MethodID: 2, + InterfaceName: "shell.capnp:Buffer", + MethodName: "read", + }, + Impl: func(ctx context.Context, call *server.Call) error { + return s.Read(ctx, Buffer_read{call}) + }, + }) + + methods = append(methods, server.Method{ + Method: capnp.Method{ + InterfaceID: 0xc385438ec56c4e58, + MethodID: 3, + InterfaceName: "shell.capnp:Buffer", + MethodName: "flush", + }, + Impl: func(ctx context.Context, call *server.Call) error { + return s.Flush(ctx, Buffer_flush{call}) + }, + }) + + return methods +} + +// Buffer_write holds the state for a server call to Buffer.write. +// See server.Call for documentation. +type Buffer_write struct { + *server.Call +} + +// Args returns the call's arguments. +func (c Buffer_write) Args() Buffer_write_Params { + return Buffer_write_Params(c.Call.Args()) +} + +// AllocResults allocates the results struct. +func (c Buffer_write) AllocResults() (Buffer_write_Results, error) { + r, err := c.Call.AllocResults(capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Buffer_write_Results(r), err +} + +// Buffer_writeString holds the state for a server call to Buffer.writeString. +// See server.Call for documentation. +type Buffer_writeString struct { + *server.Call +} + +// Args returns the call's arguments. +func (c Buffer_writeString) Args() Buffer_writeString_Params { + return Buffer_writeString_Params(c.Call.Args()) +} + +// AllocResults allocates the results struct. +func (c Buffer_writeString) AllocResults() (Buffer_writeString_Results, error) { + r, err := c.Call.AllocResults(capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Buffer_writeString_Results(r), err +} + +// Buffer_read holds the state for a server call to Buffer.read. +// See server.Call for documentation. +type Buffer_read struct { + *server.Call +} + +// Args returns the call's arguments. +func (c Buffer_read) Args() Buffer_read_Params { + return Buffer_read_Params(c.Call.Args()) +} + +// AllocResults allocates the results struct. +func (c Buffer_read) AllocResults() (Buffer_read_Results, error) { + r, err := c.Call.AllocResults(capnp.ObjectSize{DataSize: 0, PointerCount: 2}) + return Buffer_read_Results(r), err +} + +// Buffer_flush holds the state for a server call to Buffer.flush. +// See server.Call for documentation. +type Buffer_flush struct { + *server.Call +} + +// Args returns the call's arguments. +func (c Buffer_flush) Args() Buffer_flush_Params { + return Buffer_flush_Params(c.Call.Args()) +} + +// AllocResults allocates the results struct. +func (c Buffer_flush) AllocResults() (Buffer_flush_Results, error) { + r, err := c.Call.AllocResults(capnp.ObjectSize{DataSize: 0, PointerCount: 0}) + return Buffer_flush_Results(r), err +} + +// Buffer_List is a list of Buffer. +type Buffer_List = capnp.CapList[Buffer] + +// NewBuffer creates a new list of Buffer. +func NewBuffer_List(s *capnp.Segment, sz int32) (Buffer_List, error) { + l, err := capnp.NewPointerList(s, sz) + return capnp.CapList[Buffer](l), err +} + +type Buffer_Status capnp.Struct +type Buffer_Status_Which uint16 + +const ( + Buffer_Status_Which_ok Buffer_Status_Which = 0 + Buffer_Status_Which_eof Buffer_Status_Which = 1 + Buffer_Status_Which_error Buffer_Status_Which = 2 +) + +func (w Buffer_Status_Which) String() string { + const s = "okeoferror" + switch w { + case Buffer_Status_Which_ok: + return s[0:2] + case Buffer_Status_Which_eof: + return s[2:5] + case Buffer_Status_Which_error: + return s[5:10] + + } + return "Buffer_Status_Which(" + strconv.FormatUint(uint64(w), 10) + ")" +} + +// Buffer_Status_TypeID is the unique identifier for the type Buffer_Status. +const Buffer_Status_TypeID = 0xb391d0fa84c21d71 + +func NewBuffer_Status(s *capnp.Segment) (Buffer_Status, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1}) + return Buffer_Status(st), err +} + +func NewRootBuffer_Status(s *capnp.Segment) (Buffer_Status, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1}) + return Buffer_Status(st), err +} + +func ReadRootBuffer_Status(msg *capnp.Message) (Buffer_Status, error) { + root, err := msg.Root() + return Buffer_Status(root.Struct()), err +} + +func (s Buffer_Status) String() string { + str, _ := text.Marshal(0xb391d0fa84c21d71, capnp.Struct(s)) + return str +} + +func (s Buffer_Status) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Buffer_Status) DecodeFromPtr(p capnp.Ptr) Buffer_Status { + return Buffer_Status(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Buffer_Status) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} + +func (s Buffer_Status) Which() Buffer_Status_Which { + return Buffer_Status_Which(capnp.Struct(s).Uint16(0)) +} +func (s Buffer_Status) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Buffer_Status) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Buffer_Status) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} +func (s Buffer_Status) SetOk() { + capnp.Struct(s).SetUint16(0, 0) + +} + +func (s Buffer_Status) SetEof() { + capnp.Struct(s).SetUint16(0, 1) + +} + +func (s Buffer_Status) Error() (string, error) { + if capnp.Struct(s).Uint16(0) != 2 { + panic("Which() != error") + } + p, err := capnp.Struct(s).Ptr(0) + return p.Text(), err +} + +func (s Buffer_Status) HasError() bool { + if capnp.Struct(s).Uint16(0) != 2 { + return false + } + return capnp.Struct(s).HasPtr(0) +} + +func (s Buffer_Status) ErrorBytes() ([]byte, error) { + p, err := capnp.Struct(s).Ptr(0) + return p.TextBytes(), err +} + +func (s Buffer_Status) SetError(v string) error { + capnp.Struct(s).SetUint16(0, 2) + return capnp.Struct(s).SetText(0, v) +} + +// Buffer_Status_List is a list of Buffer_Status. +type Buffer_Status_List = capnp.StructList[Buffer_Status] + +// NewBuffer_Status creates a new list of Buffer_Status. +func NewBuffer_Status_List(s *capnp.Segment, sz int32) (Buffer_Status_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1}, sz) + return capnp.StructList[Buffer_Status](l), err +} + +// Buffer_Status_Future is a wrapper for a Buffer_Status promised by a client call. +type Buffer_Status_Future struct{ *capnp.Future } + +func (f Buffer_Status_Future) Struct() (Buffer_Status, error) { + p, err := f.Future.Ptr() + return Buffer_Status(p.Struct()), err +} + +type Buffer_write_Params capnp.Struct + +// Buffer_write_Params_TypeID is the unique identifier for the type Buffer_write_Params. +const Buffer_write_Params_TypeID = 0xac24ea5bb35da196 + +func NewBuffer_write_Params(s *capnp.Segment) (Buffer_write_Params, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Buffer_write_Params(st), err +} + +func NewRootBuffer_write_Params(s *capnp.Segment) (Buffer_write_Params, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Buffer_write_Params(st), err +} + +func ReadRootBuffer_write_Params(msg *capnp.Message) (Buffer_write_Params, error) { + root, err := msg.Root() + return Buffer_write_Params(root.Struct()), err +} + +func (s Buffer_write_Params) String() string { + str, _ := text.Marshal(0xac24ea5bb35da196, capnp.Struct(s)) + return str +} + +func (s Buffer_write_Params) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Buffer_write_Params) DecodeFromPtr(p capnp.Ptr) Buffer_write_Params { + return Buffer_write_Params(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Buffer_write_Params) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Buffer_write_Params) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Buffer_write_Params) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Buffer_write_Params) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} +func (s Buffer_write_Params) Input() ([]byte, error) { + p, err := capnp.Struct(s).Ptr(0) + return []byte(p.Data()), err +} + +func (s Buffer_write_Params) HasInput() bool { + return capnp.Struct(s).HasPtr(0) +} + +func (s Buffer_write_Params) SetInput(v []byte) error { + return capnp.Struct(s).SetData(0, v) +} + +// Buffer_write_Params_List is a list of Buffer_write_Params. +type Buffer_write_Params_List = capnp.StructList[Buffer_write_Params] + +// NewBuffer_write_Params creates a new list of Buffer_write_Params. +func NewBuffer_write_Params_List(s *capnp.Segment, sz int32) (Buffer_write_Params_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz) + return capnp.StructList[Buffer_write_Params](l), err +} + +// Buffer_write_Params_Future is a wrapper for a Buffer_write_Params promised by a client call. +type Buffer_write_Params_Future struct{ *capnp.Future } + +func (f Buffer_write_Params_Future) Struct() (Buffer_write_Params, error) { + p, err := f.Future.Ptr() + return Buffer_write_Params(p.Struct()), err +} + +type Buffer_write_Results capnp.Struct + +// Buffer_write_Results_TypeID is the unique identifier for the type Buffer_write_Results. +const Buffer_write_Results_TypeID = 0xd15c12b2b2c5d94e + +func NewBuffer_write_Results(s *capnp.Segment) (Buffer_write_Results, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Buffer_write_Results(st), err +} + +func NewRootBuffer_write_Results(s *capnp.Segment) (Buffer_write_Results, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Buffer_write_Results(st), err +} + +func ReadRootBuffer_write_Results(msg *capnp.Message) (Buffer_write_Results, error) { + root, err := msg.Root() + return Buffer_write_Results(root.Struct()), err +} + +func (s Buffer_write_Results) String() string { + str, _ := text.Marshal(0xd15c12b2b2c5d94e, capnp.Struct(s)) + return str +} + +func (s Buffer_write_Results) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Buffer_write_Results) DecodeFromPtr(p capnp.Ptr) Buffer_write_Results { + return Buffer_write_Results(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Buffer_write_Results) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Buffer_write_Results) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Buffer_write_Results) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Buffer_write_Results) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} +func (s Buffer_write_Results) Status() (Buffer_Status, error) { + p, err := capnp.Struct(s).Ptr(0) + return Buffer_Status(p.Struct()), err +} + +func (s Buffer_write_Results) HasStatus() bool { + return capnp.Struct(s).HasPtr(0) +} + +func (s Buffer_write_Results) SetStatus(v Buffer_Status) error { + return capnp.Struct(s).SetPtr(0, capnp.Struct(v).ToPtr()) +} + +// NewStatus sets the status field to a newly +// allocated Buffer_Status struct, preferring placement in s's segment. +func (s Buffer_write_Results) NewStatus() (Buffer_Status, error) { + ss, err := NewBuffer_Status(capnp.Struct(s).Segment()) + if err != nil { + return Buffer_Status{}, err + } + err = capnp.Struct(s).SetPtr(0, capnp.Struct(ss).ToPtr()) + return ss, err +} + +// Buffer_write_Results_List is a list of Buffer_write_Results. +type Buffer_write_Results_List = capnp.StructList[Buffer_write_Results] + +// NewBuffer_write_Results creates a new list of Buffer_write_Results. +func NewBuffer_write_Results_List(s *capnp.Segment, sz int32) (Buffer_write_Results_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz) + return capnp.StructList[Buffer_write_Results](l), err +} + +// Buffer_write_Results_Future is a wrapper for a Buffer_write_Results promised by a client call. +type Buffer_write_Results_Future struct{ *capnp.Future } + +func (f Buffer_write_Results_Future) Struct() (Buffer_write_Results, error) { + p, err := f.Future.Ptr() + return Buffer_write_Results(p.Struct()), err +} +func (p Buffer_write_Results_Future) Status() Buffer_Status_Future { + return Buffer_Status_Future{Future: p.Future.Field(0, nil)} +} + +type Buffer_writeString_Params capnp.Struct + +// Buffer_writeString_Params_TypeID is the unique identifier for the type Buffer_writeString_Params. +const Buffer_writeString_Params_TypeID = 0xeb0e5010d1b59026 + +func NewBuffer_writeString_Params(s *capnp.Segment) (Buffer_writeString_Params, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Buffer_writeString_Params(st), err +} + +func NewRootBuffer_writeString_Params(s *capnp.Segment) (Buffer_writeString_Params, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Buffer_writeString_Params(st), err +} + +func ReadRootBuffer_writeString_Params(msg *capnp.Message) (Buffer_writeString_Params, error) { + root, err := msg.Root() + return Buffer_writeString_Params(root.Struct()), err +} + +func (s Buffer_writeString_Params) String() string { + str, _ := text.Marshal(0xeb0e5010d1b59026, capnp.Struct(s)) + return str +} + +func (s Buffer_writeString_Params) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Buffer_writeString_Params) DecodeFromPtr(p capnp.Ptr) Buffer_writeString_Params { + return Buffer_writeString_Params(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Buffer_writeString_Params) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Buffer_writeString_Params) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Buffer_writeString_Params) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Buffer_writeString_Params) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} +func (s Buffer_writeString_Params) Input() (string, error) { + p, err := capnp.Struct(s).Ptr(0) + return p.Text(), err +} + +func (s Buffer_writeString_Params) HasInput() bool { + return capnp.Struct(s).HasPtr(0) +} + +func (s Buffer_writeString_Params) InputBytes() ([]byte, error) { + p, err := capnp.Struct(s).Ptr(0) + return p.TextBytes(), err +} + +func (s Buffer_writeString_Params) SetInput(v string) error { + return capnp.Struct(s).SetText(0, v) +} + +// Buffer_writeString_Params_List is a list of Buffer_writeString_Params. +type Buffer_writeString_Params_List = capnp.StructList[Buffer_writeString_Params] + +// NewBuffer_writeString_Params creates a new list of Buffer_writeString_Params. +func NewBuffer_writeString_Params_List(s *capnp.Segment, sz int32) (Buffer_writeString_Params_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz) + return capnp.StructList[Buffer_writeString_Params](l), err +} + +// Buffer_writeString_Params_Future is a wrapper for a Buffer_writeString_Params promised by a client call. +type Buffer_writeString_Params_Future struct{ *capnp.Future } + +func (f Buffer_writeString_Params_Future) Struct() (Buffer_writeString_Params, error) { + p, err := f.Future.Ptr() + return Buffer_writeString_Params(p.Struct()), err +} + +type Buffer_writeString_Results capnp.Struct + +// Buffer_writeString_Results_TypeID is the unique identifier for the type Buffer_writeString_Results. +const Buffer_writeString_Results_TypeID = 0xfae584cad6e82c9b + +func NewBuffer_writeString_Results(s *capnp.Segment) (Buffer_writeString_Results, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Buffer_writeString_Results(st), err +} + +func NewRootBuffer_writeString_Results(s *capnp.Segment) (Buffer_writeString_Results, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Buffer_writeString_Results(st), err +} + +func ReadRootBuffer_writeString_Results(msg *capnp.Message) (Buffer_writeString_Results, error) { + root, err := msg.Root() + return Buffer_writeString_Results(root.Struct()), err +} + +func (s Buffer_writeString_Results) String() string { + str, _ := text.Marshal(0xfae584cad6e82c9b, capnp.Struct(s)) + return str +} + +func (s Buffer_writeString_Results) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Buffer_writeString_Results) DecodeFromPtr(p capnp.Ptr) Buffer_writeString_Results { + return Buffer_writeString_Results(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Buffer_writeString_Results) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Buffer_writeString_Results) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Buffer_writeString_Results) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Buffer_writeString_Results) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} +func (s Buffer_writeString_Results) Status() (Buffer_Status, error) { + p, err := capnp.Struct(s).Ptr(0) + return Buffer_Status(p.Struct()), err +} + +func (s Buffer_writeString_Results) HasStatus() bool { + return capnp.Struct(s).HasPtr(0) +} + +func (s Buffer_writeString_Results) SetStatus(v Buffer_Status) error { + return capnp.Struct(s).SetPtr(0, capnp.Struct(v).ToPtr()) +} + +// NewStatus sets the status field to a newly +// allocated Buffer_Status struct, preferring placement in s's segment. +func (s Buffer_writeString_Results) NewStatus() (Buffer_Status, error) { + ss, err := NewBuffer_Status(capnp.Struct(s).Segment()) + if err != nil { + return Buffer_Status{}, err + } + err = capnp.Struct(s).SetPtr(0, capnp.Struct(ss).ToPtr()) + return ss, err +} + +// Buffer_writeString_Results_List is a list of Buffer_writeString_Results. +type Buffer_writeString_Results_List = capnp.StructList[Buffer_writeString_Results] + +// NewBuffer_writeString_Results creates a new list of Buffer_writeString_Results. +func NewBuffer_writeString_Results_List(s *capnp.Segment, sz int32) (Buffer_writeString_Results_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz) + return capnp.StructList[Buffer_writeString_Results](l), err +} + +// Buffer_writeString_Results_Future is a wrapper for a Buffer_writeString_Results promised by a client call. +type Buffer_writeString_Results_Future struct{ *capnp.Future } + +func (f Buffer_writeString_Results_Future) Struct() (Buffer_writeString_Results, error) { + p, err := f.Future.Ptr() + return Buffer_writeString_Results(p.Struct()), err +} +func (p Buffer_writeString_Results_Future) Status() Buffer_Status_Future { + return Buffer_Status_Future{Future: p.Future.Field(0, nil)} +} + +type Buffer_read_Params capnp.Struct + +// Buffer_read_Params_TypeID is the unique identifier for the type Buffer_read_Params. +const Buffer_read_Params_TypeID = 0x988f94f380d5b032 + +func NewBuffer_read_Params(s *capnp.Segment) (Buffer_read_Params, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 0}) + return Buffer_read_Params(st), err +} + +func NewRootBuffer_read_Params(s *capnp.Segment) (Buffer_read_Params, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 0}) + return Buffer_read_Params(st), err +} + +func ReadRootBuffer_read_Params(msg *capnp.Message) (Buffer_read_Params, error) { + root, err := msg.Root() + return Buffer_read_Params(root.Struct()), err +} + +func (s Buffer_read_Params) String() string { + str, _ := text.Marshal(0x988f94f380d5b032, capnp.Struct(s)) + return str +} + +func (s Buffer_read_Params) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Buffer_read_Params) DecodeFromPtr(p capnp.Ptr) Buffer_read_Params { + return Buffer_read_Params(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Buffer_read_Params) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Buffer_read_Params) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Buffer_read_Params) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Buffer_read_Params) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} +func (s Buffer_read_Params) Count() uint64 { + return capnp.Struct(s).Uint64(0) +} + +func (s Buffer_read_Params) SetCount(v uint64) { + capnp.Struct(s).SetUint64(0, v) +} + +// Buffer_read_Params_List is a list of Buffer_read_Params. +type Buffer_read_Params_List = capnp.StructList[Buffer_read_Params] + +// NewBuffer_read_Params creates a new list of Buffer_read_Params. +func NewBuffer_read_Params_List(s *capnp.Segment, sz int32) (Buffer_read_Params_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 0}, sz) + return capnp.StructList[Buffer_read_Params](l), err +} + +// Buffer_read_Params_Future is a wrapper for a Buffer_read_Params promised by a client call. +type Buffer_read_Params_Future struct{ *capnp.Future } + +func (f Buffer_read_Params_Future) Struct() (Buffer_read_Params, error) { + p, err := f.Future.Ptr() + return Buffer_read_Params(p.Struct()), err +} + +type Buffer_read_Results capnp.Struct + +// Buffer_read_Results_TypeID is the unique identifier for the type Buffer_read_Results. +const Buffer_read_Results_TypeID = 0xd97c0d57b5fd9e5d + +func NewBuffer_read_Results(s *capnp.Segment) (Buffer_read_Results, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}) + return Buffer_read_Results(st), err +} + +func NewRootBuffer_read_Results(s *capnp.Segment) (Buffer_read_Results, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}) + return Buffer_read_Results(st), err +} + +func ReadRootBuffer_read_Results(msg *capnp.Message) (Buffer_read_Results, error) { + root, err := msg.Root() + return Buffer_read_Results(root.Struct()), err +} + +func (s Buffer_read_Results) String() string { + str, _ := text.Marshal(0xd97c0d57b5fd9e5d, capnp.Struct(s)) + return str +} + +func (s Buffer_read_Results) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Buffer_read_Results) DecodeFromPtr(p capnp.Ptr) Buffer_read_Results { + return Buffer_read_Results(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Buffer_read_Results) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Buffer_read_Results) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Buffer_read_Results) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Buffer_read_Results) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} +func (s Buffer_read_Results) Output() ([]byte, error) { + p, err := capnp.Struct(s).Ptr(0) + return []byte(p.Data()), err +} + +func (s Buffer_read_Results) HasOutput() bool { + return capnp.Struct(s).HasPtr(0) +} + +func (s Buffer_read_Results) SetOutput(v []byte) error { + return capnp.Struct(s).SetData(0, v) +} + +func (s Buffer_read_Results) Status() (Buffer_Status, error) { + p, err := capnp.Struct(s).Ptr(1) + return Buffer_Status(p.Struct()), err +} + +func (s Buffer_read_Results) HasStatus() bool { + return capnp.Struct(s).HasPtr(1) +} + +func (s Buffer_read_Results) SetStatus(v Buffer_Status) error { + return capnp.Struct(s).SetPtr(1, capnp.Struct(v).ToPtr()) +} + +// NewStatus sets the status field to a newly +// allocated Buffer_Status struct, preferring placement in s's segment. +func (s Buffer_read_Results) NewStatus() (Buffer_Status, error) { + ss, err := NewBuffer_Status(capnp.Struct(s).Segment()) + if err != nil { + return Buffer_Status{}, err + } + err = capnp.Struct(s).SetPtr(1, capnp.Struct(ss).ToPtr()) + return ss, err +} + +// Buffer_read_Results_List is a list of Buffer_read_Results. +type Buffer_read_Results_List = capnp.StructList[Buffer_read_Results] + +// NewBuffer_read_Results creates a new list of Buffer_read_Results. +func NewBuffer_read_Results_List(s *capnp.Segment, sz int32) (Buffer_read_Results_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}, sz) + return capnp.StructList[Buffer_read_Results](l), err +} + +// Buffer_read_Results_Future is a wrapper for a Buffer_read_Results promised by a client call. +type Buffer_read_Results_Future struct{ *capnp.Future } + +func (f Buffer_read_Results_Future) Struct() (Buffer_read_Results, error) { + p, err := f.Future.Ptr() + return Buffer_read_Results(p.Struct()), err +} +func (p Buffer_read_Results_Future) Status() Buffer_Status_Future { + return Buffer_Status_Future{Future: p.Future.Field(1, nil)} +} + +type Buffer_flush_Params capnp.Struct + +// Buffer_flush_Params_TypeID is the unique identifier for the type Buffer_flush_Params. +const Buffer_flush_Params_TypeID = 0xe6195e5a8613fc66 + +func NewBuffer_flush_Params(s *capnp.Segment) (Buffer_flush_Params, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}) + return Buffer_flush_Params(st), err +} + +func NewRootBuffer_flush_Params(s *capnp.Segment) (Buffer_flush_Params, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}) + return Buffer_flush_Params(st), err +} + +func ReadRootBuffer_flush_Params(msg *capnp.Message) (Buffer_flush_Params, error) { + root, err := msg.Root() + return Buffer_flush_Params(root.Struct()), err +} + +func (s Buffer_flush_Params) String() string { + str, _ := text.Marshal(0xe6195e5a8613fc66, capnp.Struct(s)) + return str +} + +func (s Buffer_flush_Params) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Buffer_flush_Params) DecodeFromPtr(p capnp.Ptr) Buffer_flush_Params { + return Buffer_flush_Params(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Buffer_flush_Params) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Buffer_flush_Params) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Buffer_flush_Params) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Buffer_flush_Params) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} + +// Buffer_flush_Params_List is a list of Buffer_flush_Params. +type Buffer_flush_Params_List = capnp.StructList[Buffer_flush_Params] + +// NewBuffer_flush_Params creates a new list of Buffer_flush_Params. +func NewBuffer_flush_Params_List(s *capnp.Segment, sz int32) (Buffer_flush_Params_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}, sz) + return capnp.StructList[Buffer_flush_Params](l), err +} + +// Buffer_flush_Params_Future is a wrapper for a Buffer_flush_Params promised by a client call. +type Buffer_flush_Params_Future struct{ *capnp.Future } + +func (f Buffer_flush_Params_Future) Struct() (Buffer_flush_Params, error) { + p, err := f.Future.Ptr() + return Buffer_flush_Params(p.Struct()), err +} + +type Buffer_flush_Results capnp.Struct + +// Buffer_flush_Results_TypeID is the unique identifier for the type Buffer_flush_Results. +const Buffer_flush_Results_TypeID = 0xa71b7c75f8fd8005 + +func NewBuffer_flush_Results(s *capnp.Segment) (Buffer_flush_Results, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}) + return Buffer_flush_Results(st), err +} + +func NewRootBuffer_flush_Results(s *capnp.Segment) (Buffer_flush_Results, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}) + return Buffer_flush_Results(st), err +} + +func ReadRootBuffer_flush_Results(msg *capnp.Message) (Buffer_flush_Results, error) { + root, err := msg.Root() + return Buffer_flush_Results(root.Struct()), err +} + +func (s Buffer_flush_Results) String() string { + str, _ := text.Marshal(0xa71b7c75f8fd8005, capnp.Struct(s)) + return str +} + +func (s Buffer_flush_Results) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Buffer_flush_Results) DecodeFromPtr(p capnp.Ptr) Buffer_flush_Results { + return Buffer_flush_Results(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Buffer_flush_Results) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Buffer_flush_Results) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Buffer_flush_Results) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Buffer_flush_Results) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} + +// Buffer_flush_Results_List is a list of Buffer_flush_Results. +type Buffer_flush_Results_List = capnp.StructList[Buffer_flush_Results] + +// NewBuffer_flush_Results creates a new list of Buffer_flush_Results. +func NewBuffer_flush_Results_List(s *capnp.Segment, sz int32) (Buffer_flush_Results_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}, sz) + return capnp.StructList[Buffer_flush_Results](l), err +} + +// Buffer_flush_Results_Future is a wrapper for a Buffer_flush_Results promised by a client call. +type Buffer_flush_Results_Future struct{ *capnp.Future } + +func (f Buffer_flush_Results_Future) Struct() (Buffer_flush_Results, error) { + p, err := f.Future.Ptr() + return Buffer_flush_Results(p.Struct()), err +} + +const schema_ead48f650c32a806 = "x\xda\x8cUQh\x1cU\x14\xbd\xe7\xbd\x99\x9d\xa6\xec" + + "\xda<'*U\xca\xa2,\xa2\xd5\x84v\xe3\x87\x06t" + + "\xd7\xea\xd2\xaam\xd8I\x04Mm\xc5I2\xdb,n" + + "w6\xb3\xb3\xa6B\xc5\x06R\xc5\x0fm\x03\"\xa2 " + + "U\x10-\x08\xb5)\x15\xb4\xe4\xc7\x92\x1f\xbf\x1a\xb5B" + + "\x10\xfc\x10\xb5\xb5\xfd\x10\xf1C\x09\xc6\x91\xf7&\xb3;" + + "\xc9&\xa4_\x09\xfb\xde\x9ew\xce\xb9\xe7\xdc\xdd\x91g" + + "ymg\xeaJ\x82\x98U\xd4\x13\xc1\x05\xf3\xa7\xfb\xfe" + + "\x1e\x7fa\x8aD\x17\x88t\x18D\xbdC<\x0b\x82i" + + "\xf3\x1c!\xc8~~\xf9\xd8_o\x9fx\x97,\x13 " + + "\xd2\x0c\"s\x92/\x12\xcc\xe3\xea\\?\xb6\xf4O\xe3" + + "\xe8\x1d\x9f\x900\x97\x8f{?\xe6\x9bAZ\xf0\xce\x87" + + "\x07\xcf=w-\xf3Yx\x12B\xbf\xc9\x99\x84\x9eV" + + "_\x1d\xdf\xf6\xf5\xd4\xe2\xa5\xe9s\x0a:x\xb6\xbf2" + + "\xf7\xd6c\xc7/R\x01\x06'2\xcf\xf2o\xcdY." + + "\x9f\xfb\x92\x9f!\x045q\x05\x8f\\?s\x81\xc4\xad" + + "M8K\xeb\x93pC\x9a\x84;\xf9\xfa\x17\x0f~w" + + "\xf1\xa9Y\x12I\x1e$>\xcd&\x9d\x13\xdf_#\x82" + + "\xf9\xb2\xf6\x919\xa9\x88\xbf\xa2\xed6O\xcb\xff\x82S" + + "\xbf|\xb5\xf5\xc0\xcf\xfd\xb3\xa1nE{Z\xdb.i" + + "7y\xb4\xc3\xbcgNj\xb7\x11\xf5\xbe\xa1\x190\xbb" + + "u\x89\xd3\xbf073s\xf3\x81\xf9\xb8\xc8[\xf4\xcd" + + "\x92\xd56]\xb2\xda\xbf\xe7R\xfd\xd4\x88\xb9\x10\xa7\xfd" + + "\xb0\xae\x0c.\xa8\x0b\x07?X:\xffL\xea\xe8\xc22" + + "\x02\x93\x17\x1c]\xd9T\xd6'\x08A\xe9_\xf3\xb5\xfd" + + "\xcfo\xfd-\xe6\xf0\x9c<\xd7\x82\xbbO\x9e\x9f\xef," + + "\xdet=\xfe\xf8\xe9\x10\xfb\xac\xc2\xbe\x8a\x8e\x9d?\x1a" + + "\xc6\x1fmZ\xe6\xf5\x19sA\x0a0/\xeb\xbbM$" + + "\xa4\x94\xf7\xef\xbf\xfa\xc37S\xbf.\xc6\xd1~\xd7\x95" + + "\xc1\x7f\xea9\xea\x0e\xeacN\xa5\xd23b\xf3Z\xb5" + + "\xd6\xb7\xcf.W\x86\xdd#=UgbW\xa3Tr" + + "\xbc\xcc\x80\x93\xae7*~\xdd\xd2\xb8F\xa4\x81H\xa4" + + "\xfa\x88\xacM\x1cV\x17CnX]\x83h9L\x80" + + " 4q\x99\xc4\x0d\xc1z<\xc7\x1e\xcd\xe4\x8a\xb6g" + + "\x1f^\x01\x98m\x01\xa6G\xdcF\xd5G\x071t\xc4" + + "Px\x0c\xa5Ti\xd4\xc72\x03\x8e\xe2E\xb4\xe6\x9d" + + "\x09\xaf\xec;\x99\xf0%Z\xef\xa9r\xb5\xd6\xf0\x91\"" + + "\x86\xd4:\x84\x07}\xdbo\xa0^\x04\xac$\xd7\x92A" + + "\xa0@\x0a\xb7\x13Yy\x0ek/C\x0a\xff\x05]\xb2" + + "C\xe2\x89\xbb\x88\xac\xc79\xac\"C\x8a-\x05]`" + + "Db\x9f|p\x0f\x87\xf54\x03w_\xa4\x84\xe1\xb8" + + "%J\xa4\x1d\xcfs=$\x89!\xb9Zf\xe1%\xa7" + + "\xea\xefu\xddZ\x8f=:\xfa\xe8\x88\xefz\x99\x81\x9c" + + "\xd36\x85]-%\xaf\x1e\x0e\xe7\x06\xd1\xea\xcb\xaa1" + + " \x1a\xaf1\xec\x1e\x91z4\xae\xc7\x1a\x83he\x08" + + "1@Lt\x18A\x14\x01\x82\x97G\x11\xd8((E" + + "{\x8b4{\xe5\x83\xf2\x8c;\x9e\xa5!\xb6\x18\x04\xfa" + + "r\xca\xd8\xba\xd5\xa9HD;\x05Q\xef\xc4x\x96\x98" + + "p\x0c\xa0\xd9\x06DA\x16C\xc3\xc4\x84e\x805\xb7" + + "\x18\xa2\xb6\x89\xc2vb\xe2!\x03\xbc\xd9/D\xabL" + + "tK\xcc;\x8d\xb4\x8aF\x1e\x81\xfa;\xe8{d\x94" + + "\xab\x87\xf2\xd8\"\xc3\x99GZ\xa5k\x0d\xc5+\x82\xd5" + + "\x0c\xdfz\xb5\xa8+}\xe8l\xa9&\xa0\xf3\x06&]" + + "L\xb7\xb5#>\xe81\xbb:Zq\xd6\x09N\xbce" + + "\x11CkS\x13\xe8^I0\xc3a\xed`\x10@\x98" + + "\xdan\xf9\xe1=\x1c\xd6\x03\x0c9\xb7\xe1\xc7\x1a\xb1\xb1" + + "\x08\xa3\xad\x95\xcb\x8d\x8b.\xac6n\xd0\xf7\xca\xd5C" + + "k\x89l\xef\xe5j\x89\x88\x1cK+\xcbZ\x19\x8e\x96" + + "1\xa2\x1f\x13!\x9e\x0c3\x1c\xb9JD\x1bLt\x99" + + "\xd8Z=\xbb\x91\xb1\xfe\x1f\x00\x00\xff\xff\xe7\xa9\x16\x92" + +func RegisterSchema(reg *schemas.Registry) { + reg.Register(&schemas.Schema{ + String: schema_ead48f650c32a806, + Nodes: []uint64{ + 0x846071f72bde13bb, + 0x988f94f380d5b032, + 0xa71b7c75f8fd8005, + 0xac24ea5bb35da196, + 0xb391d0fa84c21d71, + 0xbbafeb3e01e71170, + 0xbd4bc3d338b68790, + 0xbd4ee25c19bae4a0, + 0xc385438ec56c4e58, + 0xd15c12b2b2c5d94e, + 0xd91363a073d0485a, + 0xd97c0d57b5fd9e5d, + 0xe6195e5a8613fc66, + 0xeb0e5010d1b59026, + 0xef0707db310901e8, + 0xfae584cad6e82c9b, + }, + Compressed: true, + }) +} diff --git a/cmd/ww/shell/shell.go b/cmd/ww/shell/shell.go new file mode 100644 index 0000000..5684d3f --- /dev/null +++ b/cmd/ww/shell/shell.go @@ -0,0 +1,407 @@ +//go:generate capnp compile -I.. -I$GOPATH/src/capnproto.org/go/capnp/std -ogo shell.capnp + +package shell + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "strings" + + "capnproto.org/go/capnp/v3/rpc" + "github.com/chzyer/readline" + "github.com/ipfs/boxo/path" + "github.com/spy16/slurp" + "github.com/spy16/slurp/builtin" + "github.com/spy16/slurp/core" + "github.com/urfave/cli/v2" + + "github.com/spy16/slurp/repl" + "github.com/wetware/go/cmd/internal/flags" + "github.com/wetware/go/system" + "github.com/wetware/go/util" +) + +var env util.IPFSEnv + +func Command() *cli.Command { + return &cli.Command{ + Name: "shell", + Before: func(c *cli.Context) error { + addr := c.String("ipfs") + if err := env.Boot(addr); err != nil { + return fmt.Errorf("failed to boot IPFS environment: %w", err) + } + return nil + }, + After: func(c *cli.Context) error { + return env.Close() + }, + Action: Main, + Flags: append([]cli.Flag{ + &cli.StringFlag{ + Name: "ipfs", + EnvVars: []string{"WW_IPFS"}, + Value: "/dns4/localhost/tcp/5001/http", + }, + &cli.StringFlag{ + Name: "command", + Aliases: []string{"c"}, + Usage: "execute a single command and exit", + }, + &cli.StringFlag{ + Name: "history-file", + Usage: "path to readline history file", + Value: "/tmp/ww-shell.tmp", + EnvVars: []string{"WW_SHELL_HISTORY"}, + }, + &cli.StringFlag{ + Name: "prompt", + Usage: "shell prompt string", + Value: "ww> ", + EnvVars: []string{"WW_SHELL_PROMPT"}, + }, + &cli.BoolFlag{ + Name: "no-banner", + Usage: "disable welcome banner", + EnvVars: []string{"WW_SHELL_NO_BANNER"}, + }, + }, flags.CapabilityFlags()...), + } +} + +func Main(c *cli.Context) error { + // Check if we're in guest mode (cell process) + if os.Getenv("WW_CELL") == "true" { + return runGuestMode(c) + } + + // Host mode: spawn guest process with ww run + return runHostMode(c) +} + +// runHostMode runs the shell in host mode, spawning a guest process +func runHostMode(c *cli.Context) error { + // Get the current executable path + execPath, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to get executable path: %w", err) + } + + // Build the command to run the shell in guest mode + cmd := exec.CommandContext(c.Context, execPath, "run", "-env", "WW_CELL=true") + + // Pass through capability flags to the run command + if c.Bool("with-ipfs") { + cmd.Args = append(cmd.Args, "--with-ipfs") + } + if c.Bool("with-exec") { + cmd.Args = append(cmd.Args, "--with-exec") + } + if c.Bool("with-console") { + cmd.Args = append(cmd.Args, "--with-console") + } + if c.Bool("with-all") { + cmd.Args = append(cmd.Args, "--with-all") + } + + // Add the executable and shell command + cmd.Args = append(cmd.Args, execPath, "--", "shell") + + // Pass through capability flags to the shell command as well + if c.Bool("with-ipfs") { + cmd.Args = append(cmd.Args, "--with-ipfs") + } + if c.Bool("with-exec") { + cmd.Args = append(cmd.Args, "--with-exec") + } + if c.Bool("with-console") { + cmd.Args = append(cmd.Args, "--with-console") + } + if c.Bool("with-all") { + cmd.Args = append(cmd.Args, "--with-all") + } + + // Pass through shell-specific flags + if command := c.String("command"); command != "" { + cmd.Args = append(cmd.Args, "-c", command) + } + if historyFile := c.String("history-file"); historyFile != "" { + cmd.Args = append(cmd.Args, "--history-file", historyFile) + } + if prompt := c.String("prompt"); prompt != "" { + cmd.Args = append(cmd.Args, "--prompt", prompt) + } + if c.Bool("no-banner") { + cmd.Args = append(cmd.Args, "--no-banner") + } + + // Set up stdio + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + // Run the command + return cmd.Run() +} + +// runGuestMode runs the shell in guest mode (as a cell process) +func runGuestMode(c *cli.Context) error { + // Check if the bootstrap file descriptor exists + host := os.NewFile(system.BOOTSTRAP_FD, "host") + if host == nil { + return fmt.Errorf("failed to create bootstrap file descriptor") + } + + // Check if command flag is provided + if command := c.String("command"); command != "" { + // Execute single command + if err := executeCommand(c, command, host); err != nil { + return fmt.Errorf("repl error: %w", err) + } + return nil + } + + conn := rpc.NewConn(rpc.NewStreamTransport(host), &rpc.Options{ + BaseContext: func() context.Context { return c.Context }, + // BootstrapClient: export(), + }) + defer conn.Close() + + client := conn.Bootstrap(c.Context) + defer client.Release() + + f, release := system.Terminal(client).Login(c.Context, nil) + defer release() + + res, err := f.Struct() + if err != nil { + return fmt.Errorf("failed to resolve terminal login: %w", err) + } + + // Create base environment with analyzer and globals to it. + eval := slurp.New() + // Bind base globals (common to both modes) + if err := eval.Bind(getBaseGlobals(c)); err != nil { + return fmt.Errorf("failed to bind base globals: %w", err) + } + // Bind session-specific globals (interactive mode only) + if err := eval.Bind(NewSessionGlobals(c, &res)); err != nil { + return fmt.Errorf("failed to bind session globals: %w", err) + } + + rl, err := readline.NewEx(&readline.Config{ + Prompt: c.String("prompt"), + HistoryFile: c.String("history-file"), + AutoComplete: getCompleter(c), + InterruptPrompt: "^C", + EOFPrompt: "exit", + }) + if err != nil { + return fmt.Errorf("readline: %w", err) + } + defer rl.Close() + + // Configure banner + var banner string + if !c.Bool("no-banner") { + banner = "Welcome to Wetware Shell! Type 'help' for available commands." + } + + if err := repl.New(eval, + repl.WithBanner(banner), + repl.WithPrompts("ww ", " | "), + repl.WithPrinter(printer{out: os.Stdout}), + repl.WithReaderFactory(DefaultReaderFactory{IPFS: env.IPFS}), + repl.WithInput(lineReader{Driver: rl}, func(err error) error { + if err == nil || err == readline.ErrInterrupt { + return nil + } + return err + }), + ).Loop(c.Context); err != nil { + return fmt.Errorf("repl: %w", err) + } + return nil +} + +// executeCommand executes a single command line with a specific host file descriptor +func executeCommand(c *cli.Context, command string, host *os.File) error { + conn := rpc.NewConn(rpc.NewStreamTransport(host), &rpc.Options{ + BaseContext: func() context.Context { return c.Context }, + // BootstrapClient: export(), + }) + defer conn.Close() + + client := conn.Bootstrap(c.Context) + defer client.Release() + + f, release := system.Terminal(client).Login(c.Context, nil) + defer release() + + // Resolve the future to get the actual results + res, err := f.Struct() + if err != nil { + return fmt.Errorf("failed to resolve terminal login: %w", err) + } + + // Create base environment with analyzer and globals to it. + eval := slurp.New() + + // Bind base globals (common to both modes) + if err := eval.Bind(getBaseGlobals(c)); err != nil { + return fmt.Errorf("failed to bind base globals: %w", err) + } + + // Bind session-specific globals (including executor if --with-exec is set) + if err := eval.Bind(NewSessionGlobals(c, &res)); err != nil { + return fmt.Errorf("failed to bind session globals: %w", err) + } + + // Create a reader from the command string + commandReader := strings.NewReader(command) + + // Create a reader factory for IPFS path support + readerFactory := DefaultReaderFactory{IPFS: env.IPFS} + + // Read and evaluate the command directly + reader := readerFactory.NewReader(commandReader) + + // Read the expression + expr, err := reader.One() + if err != nil { + if err == io.EOF { + return nil // Empty command, nothing to do + } + return fmt.Errorf("failed to read command: %w", err) + } + + // Evaluate the expression + result, err := eval.Eval(expr) + if err != nil { + return fmt.Errorf("failed to evaluate command: %w", err) + } + + // Print the result if it's not nil + if result != nil { + printer := printer{out: os.Stdout} + return printer.Print(result) + } + + return nil +} + +// printer implements the repl.Printer interface for better output formatting +type printer struct { + out io.Writer +} + +func (p printer) Print(val interface{}) error { + switch v := val.(type) { + case nil: + _, err := fmt.Fprintf(p.out, "nil\n") + return err + case builtin.Bool: + _, err := fmt.Fprintf(p.out, "%t\n", bool(v)) + return err + case builtin.Int64: + _, err := fmt.Fprintf(p.out, "%d\n", int64(v)) + return err + case builtin.String: + _, err := fmt.Fprintf(p.out, "%s\n", string(v)) + return err + case builtin.Float64: + _, err := fmt.Fprintf(p.out, "%g\n", float64(v)) + return err + case builtin.Nil: + _, err := fmt.Fprintf(p.out, "nil\n") + return err + case path.Path: + _, err := fmt.Fprintf(p.out, "Path: %s\n", v.String()) + return err + default: + // For any other type, use Go's default formatting + _, err := fmt.Fprintf(p.out, "%v\n", v) + return err + } +} + +// lineReader implements the repl.Input interface using readline +type lineReader struct { + Driver *readline.Instance +} + +func (s lineReader) Readline() (string, error) { + line, err := s.Driver.Readline() + return line, err +} + +// Prompt implements the repl.Prompter interface +func (s lineReader) Prompt(p string) { + s.Driver.SetPrompt(p) +} + +// getCompleter returns a readline completer for wetware commands +func getCompleter(c *cli.Context) readline.AutoCompleter { + completers := []readline.PrefixCompleterInterface{ + readline.PcItem("help"), + readline.PcItem("version"), + readline.PcItem("println"), + readline.PcItem("print"), + readline.PcItem("send"), + readline.PcItem("system"), + readline.PcItem("callc"), + readline.PcItem("+"), + readline.PcItem("*"), + readline.PcItem("="), + readline.PcItem(">"), + readline.PcItem("<"), + readline.PcItem("nil"), + readline.PcItem("true"), + readline.PcItem("false"), + } + + if c.Bool("with-ipfs") || c.Bool("with-all") { + completers = append(completers, readline.PcItem("ipfs")) + } + if c.Bool("with-exec") || c.Bool("with-all") { + completers = append(completers, readline.PcItem("exec")) + } + + return readline.NewPrefixCompleter(completers...) +} + +// getBaseGlobals returns the base globals that are common to both interactive and command modes +func getBaseGlobals(c *cli.Context) map[string]core.Any { + gs := make(map[string]core.Any, len(globals)) + + // Copy the base globals from globals.go + for k, v := range globals { + gs[k] = v + } + + // Add IPFS support if --with-ipfs flag is set + if c.Bool("with-ipfs") || c.Bool("with-all") { + if env.IPFS != nil { + gs["ipfs"] = &IPFS{CoreAPI: env.IPFS} + } else { + panic("uninitialized IPFS environment") + } + } + + return gs +} + +// NewSessionGlobals returns additional globals for interactive mode (requires terminal connection) +func NewSessionGlobals(c *cli.Context, f *system.Terminal_login_Results) map[string]core.Any { + session := make(map[string]core.Any) + + // Add exec functionality if --with-exec flag is set + if c.Bool("with-exec") || c.Bool("with-all") { + session["exec"] = &Exec{Session: f} + } + + return session +} diff --git a/cmd/ww/shell/shell_test.go b/cmd/ww/shell/shell_test.go new file mode 100644 index 0000000..5735f50 --- /dev/null +++ b/cmd/ww/shell/shell_test.go @@ -0,0 +1,321 @@ +package shell + +import ( + "bytes" + "context" + "flag" + "fmt" + "io" + "os" + "strings" + "testing" + + "github.com/spy16/slurp" + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" +) + +// createMockCLIContext creates a mock CLI context for testing +func createMockCLIContext() *cli.Context { + app := &cli.App{} + app.Flags = []cli.Flag{} + flagSet := &flag.FlagSet{} + flagSet.Bool("with-ipfs", false, "Enable IPFS capability") // Set to false for tests + flagSet.Bool("with-exec", true, "Enable exec capability") + flagSet.Bool("with-console", true, "Enable console capability") + flagSet.Bool("with-all", false, "Enable all capabilities") + flagSet.String("prompt", "ww> ", "Shell prompt") + flagSet.String("history-file", "/tmp/ww_history", "History file path") + ctx := cli.NewContext(app, flagSet, nil) + ctx.Context = context.Background() + return ctx +} + +// executeCommandForTesting executes a command without RPC setup for testing +func executeCommandForTesting(c *cli.Context, command string) error { + // Create base environment with analyzer and globals + eval := slurp.New() + + // Bind base globals (common to both modes) + if err := eval.Bind(getBaseGlobals(c)); err != nil { + return fmt.Errorf("failed to bind base globals: %w", err) + } + + // Create a reader from the command string + commandReader := strings.NewReader(command) + + // Create a reader factory for IPFS path support + readerFactory := DefaultReaderFactory{IPFS: nil} // No IPFS in tests + + // Read and evaluate the command directly + reader := readerFactory.NewReader(commandReader) + + // Read the expression + expr, err := reader.One() + if err != nil { + if err == io.EOF { + return nil // Empty command, nothing to do + } + return fmt.Errorf("failed to read command: %w", err) + } + + // Evaluate the expression + result, err := eval.Eval(expr) + if err != nil { + return fmt.Errorf("failed to evaluate command: %w", err) + } + + // Print the result if it's not nil + if result != nil { + printer := printer{out: os.Stdout} + return printer.Print(result) + } + + return nil +} + +func TestExecuteCommand(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + command string + wantError bool + }{ + { + name: "simple arithmetic", + command: "(+ 1 2 3)", + wantError: false, + }, + { + name: "multiplication", + command: "(* 2 3 4)", + wantError: false, + }, + { + name: "comparison", + command: "(> 5 3)", + wantError: false, + }, + { + name: "equality", + command: "(= 5 5)", + wantError: false, + }, + { + name: "boolean values", + command: "true", + wantError: false, + }, + { + name: "nil value", + command: "nil", + wantError: false, + }, + { + name: "version", + command: "version", + wantError: false, + }, + { + name: "help function", + command: "(help)", + wantError: false, + }, + { + name: "println function", + command: "(println \"Hello World\")", + wantError: false, + }, + { + name: "nested expressions", + command: "(+ (* 2 3) 4)", + wantError: false, + }, + { + name: "invalid syntax", + command: "(+ 1 2", + wantError: true, + }, + { + name: "unknown function", + command: "(unknown-function 1 2)", + wantError: true, + }, + { + name: "empty command", + command: "", + wantError: false, + }, + { + name: "whitespace only", + command: " ", + wantError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := executeCommandForTesting(createMockCLIContext(), tt.command) + + if tt.wantError { + assert.Error(t, err, "Expected error for command: %s", tt.command) + } else { + assert.NoError(t, err, "Expected no error for command: %s", tt.command) + } + }) + } +} + +func TestExecuteCommandWithIPFS(t *testing.T) { + t.Parallel() + + // These tests will fail if IPFS is not available, but they test the structure + tests := []struct { + name string + command string + wantError bool + }{ + { + name: "ipfs function exists", + command: "ipfs", + wantError: true, // IPFS not available in test environment + }, + { + name: "ipfs cat with invalid path", + command: "(ipfs :cat \"/invalid/path\")", + wantError: true, // Should fail with invalid path + }, + { + name: "ipfs get with invalid path", + command: "(ipfs :get \"/invalid/path\")", + wantError: true, // Should fail with invalid path + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := executeCommandForTesting(createMockCLIContext(), tt.command) + + if tt.wantError { + assert.Error(t, err, "Expected error for command: %s", tt.command) + } else { + assert.NoError(t, err, "Expected no error for command: %s", tt.command) + } + }) + } +} + +func TestGetCompleter(t *testing.T) { + t.Parallel() + + completer := getCompleter(createMockCLIContext()) + assert.NotNil(t, completer, "Completer should not be nil") + + // Test that completer can be used without panicking + assert.NotPanics(t, func() { + completer.Do([]rune("help"), 0) // HACK: zero was a wild guess + }) +} + +func TestPrinter(t *testing.T) { + t.Parallel() + + // Create a test writer to avoid nil pointer panics + testWriter := &bytes.Buffer{} + printer := &printer{out: testWriter} + + // Test that printer can handle different types without panicking + testCases := []interface{}{ + nil, + "hello", + 42, + true, + false, + []byte("bytes"), + } + + for _, tc := range testCases { + assert.NotPanics(t, func() { + printer.Print(tc) + }, "Printer should handle type %T without panicking", tc) + } +} + +func TestCommandStructure(t *testing.T) { + t.Parallel() + + cmd := Command() + + // Test that command has expected properties + assert.Equal(t, "shell", cmd.Name) + assert.NotNil(t, cmd.Action) + assert.NotNil(t, cmd.Before) + assert.NotNil(t, cmd.After) + assert.NotEmpty(t, cmd.Flags) + + // Test that expected flags are present + flagNames := make([]string, len(cmd.Flags)) + for i, flag := range cmd.Flags { + flagNames[i] = flag.Names()[0] + } + + expectedFlags := []string{"ipfs", "command", "history-file", "prompt", "no-banner"} + for _, expected := range expectedFlags { + assert.Contains(t, flagNames, expected, "Command should have flag: %s", expected) + } +} + +func TestGlobalsIntegration(t *testing.T) { + t.Parallel() + + baseGlobals := getBaseGlobals(createMockCLIContext()) + + // Test that all expected functions are present and callable + expectedFunctions := []string{"+", "*", ">", "<", "=", "/", "help", "println", "print"} + + for _, funcName := range expectedFunctions { + funcVal, exists := baseGlobals[funcName] + assert.True(t, exists, "Function %s should exist in base globals", funcName) + assert.NotNil(t, funcVal, "Function %s should not be nil", funcName) + } + + // Test that basic values are present + expectedValues := []string{"nil", "true", "false", "version"} + + for _, valName := range expectedValues { + val, exists := baseGlobals[valName] + assert.True(t, exists, "Value %s should exist in base globals", valName) + assert.NotNil(t, val, "Value %s should not be nil", valName) + } +} + +func TestArithmeticIntegration(t *testing.T) { + t.Parallel() + + // Test that arithmetic works in command execution + arithmeticTests := []struct { + command string + expectError bool + }{ + {"(+ 1 2)", false}, + {"(* 2 3)", false}, + {"(+ 1 2 3 4 5)", false}, + {"(* 2 3 4)", false}, + {"(> 5 3)", false}, + {"(< 3 5)", false}, + {"(= 5 5)", false}, + {"(* 10 0.5)", false}, + } + + for _, tt := range arithmeticTests { + t.Run(tt.command, func(t *testing.T) { + err := executeCommandForTesting(createMockCLIContext(), tt.command) + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/examples/echo/go.mod b/examples/echo/go.mod new file mode 100644 index 0000000..979d16d --- /dev/null +++ b/examples/echo/go.mod @@ -0,0 +1,3 @@ +module github.com/wetware/go/examples/echo + +go 1.25.1 diff --git a/examples/echo/main.go b/examples/echo/main.go new file mode 100644 index 0000000..cc7a4e7 --- /dev/null +++ b/examples/echo/main.go @@ -0,0 +1,21 @@ +//go:generate tinygo build -o main.wasm -target=wasi -scheduler=none main.go + +package main + +import ( + "io" + "log/slog" + "os" +) + +func main() {} + +// Echo function that can be called from the WASM module +// +//export poll +func poll() { + var buf [512]byte + if n, err := io.CopyBuffer(os.Stdout, os.Stdin, buf[:]); err != nil { + slog.Error("failed to copy", "reason", err, "written", n) + } +} diff --git a/examples/echo/main.wasm b/examples/echo/main.wasm new file mode 100644 index 0000000..8cb4b51 Binary files /dev/null and b/examples/echo/main.wasm differ diff --git a/examples/export/cap/export.capnp.go b/examples/export/cap/export.capnp.go index 3d3f93a..b3012b1 100644 --- a/examples/export/cap/export.capnp.go +++ b/examples/export/cap/export.capnp.go @@ -166,7 +166,7 @@ func (c Greeter_greet) AllocResults() (Greeter_greet_Results, error) { // Greeter_List is a list of Greeter. type Greeter_List = capnp.CapList[Greeter] -// NewGreeter_List creates a new list of Greeter. +// NewGreeter creates a new list of Greeter. func NewGreeter_List(s *capnp.Segment, sz int32) (Greeter_List, error) { l, err := capnp.NewPointerList(s, sz) return capnp.CapList[Greeter](l), err diff --git a/examples/export/export b/examples/export/export new file mode 100755 index 0000000..3354181 Binary files /dev/null and b/examples/export/export differ diff --git a/examples/fd-demo/demo.txt b/examples/fd-demo/demo.txt new file mode 100644 index 0000000..1d46e91 --- /dev/null +++ b/examples/fd-demo/demo.txt @@ -0,0 +1 @@ +Hello from demo file diff --git a/examples/fd-demo/fd-demo b/examples/fd-demo/fd-demo new file mode 100755 index 0000000..b2a6294 Binary files /dev/null and b/examples/fd-demo/fd-demo differ diff --git a/examples/fd-demo/log.txt b/examples/fd-demo/log.txt new file mode 100644 index 0000000..6e97805 --- /dev/null +++ b/examples/fd-demo/log.txt @@ -0,0 +1 @@ +Log message from log file diff --git a/examples/shell/README.md b/examples/shell/README.md deleted file mode 100644 index 5fe39d2..0000000 --- a/examples/shell/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# Shell Example - -Production-grade REPL shell built with the Wetware framework and Slurp LISP toolkit. - -## Features - -- **REPL**: Built using `github.com/spy16/slurp` for LISP interpretation -- **Readline**: Uses `github.com/chzyer/readline` for terminal experience -- **Wetware Integration**: Runs within Wetware cell environment with system capabilities -- **Function Registry**: Extensible set of built-in functions - -## Available Functions - -### Basic Values -- `nil` - null value -- `true` / `false` - boolean values -- `version` - wetware version string - -### Arithmetic -- `(+ a b ...)` - sum of numbers -- `(* a b ...)` - product of numbers -- `(/ a b)` - division (returns float) - -### Comparison -- `(= a b)` - equality comparison -- `(> a b)` - greater than -- `(< a b)` - less than - -### Utilities -- `help` - display help message -- `println expr` - print expression with newline -- `print expr` - print expression without newline - -## Usage - -```bash -# Build -go build -o wetware-shell - -# Run (requires wetware environment) -./wetware-shell - -# Or run directly -go run main.go -``` - -## Example Session - -``` -Welcome to Wetware Shell! Type 'help' for available commands. -ww> help -Wetware Shell - Available commands: - help - Show this help message - version - Show wetware version - (+ a b ...) - Sum numbers - (* a b ...) - Multiply numbers - (= a b) - Compare equality - (> a b) - Greater than - (< a b) - Less than - (println expr) - Print expression with newline - (print expr) - Print expression without newline - -ww> (+ 1 2 3 4) -10 -ww> (* 2 3 4) -24 -ww> (> 10 5) -true -ww> (println "Hello, Wetware!") -Hello, Wetware! -ww> -``` - -## Architecture - -1. **Wetware Environment**: Integrates with Wetware cell system for capabilities -2. **Slurp Interpreter**: Provides LISP evaluation engine -3. **Custom REPL**: Production-grade read-eval-print loop with error handling -4. **Readline Integration**: Enhanced terminal input with history and completion -5. **Function Registry**: Extensible set of built-in functions - -## Extending - -To add new functions, modify the `createWetwareEnvironment` function in `main.go`: - -```go -"my-function": slurp.Func("my-function", func(args ...core.Any) { - // Implementation - return result -}), -``` - -## Dependencies - -- `github.com/spy16/slurp` - LISP toolkit -- `github.com/chzyer/readline` - Terminal readline support -- `capnproto.org/go/capnp/v3` - Cap'n Proto RPC -- `github.com/wetware/go` - Wetware framework diff --git a/examples/shell/expr.go b/examples/shell/expr.go deleted file mode 100644 index fd35d3c..0000000 --- a/examples/shell/expr.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "context" - "runtime" - "time" - - "capnproto.org/go/capnp/v3" - "github.com/libp2p/go-libp2p/core/record" - "github.com/spy16/slurp/core" - "github.com/wetware/go/system" -) - -// ImportExpr implements core.Expr for import statements -type ImportExpr struct { - Client system.Importer - Envelope *record.Envelope - Timeout time.Duration -} - -func (e ImportExpr) NewEvalContext() (context.Context, context.CancelFunc) { - if e.Timeout < 0 { - return context.WithCancel(context.Background()) - } else if e.Timeout == 0 { - e.Timeout = 10 * time.Second - } - - return context.WithTimeout(context.Background(), e.Timeout) -} - -func (e ImportExpr) Eval(env core.Env) (core.Any, error) { - ctx, cancel := e.NewEvalContext() - defer cancel() - - f, release := e.Client.Import(ctx, e.SetEnvelope) - runtime.SetFinalizer(f.Future, func(*capnp.Future) { - release() - }) - - return f, nil -} - -func (e ImportExpr) SetEnvelope(p system.Importer_import_Params) error { - b, err := e.Envelope.Marshal() - if err != nil { - return err - } - - return p.SetEnvelope(b) -} diff --git a/examples/shell/main.go b/examples/shell/main.go deleted file mode 100644 index 765dffa..0000000 --- a/examples/shell/main.go +++ /dev/null @@ -1,341 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "os" - "strings" - - "capnproto.org/go/capnp/v3/rpc" - "github.com/chzyer/readline" - "github.com/ipfs/boxo/path" - "github.com/libp2p/go-libp2p/core/record" - "github.com/spy16/slurp" - "github.com/spy16/slurp/builtin" - "github.com/spy16/slurp/core" - - "github.com/spy16/slurp/repl" - "github.com/wetware/go/system" -) - -var config = readline.Config{ - Prompt: "ww> ", - HistoryFile: "/tmp/ww-shell.tmp", - AutoComplete: getCompleter(), - InterruptPrompt: "^C", - EOFPrompt: "exit", -} - -func main() { - ctx := context.Background() - - // Check command line arguments for -c flag - if len(os.Args) > 1 && os.Args[1] == "-c" { - if len(os.Args) < 3 { - fmt.Fprintf(os.Stderr, "Usage: %s -c \n", os.Args[0]) - os.Exit(1) - } - - // Execute single command - if err := executeCommand(ctx, os.Args[2]); err != nil { - fmt.Fprintf(os.Stderr, "repl error: %s\n", err) - os.Exit(1) - } - return - } - - // Check if the bootstrap file descriptor exists - host := os.NewFile(system.BOOTSTRAP_FD, "host") - if host == nil { - fmt.Fprintf(os.Stderr, "ERROR: Failed to create bootstrap file descriptor\n") - os.Exit(1) - } - - conn := rpc.NewConn(rpc.NewStreamTransport(host), &rpc.Options{ - BaseContext: func() context.Context { return ctx }, - // BootstrapClient: export(), - }) - defer conn.Close() - - client := conn.Bootstrap(ctx) - defer client.Release() - - if err := runREPL(ctx, system.Importer(client)); err != nil { - fmt.Fprintf(os.Stderr, "repl error: %s\n", err) - os.Exit(1) - } -} - -func runREPL(ctx context.Context, i system.Importer) error { - // Create readline instance - rl, err := readline.NewEx(&config) - if err != nil { - return err - } - defer rl.Close() - - // Create the REPL with custom options using our custom reader factory - return repl.New(newInterpreter(i), - repl.WithBanner("Welcome to Wetware Shell! Type 'help' for available commands."), - repl.WithPrompts("ww ", " | "), - repl.WithPrinter(printer{out: os.Stdout}), - repl.WithReaderFactory(DefaultReaderFactory{}), - repl.WithInput(lineReader{Driver: rl}, func(err error) error { - if err == nil || err == readline.ErrInterrupt { - return nil - } - return err - }), - ).Loop(ctx) -} - -// executeCommand executes a single command line -func executeCommand(ctx context.Context, command string) error { - // Create a basic interpreter without import functionality for testing - env := slurp.New() - - // Add basic globals for testing - globals := map[string]core.Any{ - "nil": builtin.Nil{}, - "true": builtin.Bool(true), - "false": builtin.Bool(false), - "version": builtin.String("wetware-0.1.0"), - "println": slurp.Func("println", func(args ...core.Any) { - for _, arg := range args { - fmt.Println(arg) - } - }), - "print": slurp.Func("print", func(args ...core.Any) { - for _, arg := range args { - fmt.Print(arg) - } - }), - } - - if err := env.Bind(globals); err != nil { - return fmt.Errorf("failed to bind globals: %w", err) - } - - // Create a reader from the command string - commandReader := strings.NewReader(command) - - // Create the REPL with the command reader - return repl.New(env, - repl.WithBanner(""), // No banner for single command execution - repl.WithPrompts("", ""), // No prompts for single command execution - repl.WithPrinter(printer{out: os.Stdout}), - repl.WithReaderFactory(DefaultReaderFactory{}), - repl.WithInput(commandReaderWrapper{Reader: commandReader}, func(err error) error { - if err != nil && err != io.EOF { - return err - } - return nil - }), - ).Loop(ctx) -} - -// commandReaderWrapper implements the repl.Input interface for single command execution -type commandReaderWrapper struct { - *strings.Reader -} - -func (r commandReaderWrapper) Readline() (string, error) { - // Read a line from the strings.Reader - buf := make([]byte, 0, 1024) - for { - if b, err := r.ReadByte(); err != nil { - if err == io.EOF { - break - } - return "", err - } else if b == '\n' { - break - } else { - buf = append(buf, b) - } - } - return strings.TrimSpace(string(buf)), nil -} - -func (r commandReaderWrapper) Prompt(p string) { - // No prompting for single command execution -} - -// Path represents an IPFS path and implements core.Any -type Path struct { - path.Path -} - -func (p Path) String() string { - return p.Path.String() -} - -// newInterpreter creates a slurp environment with wetware-specific functions -func newInterpreter(i system.Importer) *slurp.Interpreter { - // Create analyzer for special forms - analyzer := &builtin.Analyzer{ - Specials: make(map[string]builtin.ParseSpecial), - } - - // Only add import special form if importer is available - if i.IsValid() { - analyzer.Specials["import"] = func(analyzer core.Analyzer, env core.Env, args core.Seq) (core.Expr, error) { - v, err := args.First() - if err != nil { - return nil, err - } - - e, ok := v.(*record.Envelope) - if !ok { - return nil, fmt.Errorf("expected envelope, got %T", v) - } - - return &ImportExpr{Client: i, Envelope: e}, nil - } - } - - // Create base environment with analyzer - env := slurp.New(slurp.WithAnalyzer(analyzer)) - - // Add wetware-specific globals - globals := map[string]core.Any{ - // Basic values - "nil": builtin.Nil{}, - "true": builtin.Bool(true), - "false": builtin.Bool(false), - "version": builtin.String("wetware-0.1.0"), - - // Basic operations - "=": slurp.Func("=", core.Eq), - "+": slurp.Func("sum", func(a ...int) int { - sum := 0 - for _, item := range a { - sum += item - } - return sum - }), - ">": slurp.Func(">", func(a, b builtin.Int64) bool { - return a > b - }), - "<": slurp.Func("<", func(a, b builtin.Int64) bool { - return a < b - }), - "*": slurp.Func("*", func(a ...int) int { - product := 1 - for _, item := range a { - product *= item - } - return product - }), - "/": slurp.Func("/", func(a, b builtin.Int64) float64 { - return float64(a) / float64(b) - }), - - // Wetware-specific functions - "help": slurp.Func("help", func() string { - return `Wetware Shell - Available commands: - help - Show this help message - version - Show wetware version - (+ a b ...) - Sum numbers - (* a b ...) - Multiply numbers - (= a b) - Compare equality - (> a b) - Greater than - (< a b) - Less than - (println expr) - Print expression with newline - (print expr) - Print expression without newline - (import "module") - Import a module (stubbed) - - IPFS Path Syntax: - /ipfs/QmHash/... - Direct IPFS path - /ipns/domain/... - IPNS path` - }), - "println": slurp.Func("println", func(args ...core.Any) { - for _, arg := range args { - fmt.Println(arg) - } - }), - "print": slurp.Func("print", func(args ...core.Any) { - for _, arg := range args { - fmt.Print(arg) - } - }), - } - - // Bind the globals to the environment - if err := env.Bind(globals); err != nil { - fmt.Fprintf(os.Stderr, "failed to bind globals: %v\n", err) - } - - return env -} - -// printer implements the repl.Printer interface for better output formatting -type printer struct { - out io.Writer -} - -func (p printer) Print(val interface{}) error { - switch v := val.(type) { - case nil: - _, err := fmt.Fprintf(p.out, "nil\n") - return err - case builtin.Bool: - _, err := fmt.Fprintf(p.out, "%t\n", bool(v)) - return err - case builtin.Int64: - _, err := fmt.Fprintf(p.out, "%d\n", int64(v)) - return err - case builtin.String: - _, err := fmt.Fprintf(p.out, "%s\n", string(v)) - return err - case builtin.Float64: - _, err := fmt.Fprintf(p.out, "%g\n", float64(v)) - return err - case builtin.Nil: - _, err := fmt.Fprintf(p.out, "nil\n") - return err - case Path: - _, err := fmt.Fprintf(p.out, "Path: %s\n", v.String()) - return err - default: - // For any other type, use Go's default formatting - _, err := fmt.Fprintf(p.out, "%v\n", v) - return err - } -} - -// lineReader implements the repl.Input interface using readline -type lineReader struct { - Driver *readline.Instance -} - -func (s lineReader) Readline() (string, error) { - line, err := s.Driver.Readline() - return line, err -} - -// Prompt implements the repl.Prompter interface -func (s lineReader) Prompt(p string) { - s.Driver.SetPrompt(p) -} - -// getCompleter returns a readline completer for wetware commands -func getCompleter() readline.AutoCompleter { - return readline.NewPrefixCompleter( - readline.PcItem("help"), - readline.PcItem("version"), - readline.PcItem("println"), - readline.PcItem("print"), - readline.PcItem("system"), - readline.PcItem("callc"), - readline.PcItem("+"), - readline.PcItem("*"), - readline.PcItem("="), - readline.PcItem(">"), - readline.PcItem("<"), - readline.PcItem("nil"), - readline.PcItem("true"), - readline.PcItem("false"), - ) -} diff --git a/examples/shell/path.go b/examples/shell/path.go deleted file mode 100644 index afc3023..0000000 --- a/examples/shell/path.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "fmt" - "io" - "strings" - "unicode" - - "github.com/ipfs/boxo/path" - "github.com/spy16/slurp/core" - "github.com/spy16/slurp/reader" -) - -// IPFSPathReader is a ReaderMacro that handles IPFS and IPNS paths -func IPFSPathReader(rd *reader.Reader, init rune) (core.Any, error) { - beginPos := rd.Position() - - // Read the full path manually by reading runes until we hit whitespace or a delimiter - var pathBuilder strings.Builder - pathBuilder.WriteRune(init) // Start with the '/' character - - for { - r, err := rd.NextRune() - if err != nil { - if err == io.EOF { - break - } - return nil, &reader.Error{ - Cause: err, - Begin: beginPos, - End: beginPos, - } - } - - // Check if this rune should terminate the path - // Only stop on whitespace, not on forward slashes or other path characters - if unicode.IsSpace(r) { - rd.Unread(r) - break - } - - pathBuilder.WriteRune(r) - } - - pathStr := pathBuilder.String() - - // Try to create an IPFS path - path.NewPath will validate the format - ipfsPath, err := path.NewPath(pathStr) - if err == nil { - // Successfully created an IPFS path - return Path{Path: ipfsPath}, nil - } - - // If path.NewPath failed, it's a syntax error - return nil, &reader.Error{ - Cause: fmt.Errorf("invalid IPFS/IPNS path: %s", err), - Begin: beginPos, - End: beginPos, - } -} - -// DefaultReaderFactory creates readers with IPFS path support -type DefaultReaderFactory struct{} - -func (f DefaultReaderFactory) NewReader(r io.Reader) *reader.Reader { - rd := reader.New(r) - rd.SetMacro('/', false, IPFSPathReader) - return rd -} diff --git a/go.mod b/go.mod index 3048da9..122074e 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,16 @@ require ( github.com/ipfs/kubo v0.31.0 github.com/libp2p/go-libp2p v0.41.0 github.com/lmittmann/tint v1.0.4 + github.com/lthibault/go-libp2p-inproc-transport v0.4.1 github.com/mr-tron/base58 v1.2.0 github.com/multiformats/go-multiaddr v0.15.0 github.com/spy16/slurp v0.3.0 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 + github.com/tetratelabs/wazero v1.9.0 github.com/urfave/cli/v2 v2.27.5 + go.uber.org/mock v0.5.0 go.uber.org/multierr v1.11.0 + golang.org/x/sync v0.11.0 ) require ( @@ -89,6 +93,7 @@ require ( github.com/libp2p/go-netroute v0.2.2 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v5 v5.0.0 // indirect + github.com/lthibault/util v0.0.12 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/miekg/dns v1.1.63 // indirect @@ -158,14 +163,12 @@ require ( go.opentelemetry.io/otel/trace v1.34.0 // indirect go.uber.org/dig v1.18.0 // indirect go.uber.org/fx v1.23.0 // indirect - go.uber.org/mock v0.5.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 // indirect golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.36.0 // indirect - golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/tools v0.30.0 // indirect diff --git a/go.sum b/go.sum index c566d39..fb38021 100644 --- a/go.sum +++ b/go.sum @@ -414,6 +414,10 @@ github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc= github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/lthibault/go-libp2p-inproc-transport v0.4.1 h1:tuhLlTsQ7mtBPZYFnXu3ZELk7FVAPXLmmB6hWp0uuZA= +github.com/lthibault/go-libp2p-inproc-transport v0.4.1/go.mod h1:KQxTLypXhS8rRKp8zZcbPzIwEi/O1PNR0xe9MZNPSxY= +github.com/lthibault/util v0.0.12 h1:JDulMYR1TudCGBOob14p6q6ak6/zovl20uU4Pj7umuA= +github.com/lthibault/util v0.0.12/go.mod h1:cGZBEILcwYh+UkEdWKgP1vEOlayHa5dh2ay8yixCJQ0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= @@ -621,11 +625,13 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU= github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= diff --git a/system/membrane.go b/system/membrane.go deleted file mode 100644 index a7249ea..0000000 --- a/system/membrane.go +++ /dev/null @@ -1,33 +0,0 @@ -package system - -import ( - "context" - "fmt" - - capnp "capnproto.org/go/capnp/v3" -) - -var _ Importer_Server = (*Membrane)(nil) - -type ServiceToken [20]byte // 20 bytes = 160 bits = 20 hex characters - -type Membrane map[ServiceToken]capnp.Client - -func (m Membrane) Import(ctx context.Context, call Importer_import) error { - raw, err := call.Args().Envelope() - if err != nil { - return err - } - - var token ServiceToken - if copy(token[:], raw) != len(raw) { - return fmt.Errorf("invalid service token length: got %d, want %d", len(raw), len(token)) - } - - res, err := call.AllocResults() - if err != nil { - return err - } - - return res.SetService(m[token]) -} diff --git a/system/mocks/gen.go b/system/mocks/gen.go new file mode 100644 index 0000000..2bd89dc --- /dev/null +++ b/system/mocks/gen.go @@ -0,0 +1,12 @@ +//go:generate mockgen -source=gen.go -destination=mock_stream.go -package=mocks + +package mocks + +import ( + "github.com/libp2p/go-libp2p/core/network" +) + +// StreamInterface embeds network.Stream to provide a clean interface for mocking +type StreamInterface interface { + network.Stream +} diff --git a/system/mocks/mock_stream.go b/system/mocks/mock_stream.go new file mode 100644 index 0000000..1a38acc --- /dev/null +++ b/system/mocks/mock_stream.go @@ -0,0 +1,269 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: gen.go +// +// Generated by this command: +// +// mockgen -source=gen.go -destination=mock_stream.go -package=mocks +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + time "time" + + network "github.com/libp2p/go-libp2p/core/network" + protocol "github.com/libp2p/go-libp2p/core/protocol" + gomock "go.uber.org/mock/gomock" +) + +// MockStreamInterface is a mock of StreamInterface interface. +type MockStreamInterface struct { + ctrl *gomock.Controller + recorder *MockStreamInterfaceMockRecorder + isgomock struct{} +} + +// MockStreamInterfaceMockRecorder is the mock recorder for MockStreamInterface. +type MockStreamInterfaceMockRecorder struct { + mock *MockStreamInterface +} + +// NewMockStreamInterface creates a new mock instance. +func NewMockStreamInterface(ctrl *gomock.Controller) *MockStreamInterface { + mock := &MockStreamInterface{ctrl: ctrl} + mock.recorder = &MockStreamInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStreamInterface) EXPECT() *MockStreamInterfaceMockRecorder { + return m.recorder +} + +// Close mocks base method. +func (m *MockStreamInterface) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockStreamInterfaceMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockStreamInterface)(nil).Close)) +} + +// CloseRead mocks base method. +func (m *MockStreamInterface) CloseRead() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseRead") + ret0, _ := ret[0].(error) + return ret0 +} + +// CloseRead indicates an expected call of CloseRead. +func (mr *MockStreamInterfaceMockRecorder) CloseRead() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseRead", reflect.TypeOf((*MockStreamInterface)(nil).CloseRead)) +} + +// CloseWrite mocks base method. +func (m *MockStreamInterface) CloseWrite() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseWrite") + ret0, _ := ret[0].(error) + return ret0 +} + +// CloseWrite indicates an expected call of CloseWrite. +func (mr *MockStreamInterfaceMockRecorder) CloseWrite() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseWrite", reflect.TypeOf((*MockStreamInterface)(nil).CloseWrite)) +} + +// Conn mocks base method. +func (m *MockStreamInterface) Conn() network.Conn { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Conn") + ret0, _ := ret[0].(network.Conn) + return ret0 +} + +// Conn indicates an expected call of Conn. +func (mr *MockStreamInterfaceMockRecorder) Conn() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Conn", reflect.TypeOf((*MockStreamInterface)(nil).Conn)) +} + +// ID mocks base method. +func (m *MockStreamInterface) ID() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ID") + ret0, _ := ret[0].(string) + return ret0 +} + +// ID indicates an expected call of ID. +func (mr *MockStreamInterfaceMockRecorder) ID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockStreamInterface)(nil).ID)) +} + +// Protocol mocks base method. +func (m *MockStreamInterface) Protocol() protocol.ID { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Protocol") + ret0, _ := ret[0].(protocol.ID) + return ret0 +} + +// Protocol indicates an expected call of Protocol. +func (mr *MockStreamInterfaceMockRecorder) Protocol() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Protocol", reflect.TypeOf((*MockStreamInterface)(nil).Protocol)) +} + +// Read mocks base method. +func (m *MockStreamInterface) Read(p []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Read", p) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read. +func (mr *MockStreamInterfaceMockRecorder) Read(p any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockStreamInterface)(nil).Read), p) +} + +// Reset mocks base method. +func (m *MockStreamInterface) Reset() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Reset") + ret0, _ := ret[0].(error) + return ret0 +} + +// Reset indicates an expected call of Reset. +func (mr *MockStreamInterfaceMockRecorder) Reset() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reset", reflect.TypeOf((*MockStreamInterface)(nil).Reset)) +} + +// ResetWithError mocks base method. +func (m *MockStreamInterface) ResetWithError(errCode network.StreamErrorCode) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ResetWithError", errCode) + ret0, _ := ret[0].(error) + return ret0 +} + +// ResetWithError indicates an expected call of ResetWithError. +func (mr *MockStreamInterfaceMockRecorder) ResetWithError(errCode any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetWithError", reflect.TypeOf((*MockStreamInterface)(nil).ResetWithError), errCode) +} + +// Scope mocks base method. +func (m *MockStreamInterface) Scope() network.StreamScope { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Scope") + ret0, _ := ret[0].(network.StreamScope) + return ret0 +} + +// Scope indicates an expected call of Scope. +func (mr *MockStreamInterfaceMockRecorder) Scope() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scope", reflect.TypeOf((*MockStreamInterface)(nil).Scope)) +} + +// SetDeadline mocks base method. +func (m *MockStreamInterface) SetDeadline(arg0 time.Time) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetDeadline", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetDeadline indicates an expected call of SetDeadline. +func (mr *MockStreamInterfaceMockRecorder) SetDeadline(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockStreamInterface)(nil).SetDeadline), arg0) +} + +// SetProtocol mocks base method. +func (m *MockStreamInterface) SetProtocol(id protocol.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetProtocol", id) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetProtocol indicates an expected call of SetProtocol. +func (mr *MockStreamInterfaceMockRecorder) SetProtocol(id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProtocol", reflect.TypeOf((*MockStreamInterface)(nil).SetProtocol), id) +} + +// SetReadDeadline mocks base method. +func (m *MockStreamInterface) SetReadDeadline(arg0 time.Time) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetReadDeadline", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetReadDeadline indicates an expected call of SetReadDeadline. +func (mr *MockStreamInterfaceMockRecorder) SetReadDeadline(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockStreamInterface)(nil).SetReadDeadline), arg0) +} + +// SetWriteDeadline mocks base method. +func (m *MockStreamInterface) SetWriteDeadline(arg0 time.Time) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetWriteDeadline", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetWriteDeadline indicates an expected call of SetWriteDeadline. +func (mr *MockStreamInterfaceMockRecorder) SetWriteDeadline(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockStreamInterface)(nil).SetWriteDeadline), arg0) +} + +// Stat mocks base method. +func (m *MockStreamInterface) Stat() network.Stats { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Stat") + ret0, _ := ret[0].(network.Stats) + return ret0 +} + +// Stat indicates an expected call of Stat. +func (mr *MockStreamInterfaceMockRecorder) Stat() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockStreamInterface)(nil).Stat)) +} + +// Write mocks base method. +func (m *MockStreamInterface) Write(p []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", p) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Write indicates an expected call of Write. +func (mr *MockStreamInterfaceMockRecorder) Write(p any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockStreamInterface)(nil).Write), p) +} diff --git a/system/proc.go b/system/proc.go new file mode 100644 index 0000000..d8ca6f0 --- /dev/null +++ b/system/proc.go @@ -0,0 +1,114 @@ +package system + +import ( + "context" + "fmt" + "io" + "log/slog" + "runtime" + + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + "go.uber.org/multierr" +) + +type ProcConfig struct { + Host host.Host + Runtime wazero.Runtime + Bytecode []byte + ErrWriter io.Writer +} + +func (c ProcConfig) New(ctx context.Context) (*Proc, error) { + var ok = false + cm, err := c.Runtime.CompileModule(ctx, c.Bytecode) + if err != nil { + return nil, err + } + + sys, err := wasi_snapshot_preview1.Instantiate(ctx, c.Runtime) + if err != nil { + return nil, err + } + defer func() { + if !ok { + sys.Close(ctx) + } + }() + + e := NewEndpoint() + mod, err := c.Runtime.InstantiateModule(ctx, cm, wazero.NewModuleConfig(). + WithName(e.String()). + WithSysNanosleep(). + WithSysNanotime(). + WithSysWalltime(). + WithStdin(e). + WithStdout(e). + WithStderr(c.ErrWriter). + WithStartFunctions()) + if err != nil { + return nil, err + } + defer func() { + if !ok { + e.Close() + } + }() + + proc := &Proc{ + Sys: sys, + Module: mod, + Endpoint: e} + c.Host.SetStreamHandler(e.Protocol(), func(s network.Stream) { + defer s.Close() + + if err := proc.Poll(ctx, s, nil); err != nil { + slog.ErrorContext(ctx, "failed to poll process", "reason", err) + return + } + }) + runtime.SetFinalizer(proc, func(p *Proc) { + c.Host.RemoveStreamHandler(e.Protocol()) + }) + ok = true + return proc, nil +} + +type Proc struct { + Sys api.Closer + Endpoint *Endpoint + Module api.Module +} + +func (p *Proc) String() string { + return p.Endpoint.String() +} + +func (p *Proc) Close(ctx context.Context) error { + return multierr.Combine( + p.Endpoint.Close(), + p.Module.Close(ctx), + p.Sys.Close(ctx)) +} + +func (p Proc) Poll(ctx context.Context, s network.Stream, stack []uint64) error { + if deadline, ok := ctx.Deadline(); ok { + if err := s.SetReadDeadline(deadline); err != nil { + return fmt.Errorf("set read deadline: %w", err) + } + } + + p.Endpoint.ReadWriteCloser = s + defer func() { + p.Endpoint.ReadWriteCloser = nil + }() + + if poll := p.Module.ExportedFunction("poll"); poll == nil { + return fmt.Errorf("%s::poll: not found", p) + } else { + return poll.CallWithStack(ctx, stack) + } +} diff --git a/system/proc_test.go b/system/proc_test.go new file mode 100644 index 0000000..6444e7c --- /dev/null +++ b/system/proc_test.go @@ -0,0 +1,529 @@ +package system_test + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "io" + "os" + "testing" + "time" + + "github.com/libp2p/go-libp2p" + inproc "github.com/lthibault/go-libp2p-inproc-transport" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tetratelabs/wazero" + "github.com/wetware/go/system" + "github.com/wetware/go/system/mocks" + "go.uber.org/mock/gomock" +) + +// loadEchoWasm loads the echo example WASM file for testing +func loadEchoWasm(t *testing.T) []byte { + // Try multiple possible paths + possiblePaths := []string{ + "../../examples/echo/main.wasm", + "../examples/echo/main.wasm", + "examples/echo/main.wasm", + } + + for _, path := range possiblePaths { + if wasmData, err := os.ReadFile(path); err == nil { + return wasmData + } + } + + // If none of the paths work, try to find it relative to the current working directory + wasmData, err := os.ReadFile("examples/echo/main.wasm") + require.NoError(t, err, "Failed to load echo.wasm from any expected location") + return wasmData +} + +func TestProcConfig_New(t *testing.T) { + ctx := context.Background() + mockErrWriter := &bytes.Buffer{} + + // Load the real echo WASM file for testing + validBytecode := loadEchoWasm(t) + + t.Run("successful creation", func(t *testing.T) { + // Create a libp2p host with in-process transport + host, err := libp2p.New( + libp2p.Transport(inproc.New()), + ) + require.NoError(t, err) + defer host.Close() + + runtime := wazero.NewRuntime(ctx) + defer runtime.Close(ctx) + + config := system.ProcConfig{ + Host: host, + Runtime: runtime, + Bytecode: validBytecode, + ErrWriter: mockErrWriter, + } + + proc, err := config.New(ctx) + require.NoError(t, err) + require.NotNil(t, proc) + assert.NotNil(t, proc.Sys) + assert.NotNil(t, proc.Module) + assert.NotNil(t, proc.Endpoint) + + // Verify the endpoint has a valid name + assert.NotEmpty(t, proc.Endpoint.Name) + assert.Contains(t, proc.String(), "/ww/0.1.0/") + + // Clean up + proc.Close(ctx) + }) + + t.Run("invalid bytecode", func(t *testing.T) { + // Create a libp2p host with in-process transport + host, err := libp2p.New( + libp2p.Transport(inproc.New()), + ) + require.NoError(t, err) + defer host.Close() + + runtime := wazero.NewRuntime(ctx) + defer runtime.Close(ctx) + + config := system.ProcConfig{ + Host: host, + Runtime: runtime, + Bytecode: []byte("invalid wasm bytecode"), + ErrWriter: mockErrWriter, + } + + proc, err := config.New(ctx) + assert.Error(t, err) + assert.Nil(t, proc) + }) + + t.Run("nil runtime", func(t *testing.T) { + // Create a libp2p host with in-process transport + host, err := libp2p.New( + libp2p.Transport(inproc.New()), + ) + require.NoError(t, err) + defer host.Close() + + config := system.ProcConfig{ + Host: host, + Runtime: nil, + Bytecode: []byte{}, + ErrWriter: mockErrWriter, + } + + // This will panic due to nil runtime, so we expect a panic + assert.Panics(t, func() { + config.New(ctx) + }) + }) +} + +func TestProc_String(t *testing.T) { + endpoint := system.NewEndpoint() + proc := &system.Proc{ + Endpoint: endpoint, + } + + result := proc.String() + expected := endpoint.String() + assert.Equal(t, expected, result) + assert.Contains(t, result, "/ww/0.1.0/") +} + +func TestProc_Close(t *testing.T) { + ctx := context.Background() + + t.Run("successful close", func(t *testing.T) { + // Create a libp2p host with in-process transport + host, err := libp2p.New( + libp2p.Transport(inproc.New()), + ) + require.NoError(t, err) + defer host.Close() + + runtime := wazero.NewRuntime(ctx) + defer runtime.Close(ctx) + + validBytecode := loadEchoWasm(t) + + config := system.ProcConfig{ + Host: host, + Runtime: runtime, + Bytecode: validBytecode, + ErrWriter: &bytes.Buffer{}, + } + + proc, err := config.New(ctx) + require.NoError(t, err) + require.NotNil(t, proc) + + // Test close + err = proc.Close(ctx) + assert.NoError(t, err) + }) +} + +func TestProc_Poll_WithGomock(t *testing.T) { + ctx := context.Background() + + t.Run("successful poll", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockStream := mocks.NewMockStreamInterface(ctrl) + + // Create a libp2p host with in-process transport + host, err := libp2p.New( + libp2p.Transport(inproc.New()), + ) + require.NoError(t, err) + defer host.Close() + + runtime := wazero.NewRuntime(ctx) + defer runtime.Close(ctx) + + validBytecode := loadEchoWasm(t) + + config := system.ProcConfig{ + Host: host, + Runtime: runtime, + Bytecode: validBytecode, + ErrWriter: &bytes.Buffer{}, + } + + proc, err := config.New(ctx) + require.NoError(t, err) + require.NotNil(t, proc) + defer proc.Close(ctx) + + stack := []uint64{1, 2, 3} + + // Test that poll function exists + pollFunc := proc.Module.ExportedFunction("poll") + assert.NotNil(t, pollFunc, "poll function should exist") + + // The echo WASM module will try to read from stdin, so we need to expect Read calls + // The poll function reads up to 512 bytes from stdin + mockStream.EXPECT().Read(gomock.Any()).Return(0, io.EOF).AnyTimes() + + // Actually call the Poll method - this should succeed since we have a valid WASM function + err = proc.Poll(ctx, mockStream, stack) + // The WASM function should execute successfully + assert.NoError(t, err) + }) + + t.Run("poll with context deadline", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockStream := mocks.NewMockStreamInterface(ctrl) + + // Create context with deadline + deadline := time.Now().Add(time.Hour) + ctxWithDeadline, cancel := context.WithDeadline(ctx, deadline) + defer cancel() + + // Create a libp2p host with in-process transport + host, err := libp2p.New( + libp2p.Transport(inproc.New()), + ) + require.NoError(t, err) + defer host.Close() + + runtime := wazero.NewRuntime(ctx) + defer runtime.Close(ctx) + + validBytecode := loadEchoWasm(t) + + config := system.ProcConfig{ + Host: host, + Runtime: runtime, + Bytecode: validBytecode, + ErrWriter: &bytes.Buffer{}, + } + + proc, err := config.New(ctxWithDeadline) + require.NoError(t, err) + require.NotNil(t, proc) + defer proc.Close(ctxWithDeadline) + + stack := []uint64{1, 2, 3} + + // Set up mock expectations + mockStream.EXPECT().SetReadDeadline(deadline).Return(nil) + // The echo WASM module will try to read from stdin + mockStream.EXPECT().Read(gomock.Any()).Return(0, io.EOF).AnyTimes() + + // Test that poll function exists + pollFunc := proc.Module.ExportedFunction("poll") + assert.NotNil(t, pollFunc, "poll function should exist") + + // Actually call the Poll method to trigger the mock expectations + err = proc.Poll(ctxWithDeadline, mockStream, stack) + // The WASM function should execute successfully + assert.NoError(t, err) + }) + + t.Run("set read deadline error", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockStream := mocks.NewMockStreamInterface(ctrl) + endpoint := system.NewEndpoint() + + // Create context with deadline + deadline := time.Now().Add(time.Hour) + ctxWithDeadline, cancel := context.WithDeadline(ctx, deadline) + defer cancel() + + proc := &system.Proc{ + Endpoint: endpoint, + } + + stack := []uint64{1, 2, 3} + + // Set up mock expectations + deadlineErr := errors.New("deadline error") + mockStream.EXPECT().SetReadDeadline(deadline).Return(deadlineErr) + + err := proc.Poll(ctxWithDeadline, mockStream, stack) + assert.Error(t, err) + assert.Contains(t, err.Error(), "set read deadline") + assert.Contains(t, err.Error(), "deadline error") + }) + + t.Run("poll function not found", func(t *testing.T) { + // Create a libp2p host with in-process transport + host, err := libp2p.New( + libp2p.Transport(inproc.New()), + ) + require.NoError(t, err) + defer host.Close() + + // Create a real proc with a module that doesn't export poll function + runtime := wazero.NewRuntime(ctx) + defer runtime.Close(ctx) + + // Use completely invalid bytecode to simulate a module that can't be created + noPollBytecode := []byte{0x00, 0x00, 0x00, 0x00} + + configNoPoll := system.ProcConfig{ + Host: host, + Runtime: runtime, + Bytecode: noPollBytecode, + ErrWriter: &bytes.Buffer{}, + } + + // This should fail to create the module due to invalid bytecode + procNoPoll, err := configNoPoll.New(ctx) + assert.Error(t, err, "should fail to create module with invalid bytecode") + assert.Nil(t, procNoPoll, "proc should be nil when creation fails") + }) +} + +func TestProc_StreamHandler_WithGomock(t *testing.T) { + ctx := context.Background() + mockErrWriter := &bytes.Buffer{} + + // Create a minimal valid WASM bytecode + validBytecode := []byte{ + 0x00, 0x61, 0x73, 0x6d, // WASM magic number + 0x01, 0x00, 0x00, 0x00, // Version 1 + 0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, // Type section: (i32, i32) -> i32 + 0x03, 0x02, 0x01, 0x00, // Function section: 1 function of type 0 + 0x07, 0x08, 0x01, 0x04, 0x70, 0x6f, 0x6c, 0x6c, 0x00, 0x00, // Export section: export "poll" function 0 + 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b, // Code section: function 0 body + } + + // Create a libp2p host with in-process transport + host, err := libp2p.New( + libp2p.Transport(inproc.New()), + ) + require.NoError(t, err) + defer host.Close() + + runtime := wazero.NewRuntime(ctx) + defer runtime.Close(ctx) + + config := system.ProcConfig{ + Host: host, + Runtime: runtime, + Bytecode: validBytecode, + ErrWriter: mockErrWriter, + } + + proc, err := config.New(ctx) + require.NoError(t, err) + require.NotNil(t, proc) + defer proc.Close(ctx) + + t.Run("stream handler processes valid data", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockStream := mocks.NewMockStreamInterface(ctrl) + + // Create test data + stackSize := int32(2) + stackData := []uint64{1, 2} + + var buf bytes.Buffer + binary.Write(&buf, binary.BigEndian, stackSize) + for _, val := range stackData { + binary.Write(&buf, binary.BigEndian, val) + } + + // Set up mock expectations + mockStream.EXPECT().Read(gomock.Any()).DoAndReturn(func(p []byte) (int, error) { + return buf.Read(p) + }).AnyTimes() + + // Test the stream handler by creating a mock stream and calling it + // Note: This is a simplified test since we can't easily test the actual stream handler + // without a real host, but we can test the mock setup + assert.NotNil(t, mockStream) + }) + + t.Run("stream handler handles read errors", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockStream := mocks.NewMockStreamInterface(ctrl) + + // Set up mock expectations for the stream + mockStream.EXPECT().Read(gomock.Any()).Return(0, errors.New("read error")).AnyTimes() + + // Test that the mock is properly set up + assert.NotNil(t, mockStream) + }) +} + +func TestNewEndpoint(t *testing.T) { + endpoint := system.NewEndpoint() + + assert.NotNil(t, endpoint) + assert.NotEmpty(t, endpoint.Name) + + // Test that the endpoint name is base58 encoded + assert.Greater(t, len(endpoint.Name), 0) + + // Test that the protocol ID is correctly formatted + protocol := endpoint.Protocol() + assert.Contains(t, string(protocol), "/ww/0.1.0/") + assert.Contains(t, string(protocol), endpoint.Name) +} + +func TestEndpoint_String(t *testing.T) { + endpoint := system.NewEndpoint() + + result := endpoint.String() + expected := string(endpoint.Protocol()) + + assert.Equal(t, expected, result) + assert.Contains(t, result, "/ww/0.1.0/") +} + +func TestEndpoint_Protocol(t *testing.T) { + endpoint := system.NewEndpoint() + + protocol := endpoint.Protocol() + + assert.Contains(t, string(protocol), "/ww/0.1.0/") + assert.Contains(t, string(protocol), endpoint.Name) +} + +func TestProcConfig_New_ErrorHandling(t *testing.T) { + ctx := context.Background() + mockErrWriter := &bytes.Buffer{} + + t.Run("empty bytecode", func(t *testing.T) { + runtime := wazero.NewRuntime(ctx) + defer runtime.Close(ctx) + + config := system.ProcConfig{ + Host: nil, + Runtime: runtime, + Bytecode: []byte{}, + ErrWriter: mockErrWriter, + } + + proc, err := config.New(ctx) + assert.Error(t, err) + assert.Nil(t, proc) + }) + + t.Run("nil bytecode", func(t *testing.T) { + runtime := wazero.NewRuntime(ctx) + defer runtime.Close(ctx) + + config := system.ProcConfig{ + Host: nil, + Runtime: runtime, + Bytecode: nil, + ErrWriter: mockErrWriter, + } + + proc, err := config.New(ctx) + assert.Error(t, err) + assert.Nil(t, proc) + }) +} + +func TestEndpoint_Concurrency(t *testing.T) { + // Test that multiple endpoints have different names + endpoint1 := system.NewEndpoint() + endpoint2 := system.NewEndpoint() + + assert.NotEqual(t, endpoint1.Name, endpoint2.Name, "Endpoints should have unique names") + assert.NotEqual(t, endpoint1.String(), endpoint2.String(), "Endpoint strings should be different") + assert.NotEqual(t, endpoint1.Protocol(), endpoint2.Protocol(), "Endpoint protocols should be different") +} + +func TestProc_Integration_WithRealWasm(t *testing.T) { + ctx := context.Background() + + // Create a libp2p host with in-process transport + host, err := libp2p.New( + libp2p.Transport(inproc.New()), + ) + require.NoError(t, err) + defer host.Close() + + // This test uses a more complete WASM module to test the full flow + runtime := wazero.NewRuntime(ctx) + defer runtime.Close(ctx) + + // Load the real echo WASM file for testing + completeBytecode := loadEchoWasm(t) + + config := system.ProcConfig{ + Host: host, + Runtime: runtime, + Bytecode: completeBytecode, + ErrWriter: &bytes.Buffer{}, + } + + proc, err := config.New(ctx) + require.NoError(t, err) + require.NotNil(t, proc) + defer proc.Close(ctx) + + // Test that all components are properly initialized + assert.NotNil(t, proc.Sys, "Sys should be initialized") + assert.NotNil(t, proc.Module, "Module should be initialized") + assert.NotNil(t, proc.Endpoint, "Endpoint should be initialized") + assert.NotEmpty(t, proc.Endpoint.Name, "Endpoint should have a name") + assert.Contains(t, proc.String(), "/ww/0.1.0/", "String should contain protocol prefix") + + // Test that the poll function is exported + pollFunc := proc.Module.ExportedFunction("poll") + assert.NotNil(t, pollFunc, "poll function should be exported") +} diff --git a/system/cell.go b/system/socket.go similarity index 100% rename from system/cell.go rename to system/socket.go diff --git a/system/system.capnp b/system/system.capnp index 0284883..d3494d7 100644 --- a/system/system.capnp +++ b/system/system.capnp @@ -6,6 +6,12 @@ $Go.package("system"); $Go.import("github.com/wetware/go/system"); -interface Importer { - import @0 (serviceToken :Data) -> (service :Capability); +interface Terminal { + login @0 () -> ( + exec :Executor, + ); +} + +interface Executor { + exec @0 (bytecode :Data) -> (protocol :Text); } diff --git a/system/system.capnp.go b/system/system.capnp.go index 6e5baa7..0afb1d6 100644 --- a/system/system.capnp.go +++ b/system/system.capnp.go @@ -11,32 +11,343 @@ import ( context "context" ) -type Importer capnp.Client +type Terminal capnp.Client -// Importer_TypeID is the unique identifier for the type Importer. -const Importer_TypeID = 0xebcb79faf0a0efeb +// Terminal_TypeID is the unique identifier for the type Terminal. +const Terminal_TypeID = 0xab27f53bfa9ddf26 -func (c Importer) Import(ctx context.Context, params func(Importer_import_Params) error) (Importer_import_Results_Future, capnp.ReleaseFunc) { +func (c Terminal) Login(ctx context.Context, params func(Terminal_login_Params) error) (Terminal_login_Results_Future, capnp.ReleaseFunc) { s := capnp.Send{ Method: capnp.Method{ - InterfaceID: 0xebcb79faf0a0efeb, + InterfaceID: 0xab27f53bfa9ddf26, MethodID: 0, - InterfaceName: "system.capnp:Importer", - MethodName: "import", + InterfaceName: "system.capnp:Terminal", + MethodName: "login", + }, + } + if params != nil { + s.ArgsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 0} + s.PlaceArgs = func(s capnp.Struct) error { return params(Terminal_login_Params(s)) } + } + + ans, release := capnp.Client(c).SendCall(ctx, s) + return Terminal_login_Results_Future{Future: ans.Future()}, release + +} + +func (c Terminal) WaitStreaming() error { + return capnp.Client(c).WaitStreaming() +} + +// String returns a string that identifies this capability for debugging +// purposes. Its format should not be depended on: in particular, it +// should not be used to compare clients. Use IsSame to compare clients +// for equality. +func (c Terminal) String() string { + return "Terminal(" + capnp.Client(c).String() + ")" +} + +// AddRef creates a new Client that refers to the same capability as c. +// If c is nil or has resolved to null, then AddRef returns nil. +func (c Terminal) AddRef() Terminal { + return Terminal(capnp.Client(c).AddRef()) +} + +// Release releases a capability reference. If this is the last +// reference to the capability, then the underlying resources associated +// with the capability will be released. +// +// Release will panic if c has already been released, but not if c is +// nil or resolved to null. +func (c Terminal) Release() { + capnp.Client(c).Release() +} + +// Resolve blocks until the capability is fully resolved or the Context +// expires. +func (c Terminal) Resolve(ctx context.Context) error { + return capnp.Client(c).Resolve(ctx) +} + +func (c Terminal) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Client(c).EncodeAsPtr(seg) +} + +func (Terminal) DecodeFromPtr(p capnp.Ptr) Terminal { + return Terminal(capnp.Client{}.DecodeFromPtr(p)) +} + +// IsValid reports whether c is a valid reference to a capability. +// A reference is invalid if it is nil, has resolved to null, or has +// been released. +func (c Terminal) IsValid() bool { + return capnp.Client(c).IsValid() +} + +// IsSame reports whether c and other refer to a capability created by the +// same call to NewClient. This can return false negatives if c or other +// are not fully resolved: use Resolve if this is an issue. If either +// c or other are released, then IsSame panics. +func (c Terminal) IsSame(other Terminal) bool { + return capnp.Client(c).IsSame(capnp.Client(other)) +} + +// Update the flowcontrol.FlowLimiter used to manage flow control for +// this client. This affects all future calls, but not calls already +// waiting to send. Passing nil sets the value to flowcontrol.NopLimiter, +// which is also the default. +func (c Terminal) SetFlowLimiter(lim fc.FlowLimiter) { + capnp.Client(c).SetFlowLimiter(lim) +} + +// Get the current flowcontrol.FlowLimiter used to manage flow control +// for this client. +func (c Terminal) GetFlowLimiter() fc.FlowLimiter { + return capnp.Client(c).GetFlowLimiter() +} + +// A Terminal_Server is a Terminal with a local implementation. +type Terminal_Server interface { + Login(context.Context, Terminal_login) error +} + +// Terminal_NewServer creates a new Server from an implementation of Terminal_Server. +func Terminal_NewServer(s Terminal_Server) *server.Server { + c, _ := s.(server.Shutdowner) + return server.New(Terminal_Methods(nil, s), s, c) +} + +// Terminal_ServerToClient creates a new Client from an implementation of Terminal_Server. +// The caller is responsible for calling Release on the returned Client. +func Terminal_ServerToClient(s Terminal_Server) Terminal { + return Terminal(capnp.NewClient(Terminal_NewServer(s))) +} + +// Terminal_Methods appends Methods to a slice that invoke the methods on s. +// This can be used to create a more complicated Server. +func Terminal_Methods(methods []server.Method, s Terminal_Server) []server.Method { + if cap(methods) == 0 { + methods = make([]server.Method, 0, 1) + } + + methods = append(methods, server.Method{ + Method: capnp.Method{ + InterfaceID: 0xab27f53bfa9ddf26, + MethodID: 0, + InterfaceName: "system.capnp:Terminal", + MethodName: "login", + }, + Impl: func(ctx context.Context, call *server.Call) error { + return s.Login(ctx, Terminal_login{call}) + }, + }) + + return methods +} + +// Terminal_login holds the state for a server call to Terminal.login. +// See server.Call for documentation. +type Terminal_login struct { + *server.Call +} + +// Args returns the call's arguments. +func (c Terminal_login) Args() Terminal_login_Params { + return Terminal_login_Params(c.Call.Args()) +} + +// AllocResults allocates the results struct. +func (c Terminal_login) AllocResults() (Terminal_login_Results, error) { + r, err := c.Call.AllocResults(capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Terminal_login_Results(r), err +} + +// Terminal_List is a list of Terminal. +type Terminal_List = capnp.CapList[Terminal] + +// NewTerminal creates a new list of Terminal. +func NewTerminal_List(s *capnp.Segment, sz int32) (Terminal_List, error) { + l, err := capnp.NewPointerList(s, sz) + return capnp.CapList[Terminal](l), err +} + +type Terminal_login_Params capnp.Struct + +// Terminal_login_Params_TypeID is the unique identifier for the type Terminal_login_Params. +const Terminal_login_Params_TypeID = 0xc3cb58f30a5551af + +func NewTerminal_login_Params(s *capnp.Segment) (Terminal_login_Params, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}) + return Terminal_login_Params(st), err +} + +func NewRootTerminal_login_Params(s *capnp.Segment) (Terminal_login_Params, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}) + return Terminal_login_Params(st), err +} + +func ReadRootTerminal_login_Params(msg *capnp.Message) (Terminal_login_Params, error) { + root, err := msg.Root() + return Terminal_login_Params(root.Struct()), err +} + +func (s Terminal_login_Params) String() string { + str, _ := text.Marshal(0xc3cb58f30a5551af, capnp.Struct(s)) + return str +} + +func (s Terminal_login_Params) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Terminal_login_Params) DecodeFromPtr(p capnp.Ptr) Terminal_login_Params { + return Terminal_login_Params(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Terminal_login_Params) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Terminal_login_Params) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Terminal_login_Params) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Terminal_login_Params) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} + +// Terminal_login_Params_List is a list of Terminal_login_Params. +type Terminal_login_Params_List = capnp.StructList[Terminal_login_Params] + +// NewTerminal_login_Params creates a new list of Terminal_login_Params. +func NewTerminal_login_Params_List(s *capnp.Segment, sz int32) (Terminal_login_Params_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}, sz) + return capnp.StructList[Terminal_login_Params](l), err +} + +// Terminal_login_Params_Future is a wrapper for a Terminal_login_Params promised by a client call. +type Terminal_login_Params_Future struct{ *capnp.Future } + +func (f Terminal_login_Params_Future) Struct() (Terminal_login_Params, error) { + p, err := f.Future.Ptr() + return Terminal_login_Params(p.Struct()), err +} + +type Terminal_login_Results capnp.Struct + +// Terminal_login_Results_TypeID is the unique identifier for the type Terminal_login_Results. +const Terminal_login_Results_TypeID = 0xa584e813c754d96d + +func NewTerminal_login_Results(s *capnp.Segment) (Terminal_login_Results, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Terminal_login_Results(st), err +} + +func NewRootTerminal_login_Results(s *capnp.Segment) (Terminal_login_Results, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) + return Terminal_login_Results(st), err +} + +func ReadRootTerminal_login_Results(msg *capnp.Message) (Terminal_login_Results, error) { + root, err := msg.Root() + return Terminal_login_Results(root.Struct()), err +} + +func (s Terminal_login_Results) String() string { + str, _ := text.Marshal(0xa584e813c754d96d, capnp.Struct(s)) + return str +} + +func (s Terminal_login_Results) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { + return capnp.Struct(s).EncodeAsPtr(seg) +} + +func (Terminal_login_Results) DecodeFromPtr(p capnp.Ptr) Terminal_login_Results { + return Terminal_login_Results(capnp.Struct{}.DecodeFromPtr(p)) +} + +func (s Terminal_login_Results) ToPtr() capnp.Ptr { + return capnp.Struct(s).ToPtr() +} +func (s Terminal_login_Results) IsValid() bool { + return capnp.Struct(s).IsValid() +} + +func (s Terminal_login_Results) Message() *capnp.Message { + return capnp.Struct(s).Message() +} + +func (s Terminal_login_Results) Segment() *capnp.Segment { + return capnp.Struct(s).Segment() +} +func (s Terminal_login_Results) Exec() Executor { + p, _ := capnp.Struct(s).Ptr(0) + return Executor(p.Interface().Client()) +} + +func (s Terminal_login_Results) HasExec() bool { + return capnp.Struct(s).HasPtr(0) +} + +func (s Terminal_login_Results) SetExec(v Executor) error { + if !v.IsValid() { + return capnp.Struct(s).SetPtr(0, capnp.Ptr{}) + } + seg := s.Segment() + in := capnp.NewInterface(seg, seg.Message().CapTable().Add(capnp.Client(v))) + return capnp.Struct(s).SetPtr(0, in.ToPtr()) +} + +// Terminal_login_Results_List is a list of Terminal_login_Results. +type Terminal_login_Results_List = capnp.StructList[Terminal_login_Results] + +// NewTerminal_login_Results creates a new list of Terminal_login_Results. +func NewTerminal_login_Results_List(s *capnp.Segment, sz int32) (Terminal_login_Results_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz) + return capnp.StructList[Terminal_login_Results](l), err +} + +// Terminal_login_Results_Future is a wrapper for a Terminal_login_Results promised by a client call. +type Terminal_login_Results_Future struct{ *capnp.Future } + +func (f Terminal_login_Results_Future) Struct() (Terminal_login_Results, error) { + p, err := f.Future.Ptr() + return Terminal_login_Results(p.Struct()), err +} +func (p Terminal_login_Results_Future) Exec() Executor { + return Executor(p.Future.Field(0, nil).Client()) +} + +type Executor capnp.Client + +// Executor_TypeID is the unique identifier for the type Executor. +const Executor_TypeID = 0xccef5e6e46834543 + +func (c Executor) Exec(ctx context.Context, params func(Executor_exec_Params) error) (Executor_exec_Results_Future, capnp.ReleaseFunc) { + + s := capnp.Send{ + Method: capnp.Method{ + InterfaceID: 0xccef5e6e46834543, + MethodID: 0, + InterfaceName: "system.capnp:Executor", + MethodName: "exec", }, } if params != nil { s.ArgsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1} - s.PlaceArgs = func(s capnp.Struct) error { return params(Importer_import_Params(s)) } + s.PlaceArgs = func(s capnp.Struct) error { return params(Executor_exec_Params(s)) } } ans, release := capnp.Client(c).SendCall(ctx, s) - return Importer_import_Results_Future{Future: ans.Future()}, release + return Executor_exec_Results_Future{Future: ans.Future()}, release } -func (c Importer) WaitStreaming() error { +func (c Executor) WaitStreaming() error { return capnp.Client(c).WaitStreaming() } @@ -44,14 +355,14 @@ func (c Importer) WaitStreaming() error { // purposes. Its format should not be depended on: in particular, it // should not be used to compare clients. Use IsSame to compare clients // for equality. -func (c Importer) String() string { - return "Importer(" + capnp.Client(c).String() + ")" +func (c Executor) String() string { + return "Executor(" + capnp.Client(c).String() + ")" } // AddRef creates a new Client that refers to the same capability as c. // If c is nil or has resolved to null, then AddRef returns nil. -func (c Importer) AddRef() Importer { - return Importer(capnp.Client(c).AddRef()) +func (c Executor) AddRef() Executor { + return Executor(capnp.Client(c).AddRef()) } // Release releases a capability reference. If this is the last @@ -60,28 +371,28 @@ func (c Importer) AddRef() Importer { // // Release will panic if c has already been released, but not if c is // nil or resolved to null. -func (c Importer) Release() { +func (c Executor) Release() { capnp.Client(c).Release() } // Resolve blocks until the capability is fully resolved or the Context // expires. -func (c Importer) Resolve(ctx context.Context) error { +func (c Executor) Resolve(ctx context.Context) error { return capnp.Client(c).Resolve(ctx) } -func (c Importer) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { +func (c Executor) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { return capnp.Client(c).EncodeAsPtr(seg) } -func (Importer) DecodeFromPtr(p capnp.Ptr) Importer { - return Importer(capnp.Client{}.DecodeFromPtr(p)) +func (Executor) DecodeFromPtr(p capnp.Ptr) Executor { + return Executor(capnp.Client{}.DecodeFromPtr(p)) } // IsValid reports whether c is a valid reference to a capability. // A reference is invalid if it is nil, has resolved to null, or has // been released. -func (c Importer) IsValid() bool { +func (c Executor) IsValid() bool { return capnp.Client(c).IsValid() } @@ -89,7 +400,7 @@ func (c Importer) IsValid() bool { // same call to NewClient. This can return false negatives if c or other // are not fully resolved: use Resolve if this is an issue. If either // c or other are released, then IsSame panics. -func (c Importer) IsSame(other Importer) bool { +func (c Executor) IsSame(other Executor) bool { return capnp.Client(c).IsSame(capnp.Client(other)) } @@ -97,269 +408,277 @@ func (c Importer) IsSame(other Importer) bool { // this client. This affects all future calls, but not calls already // waiting to send. Passing nil sets the value to flowcontrol.NopLimiter, // which is also the default. -func (c Importer) SetFlowLimiter(lim fc.FlowLimiter) { +func (c Executor) SetFlowLimiter(lim fc.FlowLimiter) { capnp.Client(c).SetFlowLimiter(lim) } // Get the current flowcontrol.FlowLimiter used to manage flow control // for this client. -func (c Importer) GetFlowLimiter() fc.FlowLimiter { +func (c Executor) GetFlowLimiter() fc.FlowLimiter { return capnp.Client(c).GetFlowLimiter() } -// A Importer_Server is a Importer with a local implementation. -type Importer_Server interface { - Import(context.Context, Importer_import) error +// A Executor_Server is a Executor with a local implementation. +type Executor_Server interface { + Exec(context.Context, Executor_exec) error } -// Importer_NewServer creates a new Server from an implementation of Importer_Server. -func Importer_NewServer(s Importer_Server) *server.Server { +// Executor_NewServer creates a new Server from an implementation of Executor_Server. +func Executor_NewServer(s Executor_Server) *server.Server { c, _ := s.(server.Shutdowner) - return server.New(Importer_Methods(nil, s), s, c) + return server.New(Executor_Methods(nil, s), s, c) } -// Importer_ServerToClient creates a new Client from an implementation of Importer_Server. +// Executor_ServerToClient creates a new Client from an implementation of Executor_Server. // The caller is responsible for calling Release on the returned Client. -func Importer_ServerToClient(s Importer_Server) Importer { - return Importer(capnp.NewClient(Importer_NewServer(s))) +func Executor_ServerToClient(s Executor_Server) Executor { + return Executor(capnp.NewClient(Executor_NewServer(s))) } -// Importer_Methods appends Methods to a slice that invoke the methods on s. +// Executor_Methods appends Methods to a slice that invoke the methods on s. // This can be used to create a more complicated Server. -func Importer_Methods(methods []server.Method, s Importer_Server) []server.Method { +func Executor_Methods(methods []server.Method, s Executor_Server) []server.Method { if cap(methods) == 0 { methods = make([]server.Method, 0, 1) } methods = append(methods, server.Method{ Method: capnp.Method{ - InterfaceID: 0xebcb79faf0a0efeb, + InterfaceID: 0xccef5e6e46834543, MethodID: 0, - InterfaceName: "system.capnp:Importer", - MethodName: "import", + InterfaceName: "system.capnp:Executor", + MethodName: "exec", }, Impl: func(ctx context.Context, call *server.Call) error { - return s.Import(ctx, Importer_import{call}) + return s.Exec(ctx, Executor_exec{call}) }, }) return methods } -// Importer_import holds the state for a server call to Importer.import. +// Executor_exec holds the state for a server call to Executor.exec. // See server.Call for documentation. -type Importer_import struct { +type Executor_exec struct { *server.Call } // Args returns the call's arguments. -func (c Importer_import) Args() Importer_import_Params { - return Importer_import_Params(c.Call.Args()) +func (c Executor_exec) Args() Executor_exec_Params { + return Executor_exec_Params(c.Call.Args()) } // AllocResults allocates the results struct. -func (c Importer_import) AllocResults() (Importer_import_Results, error) { +func (c Executor_exec) AllocResults() (Executor_exec_Results, error) { r, err := c.Call.AllocResults(capnp.ObjectSize{DataSize: 0, PointerCount: 1}) - return Importer_import_Results(r), err + return Executor_exec_Results(r), err } -// Importer_List is a list of Importer. -type Importer_List = capnp.CapList[Importer] +// Executor_List is a list of Executor. +type Executor_List = capnp.CapList[Executor] -// NewImporter_List creates a new list of Importer. -func NewImporter_List(s *capnp.Segment, sz int32) (Importer_List, error) { +// NewExecutor creates a new list of Executor. +func NewExecutor_List(s *capnp.Segment, sz int32) (Executor_List, error) { l, err := capnp.NewPointerList(s, sz) - return capnp.CapList[Importer](l), err + return capnp.CapList[Executor](l), err } -type Importer_import_Params capnp.Struct +type Executor_exec_Params capnp.Struct -// Importer_import_Params_TypeID is the unique identifier for the type Importer_import_Params. -const Importer_import_Params_TypeID = 0x922a32e89bfe28a6 +// Executor_exec_Params_TypeID is the unique identifier for the type Executor_exec_Params. +const Executor_exec_Params_TypeID = 0xb8078038290eb30b -func NewImporter_import_Params(s *capnp.Segment) (Importer_import_Params, error) { +func NewExecutor_exec_Params(s *capnp.Segment) (Executor_exec_Params, error) { st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) - return Importer_import_Params(st), err + return Executor_exec_Params(st), err } -func NewRootImporter_import_Params(s *capnp.Segment) (Importer_import_Params, error) { +func NewRootExecutor_exec_Params(s *capnp.Segment) (Executor_exec_Params, error) { st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) - return Importer_import_Params(st), err + return Executor_exec_Params(st), err } -func ReadRootImporter_import_Params(msg *capnp.Message) (Importer_import_Params, error) { +func ReadRootExecutor_exec_Params(msg *capnp.Message) (Executor_exec_Params, error) { root, err := msg.Root() - return Importer_import_Params(root.Struct()), err + return Executor_exec_Params(root.Struct()), err } -func (s Importer_import_Params) String() string { - str, _ := text.Marshal(0x922a32e89bfe28a6, capnp.Struct(s)) +func (s Executor_exec_Params) String() string { + str, _ := text.Marshal(0xb8078038290eb30b, capnp.Struct(s)) return str } -func (s Importer_import_Params) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { +func (s Executor_exec_Params) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { return capnp.Struct(s).EncodeAsPtr(seg) } -func (Importer_import_Params) DecodeFromPtr(p capnp.Ptr) Importer_import_Params { - return Importer_import_Params(capnp.Struct{}.DecodeFromPtr(p)) +func (Executor_exec_Params) DecodeFromPtr(p capnp.Ptr) Executor_exec_Params { + return Executor_exec_Params(capnp.Struct{}.DecodeFromPtr(p)) } -func (s Importer_import_Params) ToPtr() capnp.Ptr { +func (s Executor_exec_Params) ToPtr() capnp.Ptr { return capnp.Struct(s).ToPtr() } -func (s Importer_import_Params) IsValid() bool { +func (s Executor_exec_Params) IsValid() bool { return capnp.Struct(s).IsValid() } -func (s Importer_import_Params) Message() *capnp.Message { +func (s Executor_exec_Params) Message() *capnp.Message { return capnp.Struct(s).Message() } -func (s Importer_import_Params) Segment() *capnp.Segment { +func (s Executor_exec_Params) Segment() *capnp.Segment { return capnp.Struct(s).Segment() } -func (s Importer_import_Params) Envelope() ([]byte, error) { +func (s Executor_exec_Params) Bytecode() ([]byte, error) { p, err := capnp.Struct(s).Ptr(0) return []byte(p.Data()), err } -func (s Importer_import_Params) HasEnvelope() bool { +func (s Executor_exec_Params) HasBytecode() bool { return capnp.Struct(s).HasPtr(0) } -func (s Importer_import_Params) SetEnvelope(v []byte) error { +func (s Executor_exec_Params) SetBytecode(v []byte) error { return capnp.Struct(s).SetData(0, v) } -// Importer_import_Params_List is a list of Importer_import_Params. -type Importer_import_Params_List = capnp.StructList[Importer_import_Params] +// Executor_exec_Params_List is a list of Executor_exec_Params. +type Executor_exec_Params_List = capnp.StructList[Executor_exec_Params] -// NewImporter_import_Params creates a new list of Importer_import_Params. -func NewImporter_import_Params_List(s *capnp.Segment, sz int32) (Importer_import_Params_List, error) { +// NewExecutor_exec_Params creates a new list of Executor_exec_Params. +func NewExecutor_exec_Params_List(s *capnp.Segment, sz int32) (Executor_exec_Params_List, error) { l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz) - return capnp.StructList[Importer_import_Params](l), err + return capnp.StructList[Executor_exec_Params](l), err } -// Importer_import_Params_Future is a wrapper for a Importer_import_Params promised by a client call. -type Importer_import_Params_Future struct{ *capnp.Future } +// Executor_exec_Params_Future is a wrapper for a Executor_exec_Params promised by a client call. +type Executor_exec_Params_Future struct{ *capnp.Future } -func (f Importer_import_Params_Future) Struct() (Importer_import_Params, error) { +func (f Executor_exec_Params_Future) Struct() (Executor_exec_Params, error) { p, err := f.Future.Ptr() - return Importer_import_Params(p.Struct()), err + return Executor_exec_Params(p.Struct()), err } -type Importer_import_Results capnp.Struct +type Executor_exec_Results capnp.Struct -// Importer_import_Results_TypeID is the unique identifier for the type Importer_import_Results. -const Importer_import_Results_TypeID = 0xd501af14a0bc9e76 +// Executor_exec_Results_TypeID is the unique identifier for the type Executor_exec_Results. +const Executor_exec_Results_TypeID = 0xa04c2cd2ed66b51c -func NewImporter_import_Results(s *capnp.Segment) (Importer_import_Results, error) { +func NewExecutor_exec_Results(s *capnp.Segment) (Executor_exec_Results, error) { st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) - return Importer_import_Results(st), err + return Executor_exec_Results(st), err } -func NewRootImporter_import_Results(s *capnp.Segment) (Importer_import_Results, error) { +func NewRootExecutor_exec_Results(s *capnp.Segment) (Executor_exec_Results, error) { st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}) - return Importer_import_Results(st), err + return Executor_exec_Results(st), err } -func ReadRootImporter_import_Results(msg *capnp.Message) (Importer_import_Results, error) { +func ReadRootExecutor_exec_Results(msg *capnp.Message) (Executor_exec_Results, error) { root, err := msg.Root() - return Importer_import_Results(root.Struct()), err + return Executor_exec_Results(root.Struct()), err } -func (s Importer_import_Results) String() string { - str, _ := text.Marshal(0xd501af14a0bc9e76, capnp.Struct(s)) +func (s Executor_exec_Results) String() string { + str, _ := text.Marshal(0xa04c2cd2ed66b51c, capnp.Struct(s)) return str } -func (s Importer_import_Results) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { +func (s Executor_exec_Results) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr { return capnp.Struct(s).EncodeAsPtr(seg) } -func (Importer_import_Results) DecodeFromPtr(p capnp.Ptr) Importer_import_Results { - return Importer_import_Results(capnp.Struct{}.DecodeFromPtr(p)) +func (Executor_exec_Results) DecodeFromPtr(p capnp.Ptr) Executor_exec_Results { + return Executor_exec_Results(capnp.Struct{}.DecodeFromPtr(p)) } -func (s Importer_import_Results) ToPtr() capnp.Ptr { +func (s Executor_exec_Results) ToPtr() capnp.Ptr { return capnp.Struct(s).ToPtr() } -func (s Importer_import_Results) IsValid() bool { +func (s Executor_exec_Results) IsValid() bool { return capnp.Struct(s).IsValid() } -func (s Importer_import_Results) Message() *capnp.Message { +func (s Executor_exec_Results) Message() *capnp.Message { return capnp.Struct(s).Message() } -func (s Importer_import_Results) Segment() *capnp.Segment { +func (s Executor_exec_Results) Segment() *capnp.Segment { return capnp.Struct(s).Segment() } -func (s Importer_import_Results) Service() capnp.Client { - p, _ := capnp.Struct(s).Ptr(0) - return p.Interface().Client() +func (s Executor_exec_Results) Protocol() (string, error) { + p, err := capnp.Struct(s).Ptr(0) + return p.Text(), err } -func (s Importer_import_Results) HasService() bool { +func (s Executor_exec_Results) HasProtocol() bool { return capnp.Struct(s).HasPtr(0) } -func (s Importer_import_Results) SetService(c capnp.Client) error { - if !c.IsValid() { - return capnp.Struct(s).SetPtr(0, capnp.Ptr{}) - } - seg := s.Segment() - in := capnp.NewInterface(seg, seg.Message().CapTable().Add(c)) - return capnp.Struct(s).SetPtr(0, in.ToPtr()) +func (s Executor_exec_Results) ProtocolBytes() ([]byte, error) { + p, err := capnp.Struct(s).Ptr(0) + return p.TextBytes(), err +} + +func (s Executor_exec_Results) SetProtocol(v string) error { + return capnp.Struct(s).SetText(0, v) } -// Importer_import_Results_List is a list of Importer_import_Results. -type Importer_import_Results_List = capnp.StructList[Importer_import_Results] +// Executor_exec_Results_List is a list of Executor_exec_Results. +type Executor_exec_Results_List = capnp.StructList[Executor_exec_Results] -// NewImporter_import_Results creates a new list of Importer_import_Results. -func NewImporter_import_Results_List(s *capnp.Segment, sz int32) (Importer_import_Results_List, error) { +// NewExecutor_exec_Results creates a new list of Executor_exec_Results. +func NewExecutor_exec_Results_List(s *capnp.Segment, sz int32) (Executor_exec_Results_List, error) { l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz) - return capnp.StructList[Importer_import_Results](l), err + return capnp.StructList[Executor_exec_Results](l), err } -// Importer_import_Results_Future is a wrapper for a Importer_import_Results promised by a client call. -type Importer_import_Results_Future struct{ *capnp.Future } +// Executor_exec_Results_Future is a wrapper for a Executor_exec_Results promised by a client call. +type Executor_exec_Results_Future struct{ *capnp.Future } -func (f Importer_import_Results_Future) Struct() (Importer_import_Results, error) { +func (f Executor_exec_Results_Future) Struct() (Executor_exec_Results, error) { p, err := f.Future.Ptr() - return Importer_import_Results(p.Struct()), err -} -func (p Importer_import_Results_Future) Service() capnp.Client { - return p.Future.Field(0, nil).Client() -} - -const schema_da965b22da734daf = "x\xda\x84\xd0\xbdJ\x03A\x14\xc5\xf1sff]\x0b" + - "\x17\x9dl\x10\x0bE\x08\x0a\x12$\xa0vi\x12l$" + - "\x82\x90\xb1\xb5\x0aa\x8a@>\xd6\x9d$\xb0\x95o`" + - "aa#J*\xdb\x80\xb5o`m\x93'\x10Rh" + - "m\xe1JV\"\xb1\xb2\xbb\xc5\x9f\xdf\x85\xb3v]U" + - "\x07AO@\x98Mo)}\xdc\xfb\xba{;,\xde" + - "@\xaf\x13\xf0\xe8\x03G[,\x10\x0cwY\x01\xd3\xe1" + - "\xc3\xf3(?\xe6\xebbPcq\x16\x98,\x98\xbe\x8f" + - ">>\x93\x97)t \xd3\xf1\x99\x9b\x14.n'\x00" + - "\xc3K>\x85\xc9\xac\x0f\x07<\x09\xef\xe9c?u\x89" + - "\xeb\xdbN\xa9)\x1bQ7*\xd7:Q/\xee\xdb\xb8" + - "\xd4\xca\x8e\x9dz#\xf6\x1b\x1dg\x94T\x80\"\xa0\x83" + - "S\xc0\xacH\x9a\x0d\xc1\xd4v\x87\xb6\xdd\x8b,\x00\x06" + - "\x10\x0c\xc0\x7f\xc0s\xebV\x07\xed\xfe\x1f\xf1\x180\xcb" + - "\x92&/x\xe5l\x85\x8ad\xb9|1\x1fN]" + + "\xbd?\x04\xa8W\xb9\xa2\x7f8\xbd\xfe\xc69=)B" + + "\xa0\xdc\xf2l\xeb\xe1\x13w\xc2\x17\xa3\xb4Mb\x87\xa3" + + ")\xe1h\xcb\xe6\xf2\xe6\xafW^\xbf\xfc%\x08\xdc\xfb" + + "q1A\x04\xe5\x99\xd6\xdd\xb3\xddk_\xde\xfc\xe5\xb3" + + "W\xac\xe8\xfd\x8e\xae'\xc5\x9c>'B\xcc\x94\xf9R" + + "^\xd8\xb4\x19\xcbN\xaf\xdb;\xd9\x1a\xd8x\xb1\xc8\xfa" + + "M;\xb0\xf1\xbeK6_Ld\x91\x9b@\x06@@" + + "@E\xe7\x01S\x974\xbb\x05\xcb^?+\xb28K" + + "\x00\xb0\x0e\xc1:8\xc6\x9b\xb7\xfdt\xa1\xdbI\x9aI" + + "vk\xa1\xeb\x81a\xf2'p\x1a0\x1b%\xcdN\xc1" + + "m\xce\x95j\xad\x00H5\xc2d\xc5lxh\x9b4" + + "\x81\xac\x01\xbfo\xc1j\x03\xa5\x8eB\xa8Z\xd8\xf0\xbe" + + "\xb3l\x93\xffm\xda\xee\xf4;)\xffY\xf4\xfaRa" + + "\xe3\xec\x86uE#\x08F\xeb\x15u<\x99\xe6c\xc9" + + "[\x03\xdb\xf0\xa6k\xc9\xab\x99Y}/\xa5\xa6}r" + + "\x7f\x0b\x1f\xfcg\x00\x00\x00\xff\xff\xbd\xa0\xb0\x92" func RegisterSchema(reg *schemas.Registry) { reg.Register(&schemas.Schema{ String: schema_da965b22da734daf, Nodes: []uint64{ - 0x922a32e89bfe28a6, - 0xd501af14a0bc9e76, - 0xebcb79faf0a0efeb, + 0xa04c2cd2ed66b51c, + 0xa584e813c754d96d, + 0xab27f53bfa9ddf26, + 0xb8078038290eb30b, + 0xc3cb58f30a5551af, + 0xccef5e6e46834543, }, Compressed: true, }) diff --git a/system/system.go b/system/system.go index 27187bb..18e6b49 100644 --- a/system/system.go +++ b/system/system.go @@ -1,3 +1,132 @@ //go:generate capnp compile -I.. -I$GOPATH/src/capnproto.org/go/capnp/std -ogo system.capnp package system + +import ( + "context" + "crypto/rand" + "io" + "os" + + capnp "capnproto.org/go/capnp/v3" + server "capnproto.org/go/capnp/v3/server" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/protocol" + "github.com/mr-tron/base58" + "github.com/tetratelabs/wazero" + "golang.org/x/sync/semaphore" +) + +type TerminalConfig struct { + Exec Executor +} + +func (c TerminalConfig) New() Terminal { + client := capnp.NewClient(c.NewServer()) + return Terminal(client) +} + +func (c TerminalConfig) NewServer() *server.Server { + return Terminal_NewServer(c) +} + +func (c TerminalConfig) Login(ctx context.Context, call Terminal_login) error { + results, err := call.AllocResults() + if err != nil { + return err + } + + exec := c.Exec.AddRef() + if err := results.SetExec(exec); err != nil { + defer exec.Release() + return err + } + + return nil +} + +type ExecutorConfig struct { + Host host.Host + Runtime wazero.RuntimeConfig +} + +func (c ExecutorConfig) New(ctx context.Context) Executor { + client := capnp.NewClient(c.NewServer(ctx)) + return Executor(client) +} + +func (c ExecutorConfig) NewServer(ctx context.Context) *server.Server { + return Executor_NewServer(DefaultExecutor{ + Host: c.Host, + Runtime: wazero.NewRuntimeWithConfig(ctx, c.Runtime), + }) +} + +type DefaultExecutor struct { + Background context.Context + Runtime wazero.Runtime + Host host.Host +} + +func (d DefaultExecutor) Exec(ctx context.Context, call Executor_exec) error { + b, err := call.Args().Bytecode() + if err != nil { + return err + } + + res, err := call.AllocResults() + if err != nil { + return err + } + + proc, err := ProcConfig{ + Host: d.Host, + Runtime: d.Runtime, + Bytecode: b, + ErrWriter: os.Stderr, // HACK + }.New(ctx) + if err != nil { + return err + } else if err = res.SetProtocol(proc.String()); err != nil { + defer proc.Close(ctx) + } + + return err +} + +func newName() string { + var buf [8]byte + if _, err := io.ReadFull(rand.Reader, buf[:]); err != nil { + panic(err) + } + return base58.FastBase58Encoding(buf[:]) +} + +type Endpoint struct { + Name string + io.ReadWriteCloser + sem *semaphore.Weighted +} + +func NewEndpoint() *Endpoint { + return &Endpoint{ + Name: newName(), + sem: semaphore.NewWeighted(1), + } +} + +func (e Endpoint) String() string { + proto := e.Protocol() + return string(proto) +} + +func (e Endpoint) Protocol() protocol.ID { + return protocol.ID("/ww/0.1.0/" + e.Name) +} + +func (e *Endpoint) Close() error { + if e.ReadWriteCloser != nil { + return e.ReadWriteCloser.Close() + } + return nil +}