d63ffd6a3a
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>
65 lines
2.3 KiB
Go
65 lines
2.3 KiB
Go
// 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 }
|