cliutil

package module
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 11, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package cliutil provides output rendering and cobra flag bundles for building ConfigHub CLIs and `cub` plugins with a consistent UX. It depends only on cobra, gojq, and yqkit — not on the ConfigHub API client — so API-only tools don't pay for it and UX-only code doesn't drag in the client. Combine it with github.com/confighub/sdk/core/cubapi to build a full tool.

It has no global state: every function takes its inputs (an io.Writer, an OutputSpec, a *cobra.Command) explicitly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ColumnHeader

func ColumnHeader(col string) string

ColumnHeader converts a column path to a display header: CamelCase becomes HYPHEN-CASE and Labels.X / Annotations.X become LABEL:X / ANNOTATION:X.

func Dash added in v0.4.5

func Dash(s string) string

Dash renders an empty value as "-", so a table column reads as absent rather than blank -- a cluster-scoped resource's namespace, an unset owner.

func Fprintln added in v0.4.5

func Fprintln(w io.Writer, a ...any)

Fprintln writes a line to w, discarding the error the way a CLI's final output can: there is nowhere left to report a failed write to, and a command that returned one would report a failure for output it had already produced.

func OutputFlag

func OutputFlag(cmd *cobra.Command, dest *string, defaultValue string)

OutputFlag registers the standard -o/--output flag, binding it to dest. If defaultValue is empty, "json" is used (the agent-friendly default).

func PrintJSON

func PrintJSON(w io.Writer, v any) error

PrintJSON writes v as indented JSON (HTML escaping disabled).

func PrintYAML

func PrintYAML(w io.Writer, v any) error

PrintYAML writes v as YAML.

It marshals through JSON, so the YAML is the JSON projection of v in YAML syntax: the same field names, the same omissions, the same rendering of a type with a custom JSON marshaler. Marshaling to YAML directly would read `yaml:` struct tags instead, and a model tagged for an API that speaks JSON carries none -- so its fields would come out lowercased with every empty one present, describing the same value differently depending on which flag was passed.

func ProfilesSpaceFlag added in v0.4.5

func ProfilesSpaceFlag(cmd *cobra.Command, dest *string, defaultSpace string)

ProfilesSpaceFlag registers --profiles-space, the Space holding a tool's stored profile Invocations. defaultSpace is what the flag defaults to; tools that share a profile library pass the same one, so that an operator has one place to look rather than one per tool they happen to have installed.

func RenderJQ

func RenderJQ(w io.Writer, v any, expr string) error

RenderJQ evaluates a jq expression against v (marshaled to JSON) and writes each result to w — scalars on their own line, structured values as JSON.

func RenderTable

func RenderTable[T any](w io.Writer, entities []*T, columns, defaultColumns []string, opts TableOptions) error

RenderTable writes entities as a column table to w. columns is the list of dotted field paths; if empty, defaultColumns is used. Headers derive from ColumnHeader unless opts.NoHeader is set.

func RenderYQ

func RenderYQ(w io.Writer, v any, expr string) error

RenderYQ evaluates a yq expression against v (marshaled to YAML) and writes the result to w. The YAML is the same projection PrintYAML renders, so a yq expression and a jq expression address a value by the same names.

func YesNo added in v0.4.5

func YesNo(b bool) string

YesNo renders a bool for a table column.

Types

type ColumnProvider

type ColumnProvider struct {
	// contains filtered or unexported fields
}

ColumnProvider renders columns from arbitrary structs by dotted field path, using reflection. It understands the ConfigHub "Extended<Entity>" envelope convention (e.g. ExtendedUnit{Unit: *Unit}): a bare field name is looked up on the envelope and, failing that, on the embedded entity. It also resolves Labels.<key> / Annotations.<key> against map fields. The zero value is unusable; construct one with NewColumnProvider.

func NewColumnProvider

func NewColumnProvider(entity any) *ColumnProvider

NewColumnProvider creates a provider for the type of entity (typically a pointer to a struct), with common aliases (Name -> Slug) and formatters for time.Time and uuid.UUID.

func (*ColumnProvider) GetValue

func (p *ColumnProvider) GetValue(obj any, fieldPath string) string

GetValue returns the string value of fieldPath on obj.

func (*ColumnProvider) WithAliases

func (p *ColumnProvider) WithAliases(aliases map[string]string) *ColumnProvider

WithAliases adds column-name aliases (alias -> real field path).

func (*ColumnProvider) WithCustomColumns

func (p *ColumnProvider) WithCustomColumns(custom map[string]func(any) string) *ColumnProvider

WithCustomColumns adds named columns computed by a function over the entity.

type CommitFlags

type CommitFlags struct {
	Commit     bool
	ChangeDesc string
}

CommitFlags carry the dry-run/commit controls shared by mutating commands. Mutations are dry-run by default; --commit performs the write and requires --change-desc for provenance.

func (*CommitFlags) Bind

func (c *CommitFlags) Bind(cmd *cobra.Command)

Bind registers --commit and --change-desc on cmd.

func (CommitFlags) Validate

func (c CommitFlags) Validate(summary string) (changeDesc string, dryRun bool, err error)

Validate enforces the commit policy and returns the change description to record (empty on dry-run) plus whether this is a dry-run. summary is suggested in the error when --change-desc is missing on commit.

type OutputKind

type OutputKind int

OutputKind enumerates the supported -o/--output formats.

const (
	OutputDefault OutputKind = iota
	OutputJSON
	OutputYAML
	OutputName
	OutputJQ
	OutputYQ
	OutputWide
	OutputCustomColumns
	OutputTable
)

