refactor(maglevc): build the CLI on the golang-cli library
Replace maglevc's hand-rolled command-tree CLI with git.ipng.ch/ipng/golang-cli v1.3.0, mirroring the evpnc refactor. The tree (commands.go) and the gRPC-status error unwrap (color.go) stay app-specific; the generic parts — parse tree, completion, '?'-help, the readline shell, one-shot dispatch, color helpers, and the watch keypress handler — now come from the library. - main.go: a single cli.App[grpcapi.MaglevClient] with a Connect callback; drops the flag/color-default/dispatch boilerplate. - commands.go: `type node = cli.Node[grpcapi.MaglevClient]`; label() -> cli.Label(); dyn* gain the captured-args parameter the library's Dynamic signature carries; runQuit returns cli.ErrQuit. - watch.go: keypress.WaitForKey replaces the inline cbreak helper. - color.go: only formatError remains, reading cli.ColorEnabled(). - delete tree.go, complete.go, shell.go. - tests use the library API; add TestTreeValid (cli.Validate). Behavior is unchanged except labels/errors now use the library's bright ANSI palette (was dark); escape lengths are identical so tabwriter alignment is unaffected. maglevc additionally gains the OpenBSD readline fix and BSD-correct watch keypress it previously lacked. Builds on linux and openbsd. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+29
-70
@@ -1,94 +1,53 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Command maglevc is the vpp-maglev CLI. It talks only to maglevd. With no
|
||||
// arguments it starts an interactive shell (readline, tab-completion, '?' help,
|
||||
// prefix abbreviation); with arguments it runs one command and exits. The
|
||||
// command set is a single declarative tree (buildTree) -- dispatch, help, and
|
||||
// completion are all derived from it, via the git.ipng.ch/ipng/golang-cli
|
||||
// library.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
cli "git.ipng.ch/ipng/golang-cli"
|
||||
buildinfo "git.ipng.ch/ipng/vpp-maglev/cmd"
|
||||
"git.ipng.ch/ipng/vpp-maglev/internal/grpcapi"
|
||||
"git.ipng.ch/ipng/vpp-maglev/internal/netutil"
|
||||
)
|
||||
|
||||
// defaultGRPCPort is the maglevd gRPC port (mirrors the server's
|
||||
// -grpc-addr default in cmd/server/main.go). Used when -server is given
|
||||
// without an explicit ":<port>" so operators can type "--server chbtl2"
|
||||
// instead of "--server chbtl2:9090".
|
||||
// defaultGRPCPort is the maglevd gRPC port (mirrors the server's -grpc-addr
|
||||
// default), used when -server is given without an explicit ":<port>".
|
||||
const defaultGRPCPort = "9090"
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", formatError(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
(&cli.App[grpcapi.MaglevClient]{
|
||||
Name: "maglevc",
|
||||
Version: buildinfo.Version(),
|
||||
Commit: buildinfo.Commit(),
|
||||
Date: buildinfo.Date(),
|
||||
Prompt: "maglev> ",
|
||||
Root: buildTree(),
|
||||
DefaultServer: "localhost:9090",
|
||||
ServerEnv: "MAGLEV_SERVER",
|
||||
Connect: connect,
|
||||
FormatError: formatError,
|
||||
}).Main()
|
||||
}
|
||||
|
||||
func run() error {
|
||||
defaultServer := "localhost:9090"
|
||||
if v := os.Getenv("MAGLEV_SERVER"); v != "" {
|
||||
defaultServer = v
|
||||
}
|
||||
serverAddr := flag.String("server", defaultServer, "maglev server address (env: MAGLEV_SERVER)")
|
||||
color := flag.Bool("color", true, "colorize static labels in output (defaults to false in one-shot mode)")
|
||||
printVersion := flag.Bool("version", false, "print version and exit")
|
||||
flag.Parse()
|
||||
|
||||
if *printVersion {
|
||||
fmt.Printf("maglevc %s (commit %s, built %s)\n",
|
||||
buildinfo.Version(), buildinfo.Commit(), buildinfo.Date())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Detect whether -color was explicitly set so we can pick a
|
||||
// mode-aware default: color is useful in the interactive shell but
|
||||
// noise (ANSI escapes) when piping one-shot output into scripts.
|
||||
colorExplicit := false
|
||||
flag.Visit(func(f *flag.Flag) {
|
||||
if f.Name == "color" {
|
||||
colorExplicit = true
|
||||
}
|
||||
})
|
||||
|
||||
addr := netutil.EnsurePort(*serverAddr, defaultGRPCPort)
|
||||
conn, err := grpc.NewClient(addr,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
// connect dials maglevd and returns the gRPC client. App resolves -server (env
|
||||
// MAGLEV_SERVER, default localhost:9090) and hands us the raw address; we ensure
|
||||
// a port and dial insecure, mirroring maglevd's default transport.
|
||||
func connect(_ context.Context, server string) (grpcapi.MaglevClient, func(), error) {
|
||||
addr := netutil.EnsurePort(server, defaultGRPCPort)
|
||||
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect %s: %w", addr, err)
|
||||
return nil, nil, fmt.Errorf("connect %s: %w", addr, err)
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
client := grpcapi.NewMaglevClient(conn)
|
||||
ctx := context.Background()
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) == 0 {
|
||||
// Interactive shell: color defaults to true.
|
||||
if colorExplicit {
|
||||
colorEnabled = *color
|
||||
} else {
|
||||
colorEnabled = true
|
||||
}
|
||||
fmt.Printf("maglevc %s (commit %s, built %s)\n",
|
||||
buildinfo.Version(), buildinfo.Commit(), buildinfo.Date())
|
||||
return runShell(ctx, client)
|
||||
}
|
||||
|
||||
// One-shot command from CLI arguments: color defaults to false so
|
||||
// output is script-safe. Operators wanting color can still pass
|
||||
// -color=true explicitly.
|
||||
if colorExplicit {
|
||||
colorEnabled = *color
|
||||
} else {
|
||||
colorEnabled = false
|
||||
}
|
||||
root := buildTree()
|
||||
tokens := splitTokens(strings.Join(args, " "))
|
||||
return dispatch(ctx, root, client, tokens)
|
||||
return grpcapi.NewMaglevClient(conn), func() { _ = conn.Close() }, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user