Files
pim d63ffd6a3a 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>
2026-06-05 21:48:48 +02:00

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 }