type OutputSpec

type OutputSpec struct {
	Kind OutputKind
	Arg  string
}

OutputSpec is the parsed form of an --output value. Arg is populated only for formats that take an expression or spec (jq, yq, custom-columns).

func ParseOutput

func ParseOutput(s string) (OutputSpec, error)

ParseOutput parses an --output/-o value into an OutputSpec. Supported values: "" (default), json, yaml, name, wide, table, jq=<expr>, yq=<expr>, custom-columns=<spec>.

func (OutputSpec) Render

func (s OutputSpec) Render(w io.Writer, v any) (handled bool, err error)

Render writes v to w in the spec's format. It returns handled=true for the formats it renders directly (json, yaml, jq, yq); for table/name/wide/ custom-columns/default it returns false so the caller can render its own (typically tabular) view.

type QueryFlags

type QueryFlags struct {
	Where    string
	Filter   string
	Contains string
	Select   string

	// Space scopes, registered by [QueryFlags.BindSpaceLabels]. Component names
	// the Component the Space is a Variant of, by slug or ID; the rest are the
	// standard Space labels, the ones the `cub variant` commands use.
	Component   string
	Environment string
	Region      string
	Owner       string
	Layer       string
	Variant     string

	// ResolveComponent returns the ComponentID of the Component with the given
	// slug. This package does not depend on the API client, so a tool that binds
	// --component sets it from its own; without it, --component accepts only an ID.
	ResolveComponent func(slug string) (string, error)
	// contains filtered or unexported fields
}

QueryFlags are the standard read/list scoping flags, plus the Space-label shorthands that spell a common scope without writing a predicate.

Bind them all with QueryFlags.Bind and QueryFlags.BindSpaceLabels, or bind the groups a command actually supports: a command whose query pins its own --select, for instance, wants QueryFlags.BindWhere rather than the field registered for a caller to override.

QueryFlags.Predicate compiles --where and the label shorthands into one expression. Filter, Contains and Select are not part of it: they are separate query parameters, not clauses.

func (*QueryFlags) Bind

func (q *QueryFlags) Bind(cmd *cobra.Command)

Bind registers --where, --filter, --contains, and --select on cmd. The Space-label shorthands are registered separately, by QueryFlags.BindSpaceLabels.

func (*QueryFlags) BindSelect added in v0.4.5

func (q *QueryFlags) BindSelect(cmd *cobra.Command)

BindSelect registers --select. A command whose query needs particular fields to build its own model should not register it: a caller who narrowed the selection would get those fields back zeroed, with nothing to say they were never fetched.

func (*QueryFlags) BindSpaceLabels added in v0.4.5

func (q *QueryFlags) BindSpaceLabels(cmd *cobra.Command)

BindSpaceLabels registers the Space-label shorthands: --component, --environment, --region, --owner, --layer, --variant, and any label added with QueryFlags.Label.

One Unit-level predicate can reference Unit, Space and Target metadata, so there is no need for separate Space or Target filters: the server does the scoping, and only the matching Units come back. These flags are shorthands over --where for the scopes that come up most.

--component scopes by the Space's Component, and the rest by a label on the Space, which is where the standard labels live. That costs something: the server compiles a term naming the Unit's own columns into SQL, but a term prefixed with another entity it cannot, so it expands the Space for every row the query returned and evaluates the term afterwards. The answer is the same either way. Where a command has the choice -- a label the Units carry themselves -- a --where over Labels.* narrows the read and these do not.

func (*QueryFlags) BindStored added in v0.4.5

func (q *QueryFlags) BindStored(cmd *cobra.Command)

BindStored registers --filter and --contains.

func (*QueryFlags) BindWhere added in v0.4.5

func (q *QueryFlags) BindWhere(cmd *cobra.Command)

BindWhere registers --where alone, for a command that scopes a query but does not let the caller choose the fields or apply a stored Filter.

func (*QueryFlags) Label added in v0.4.5

func (q *QueryFlags) Label(flag, spaceLabel, usage string)

Label adds a Space-label scope beyond the standard set, for a label a single command cares about. Its flag joins the predicate with the others, so a command gets its own scoping without every command growing the flag.

Call it before BindSpaceLabels. The terms it adds follow the standard labels; a predicate is a flat conjunction, so their position carries no meaning.

func (QueryFlags) Predicate added in v0.4.5

func (q QueryFlags) Predicate() (string, error)

Predicate compiles --where and the Space-label shorthands into a single ConfigHub `where` expression, empty when nothing is set (everything the caller can view). A ConfigHub predicate is flat AND-only -- no parentheses, no OR -- so the shorthands are joined to any raw --where with a bare AND.

A label value carrying a quote or a backslash is rejected. The server screens every filter literal with an anchored regexp admitting neither, because it builds its SQL by concatenation with no parameter binding and that screen is what keeps a filter from being an injection point -- so such a value cannot be sent at all, and rewriting it would scope the query to a different value than the operator typed. Rejecting it here names the flag they typed, where the server can only name a position in an expression they never wrote. The API client applies the same rule to the predicates it builds; the rule is restated here because this package does not depend on it.

type TableOptions

type TableOptions struct {
	// NoHeader suppresses the header row.
	NoHeader bool
	// Aliases and CustomColumns are passed to the column provider.
	Aliases       map[string]string
	CustomColumns map[string]func(any) string
}

TableOptions configures RenderTable.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL