feat: golang-cli v1.0.0 — generic command-tree CLI library
Reusable, generics-based CLI extracted from vpp-evpn's cmd/evpnc: a declarative command tree (Node[C]) from which dispatch, '?'-help and TAB-completion are derived, an interactive Shell[C], dynamic slot resolvers (context-dependent via captured args), text-or-JSON output (Emit), and color helpers (Paint/Label/KV). Builds on Linux and OpenBSD (readline termios override). Includes a self-contained example and a design proposal under docs/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
// SPDX-FileCopyrightText: (C) Copyright 2026 Pim van Pelt <pim@ipng.ch>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Format selects how command results are rendered. It is process-global,
|
||||
// toggled once at startup via SetFormat (parallel to SetColor), so command
|
||||
// functions can stay simple: they describe a result once and the framework
|
||||
// renders it the chosen way.
|
||||
type Format int
|
||||
|
||||
const (
|
||||
// FormatText renders the human-readable string a command passes to Emit.
|
||||
FormatText Format = iota
|
||||
// FormatJSON marshals the machine value a command passes to Emit and
|
||||
// ignores the text. Color is irrelevant in this mode.
|
||||
FormatJSON
|
||||
)
|
||||
|
||||
var outputFormat Format
|
||||
|
||||
// SetFormat selects the output format for Emit. Call it once at startup, e.g.
|
||||
// from a -json flag.
|
||||
func SetFormat(f Format) { outputFormat = f }
|
||||
|
||||
// OutputFormat reports the current output format.
|
||||
func OutputFormat() Format { return outputFormat }
|
||||
|
||||
// IsJSON reports whether output is in JSON mode. Commands can use it to skip
|
||||
// building the (then-unused) text representation, or to suppress banners.
|
||||
func IsJSON() bool { return outputFormat == FormatJSON }
|
||||
|
||||
// Emit renders one command result to stdout. In FormatText mode it prints text
|
||||
// — which the caller may have colorized with Paint/Label/KV — followed by a
|
||||
// newline. In FormatJSON mode it marshals v (indented) and ignores text. The
|
||||
// caller supplies both: text is the human form, v is the machine form. This is
|
||||
// the single seam that lets the same command serve "show foo" and "-json show
|
||||
// foo". Returns an error only if JSON marshaling fails.
|
||||
//
|
||||
// For multi-record output, marshal a slice as v and join the text yourself;
|
||||
// Emit is one call per logical result so the JSON stays a single document.
|
||||
func Emit(text string, v any) error {
|
||||
if outputFormat == FormatJSON {
|
||||
b, err := json.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal json: %w", err)
|
||||
}
|
||||
_, _ = fmt.Fprintln(os.Stdout, string(b))
|
||||
return nil
|
||||
}
|
||||
_, _ = fmt.Fprintln(os.Stdout, text)
|
||||
return nil
|
||||
}
|
||||
|
||||
// KV renders "key=value" with the key (and the '=') painted blue when color is
|
||||
// enabled, leaving the value in normal font. It is the building block for
|
||||
// one-line text output and pairs with Emit's text argument.
|
||||
func KV(key, value string) string { return Label(key+"=") + value }
|
||||
Reference in New Issue
Block a user