feat: builder + App runner, Makefile (v1.1.0)
Builder (cli.For[C]): Root/Dir/Cmd/SlotDir/Slot construct the tree without repeating the [C] type parameter at every node; returns plain *Node[C] so it interoperates with struct-literal construction. App[C]: collapses the per-binary main.go — standard flags (-color/-json/-version, -server when configured), mode-aware color defaults, version banner, client connect, and the one-shot-vs-shell split — into one Main(). Transport-agnostic via a Connect callback, so it never assumes gRPC. Makefile: `make check` = fixstyle vet lint test (the pre-commit gate), plus build and a linux+openbsd cross target. The example now dogfoods both Builder and App. Tests cover the builder tree and App's one-shot dispatch / -version / nil-Connect paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+79
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: (C) Copyright 2026 Pim van Pelt <pim@ipng.ch>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestAppOneShotDispatch runs a one-shot command through App.Run and checks the
|
||||
// positional args reach the command, the captured slot arg is correct, and the
|
||||
// client from Connect is threaded through.
|
||||
func TestAppOneShotDispatch(t *testing.T) {
|
||||
var gotArg string
|
||||
var gotClient fakeClient
|
||||
b := For[fakeClient]()
|
||||
root := b.Root(
|
||||
b.Dir("ping", "",
|
||||
b.Slot("<name>", "", func(context.Context, fakeClient, []string) []string { return nil },
|
||||
func(_ context.Context, c fakeClient, args []string) error {
|
||||
gotClient = c
|
||||
gotArg = args[0]
|
||||
return nil
|
||||
})),
|
||||
)
|
||||
app := &App[fakeClient]{
|
||||
Name: "t",
|
||||
Root: root,
|
||||
Connect: func(context.Context, string) (fakeClient, func(), error) {
|
||||
return fakeClient{instances: []string{"sentinel"}}, nil, nil
|
||||
},
|
||||
}
|
||||
if err := app.Run(context.Background(), []string{"ping", "host9"}); err != nil {
|
||||
t.Fatalf("Run: %v", err)
|
||||
}
|
||||
if gotArg != "host9" {
|
||||
t.Errorf("arg = %q, want host9", gotArg)
|
||||
}
|
||||
if len(gotClient.instances) != 1 || gotClient.instances[0] != "sentinel" {
|
||||
t.Errorf("client not threaded from Connect: %+v", gotClient)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAppVersion checks -version short-circuits before connecting.
|
||||
func TestAppVersion(t *testing.T) {
|
||||
connected := false
|
||||
app := &App[fakeClient]{
|
||||
Name: "t",
|
||||
Version: "9.9.9",
|
||||
Root: For[fakeClient]().Root(),
|
||||
Connect: func(context.Context, string) (fakeClient, func(), error) {
|
||||
connected = true
|
||||
return fakeClient{}, nil, nil
|
||||
},
|
||||
}
|
||||
if err := app.Run(context.Background(), []string{"-version"}); err != nil {
|
||||
t.Fatalf("Run -version: %v", err)
|
||||
}
|
||||
if connected {
|
||||
t.Error("-version should not connect")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAppNilConnect checks a local CLI with no Connect uses the zero client.
|
||||
func TestAppNilConnect(t *testing.T) {
|
||||
ran := false
|
||||
b := For[fakeClient]()
|
||||
app := &App[fakeClient]{
|
||||
Name: "t",
|
||||
Root: b.Root(b.Cmd("go", "", func(context.Context, fakeClient, []string) error { ran = true; return nil })),
|
||||
}
|
||||
if err := app.Run(context.Background(), []string{"go"}); err != nil {
|
||||
t.Fatalf("Run: %v", err)
|
||||
}
|
||||
if !ran {
|
||||
t.Error("command did not run with nil Connect")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user