cliutil

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Dec 5, 2025 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package cliutil provides a tiny, stdlib-only way to attach structured metadata and sentinels to errors while staying fully composable with the Go standard library. The model is:

  • Each function builds an entry with New(...) passing an optional trailing cause: return doterr.New(ErrRepo, "key", val, cause) // cause last

  • With(...) is a flexible convenience for same-function enrichment. It can:

  • Enrich the rightmost doterr entry within an existing joined error, or

  • Join a new entry if no doterr entry exists, and

  • (Optionally) treat a final trailing error as the cause and join it last.

  • Combine([]error) bundles independent failures into a single error that unwraps to its members, preserving order.

Package cliutil provides output management and synchronized writing for CLI applications.

Index

Constants

View Source
const (
	ExitSuccess             = 0 // Successful execution
	ExitOptionsParseError   = 1 // Command-line option parsing failed
	ExitConfigLoadError     = 2 // Configuration file loading failed
	ExitConfigParseError    = 3 // Configuration parsing/validation failed
	ExitKnownRuntimeError   = 4 // Expected/known runtime error during execution
	ExitUnknownRuntimeError = 5 // Unexpected/unknown runtime error
	ExitLoggerSetupError    = 6 // Logger initialization failed
)
View Source
const (
	DefaultTimeout   = 3
	DefaultQuiet     = false
	DefaultDryRun    = false
	DefaultForce     = false
	DefaultVerbosity = int(LowVerbosity)
)

Variables

View Source
var (
	ErrMissingSentinel     = errors.New("missing required sentinel error")
	ErrTrailingKey         = errors.New("trailing key without value")
	ErrMisplacedError      = errors.New("error in wrong position")
	ErrInvalidArgumentType = errors.New("invalid argument type")
	ErrOddKeyValueCount    = errors.New("odd number of key-value arguments")
	ErrCrossPackageError   = errors.New("error from different doterr package")
	ErrFailedTypeAssertion = errors.New("failed type assertion")
)

Sentinel errors for validation failures

View Source
var (
	ErrShowUsage           = fmt.Errorf("run '%s help' for usage", os.Args[0])
	ErrUnknownCommand      = errors.New("unknown command")
	ErrCommandNotFound     = errors.New("command not found")
	ErrFlagsParsingFailed  = errors.New("flags parsing failed")
	ErrAssigningArgsFailed = errors.New("assigning args failed")

	// ErrOmitUserNotify signals that the error has already been displayed to the user
	// in a user-friendly format, and the technical error message should be omitted
	// from user output (but can still be logged).
	ErrOmitUserNotify = errors.New("omit user notification")
)
View Source
var (
	ErrInvalidateVerbosity = errors.New("invalid verbosity level")
	ErrVerbosityTooLow     = errors.New("verbosity too low; must be between 0..3 inclusive")
	ErrVerbosityTooHigh    = errors.New("verbosity too high; must be between 0..3 inclusive")
)
View Source
var CmdUsageTemplate = template.Must(template.New("cmd_usage").Parse(CmdUsageTemplateText))
View Source
var CmdUsageTemplateText string
View Source
var ErrCommandRegistrationFailed = errors.New("command registration failed")
View Source
var ErrFlagTypeNotDiscoverable = errors.New("flag type is not discoverable")
View Source
var UsageTemplate = template.Must(template.New("usage").Parse(UsageTemplateText))
View Source
var UsageTemplateText string

Functions

func AddCLIOption added in v0.2.0

func AddCLIOption(flagDef FlagDef) (err error)

func AppendErr

func AppendErr(errs []error, err error) []error

func BuildCommandTree

func BuildCommandTree() (err error)

BuildCommandTree builds the command hierarchy from registrations This should be called by gmover.Initialize() after all init() functions complete

func CallInitializerFuncs

func CallInitializerFuncs(args InitializerArgs) (err error)

func CombineErrs

func CombineErrs(errs []error) error

CombineErrs bundles a slice of errors into a single composite error that unwraps to its members. Order is preserved and nils are skipped. Returns nil for an empty/fully-nil slice, or the sole error when there is exactly one.

func ErrValue

func ErrValue[T any](err error, key string) (T, bool)

ErrValue extracts a single metadata value by key with type safety. Returns the value and true if found and the value is of type T. Returns the zero value of T and false if not found or type mismatch.

Example:

status, ok := doterr.ErrValue[int](err, "http_status")
if ok {
    fmt.Printf("Status: %d\n", status)
}

name, ok := doterr.ErrValue[string](err, "parameter_name")

func Errorf

func Errorf(format string, args ...any)

Errorf writes to formatted error writer

func Errors

func Errors(err error) []error

Errors returns the errors stored on a doterr entry. If err is a doterr entry, returns its errors. If err is a joined error (has Unwrap() []error), scans immediate children left-to-right and returns errors from the first doterr entry found. Otherwise returns nil. The returned slice preserves insertion order and is a copy.

Note: These errors may be sentinel errors (e.g., ErrRepo), custom error types (e.g., *rfc9457.Error), or any other error type stored in the entry.

func FindErr

func FindErr[T error](err error) (out T, ok bool)

FindErr walks an error tree (including errors.Join trees) and returns the first match for target (via errors.As).

func Initialize

func Initialize(w Writer) (err error)

func IsTerminalError

func IsTerminalError(err error) (isTermErr bool)

IsTerminalError checks if an error is related to terminal/input operations These errors should abort the entire operation rather than continue

func NewErr

func NewErr(parts ...any) error

NewErr builds a standalone structured entry (no primary cause inside). Accepted parts:

  • error — sentinel/tag (required: at least one, must be first)
  • KV{Key,Value} — explicit key/value
  • "key", value — implicit pair (value can be any type, including error)
  • error — optional trailing cause (joined last via errors.Join)

Pattern: one or more sentinels (error), then zero or more key-value pairs, then optional trailing cause (error). After the first string key, all remaining args must form valid pairs, except for an optional final error. Returns nil if no meaningful parts are provided after validation. Returns a validation error joined with the partial entry if validation fails.

func Printf

func Printf(format string, args ...any)

Printf writes formatted writer

func RegisterCommand

func RegisterCommand(cmd Command, parents ...Command) (err error)

RegisterCommand registers a command with optional parent type declarations First argument is the actual command, remaining arguments are parent type prototypes Example: RegisterCommand(&JobRunCmd{...}, &JobCmd{})

func RegisterInitializerFunc

func RegisterInitializerFunc(f InitializerFunc)

func SetWriter

func SetWriter(w Writer)

SetWriter sets the global writer writer (primarily for testing)

func ShowCmdHelp

func ShowCmdHelp(cmdName string, args UsageArgs) (err error)

ShowCmdHelp displays help for a specific command

func ShowMainHelp

func ShowMainHelp(args UsageArgs) error

ShowMainHelp displays the main help screen

func Stderrf

func Stderrf(format string, args ...any)

func Stdiof

func Stdiof(w io.Writer, format string, args ...any)

func Stdoutf

func Stdoutf(format string, args ...any)

func ValidateCmds

func ValidateCmds() (err error)

ValidateCmds ensures all registered commands have handlers

func ValidateCommands

func ValidateCommands() (err error)

func WithErr

func WithErr(parts ...any) error

WithErr is a flexible enrichment helper. Typical uses:

// Enrich an existing composite error (err may be an errors.Join tree):
err = doterr.With(err, "Foo", 10)

// Build an entry and join a trailing cause in one shot:
err = doterr.With("endpoint", ep, ErrTemplate, cause) // 'cause' is last

Behavior:

  1. If the FIRST arg is an error, it is treated as the base error to enrich: • If it is a doterr entry, merge KVs/sentinels into that entry. • If it is a multi-unwrap (errors.Join tree), find the RIGHTMOST doterr entry, merge into it, and rebuild preserving order. • If no doterr entry is found, a new entry will be joined in (see step 3).

  2. After consuming the base (if present), if the LAST remaining arg is an error, it is treated as the CAUSE and joined LAST.

  3. The remaining middle args (if any) are collected into an entry. If we enriched an existing doterr entry in step 1, that merged entry is used; otherwise, a fresh entry is created. If there is a trailing CAUSE from step 2, the result is errors.Join(entry, cause). If there is no cause, the entry is returned.

Note: For inter-function composition, prefer New() with trailing cause:

return doterr.New(ErrRepo, "key", val, cause) // cause last

Types

type ArgDef

type ArgDef struct {
	Name     string
	Usage    string
	Required bool
	Default  any
	String   *string // Where to assign the argument value
	Example  string  // OPTIONAL: sample value for example generation (e.g., "www")
}

ArgDef defines a positional command argument

type CmdArgs

type CmdArgs struct {
	Name         string
	Usage        string
	Description  string
	DelegateTo   Command
	FlagDefs     []FlagDef  // Legacy flag definitions (will be deprecated)
	FlagSets     []*FlagSet // New FlagSet-based approach
	ArgDefs      []*ArgDef  // Positional argument definitions
	Examples     []Example  // Custom examples
	NoExamples   bool       // Do not display any examples
	AutoExamples bool       // Display auto-generated examples even if custom are provided
	Order        int        // Display order in help (0=last, 1+=ordered)
	FlagName     string     // Flag name that triggers this command (e.g., "setup" for --setup)
	Hide         bool       // Hide from help output
}

type CmdBase

type CmdBase struct {
	CmdRunnerArgs
	// contains filtered or unexported fields
}

CmdBase provides common functionality for all commands It implements the cliutil.Cmd interface

func NewCmdBase

func NewCmdBase(args CmdArgs) *CmdBase

NewCmdBase creates a new command base

func (*CmdBase) AddParent

func (c *CmdBase) AddParent(r reflect.Type)

func (*CmdBase) AddSubCommand

func (c *CmdBase) AddSubCommand(cmd Command)

AddSubCommand returns the subcommands map

func (*CmdBase) ArgDefs

func (c *CmdBase) ArgDefs() []*ArgDef

func (*CmdBase) AssignArgs

func (c *CmdBase) AssignArgs(args []string) (err error)

AssignArgs assigns positional arguments to their defined config fields

func (*CmdBase) AutoExamples

func (c *CmdBase) AutoExamples() bool

func (*CmdBase) DelegateTo

func (c *CmdBase) DelegateTo() Command

DelegateTo returns the command to delegate to, if any

func (*CmdBase) Description

func (c *CmdBase) Description() string

Description returns the command description with flag details

func (*CmdBase) Examples

func (c *CmdBase) Examples() []Example

func (*CmdBase) FlagName added in v0.2.0

func (c *CmdBase) FlagName() string

func (*CmdBase) FlagSets

func (c *CmdBase) FlagSets() []*FlagSet

func (*CmdBase) FullNames

func (c *CmdBase) FullNames() (names []string)

FullNames returns the command names prefixed with any parent names

func (*CmdBase) IsHidden added in v0.2.0

func (c *CmdBase) IsHidden() bool

func (*CmdBase) Name

func (c *CmdBase) Name() string

Name returns the command name

func (*CmdBase) NoExamples

func (c *CmdBase) NoExamples() bool

func (*CmdBase) Order

func (c *CmdBase) Order() int

func (*CmdBase) ParentTypes

func (c *CmdBase) ParentTypes() []reflect.Type

func (*CmdBase) ParseFlagSets

func (c *CmdBase) ParseFlagSets(args []string) (remainingArgs []string, err error)

ParseFlagSets parses flags using the new FlagSet-based approach

func (*CmdBase) SetCommandRunnerArgs

func (c *CmdBase) SetCommandRunnerArgs(args CmdRunnerArgs)
func (c *CmdBase) Logger() *slog.Logger {
	return c.logger
}
func (c *CmdBase) Writer() Writer {
	return c.writer
}

func (*CmdBase) SetDelegateTo

func (c *CmdBase) SetDelegateTo(cmd Command)

SetDelegateTo sets the command to delegate to

func (*CmdBase) Usage

func (c *CmdBase) Usage() string

Usage returns the command usage string with flags

type CmdRunner

type CmdRunner struct {
	Args CmdRunnerArgs
}

func NewCmdRunner

func NewCmdRunner(args CmdRunnerArgs) *CmdRunner

func (CmdRunner) ParseCmd

func (cr CmdRunner) ParseCmd(args []string) (cmd Command, err error)

func (CmdRunner) RunCmd

func (cr CmdRunner) RunCmd(cmd Command) (err error)

type CmdRunnerArgs

type CmdRunnerArgs struct {
	AppInfo appinfo.AppInfo
	Logger  *slog.Logger
	Writer  Writer
	Context context.Context
	Config  Config
	Options Options
	Args    []string
}

type CmdUsage added in v0.2.0

type CmdUsage struct {
	CmdName     string
	Usage       string
	Description string
	FlagRows    []FlagRow
	SubCmdRows  []SubCmdRow
	Examples    []Example
}

func BuildCmdUsage added in v0.2.0

func BuildCmdUsage(cmd Command) CmdUsage

BuildCmdUsage builds the data structure for command-specific help

type Command

type Command interface {
	Name() string
	FullNames() []string
	Usage() string
	Description() string
	AddSubCommand(Command)
	DelegateTo() Command
	AddParent(reflect.Type)
	ParentTypes() []reflect.Type
	FlagSets() []*FlagSet
	ParseFlagSets([]string) ([]string, error)
	AssignArgs([]string) error
	Examples() []Example
	NoExamples() bool
	AutoExamples() bool
	ArgDefs() []*ArgDef
	Order() int
	SetCommandRunnerArgs(CmdRunnerArgs)
	FlagName() string
	IsHidden() bool
}

Command interface for basic command metadata and delegation

func GetDefaultCommand

func GetDefaultCommand(path string, args []string) (cmd Command, _ string)

GetDefaultCommand retrieves a command or its default at any depth using dot notation

func GetExactCommand

func GetExactCommand(path string) Command

GetExactCommand retrieves a command at any depth using dot notation

func GetSubCmds

func GetSubCmds(path string) []Command

GetSubCmds returns all subcommands for a given path

func GetTopLevelCmds

func GetTopLevelCmds() []Command

GetTopLevelCmds returns all top-level commands sorted by name

func RegisteredCommands added in v0.2.0

func RegisteredCommands() (cmds []Command)

type CommandHandler

type CommandHandler interface {
	Command
	Handle() error
}

CommandHandler interface for commands that actually execute logic

type Config

type Config interface {
	Config()
}

type Example

type Example struct {
	Descr string // short comment, e.g., "Serve from custom directory"
	Cmd   string // the full command line to show
}

type FlagDef

type FlagDef struct {
	Name           string
	Shortcut       byte
	Default        any
	Usage          string
	Required       bool
	Regex          *regexp.Regexp
	ValidationFunc ValidationFunc
	String         *string
	Bool           *bool
	Int64          *int64
	Int            *int
	Example        string // OPTIONAL: sample value for example generation (e.g., "www")
}

FlagDef defines a command flag declaratively

func (*FlagDef) SetValue

func (fd *FlagDef) SetValue(value any)

func (*FlagDef) Type

func (fd *FlagDef) Type() (ft FlagType)

func (*FlagDef) ValidateValue

func (fd *FlagDef) ValidateValue(value any) error

ValidateValue validates the flag value using the defined validation rules

type FlagRow added in v0.2.0

type FlagRow struct {
	Name     string
	Shortcut string
	Usage    string
	Default  string
	Required bool
}

type FlagSet

type FlagSet struct {
	Name     string
	FlagSet  *flag.FlagSet
	FlagDefs []FlagDef
	Values   map[string]any
	// contains filtered or unexported fields
}

FlagSet combines a FlagSet with automatic config binding

func GetGlobalFlagSet added in v0.3.0

func GetGlobalFlagSet() *FlagSet

func (*FlagSet) Assign

func (fs *FlagSet) Assign() (err error)

func (*FlagSet) Build

func (fs *FlagSet) Build() (err error)

func (*FlagSet) FlagNames

func (fs *FlagSet) FlagNames() (names []string)

func (*FlagSet) GetUnknownFlags added in v0.2.0

func (fs *FlagSet) GetUnknownFlags() []string

GetUnknownFlags returns the list of flags that were not recognized by this FlagSet

func (*FlagSet) Parse

func (fs *FlagSet) Parse(args []string) (remainingArgs []string, err error)

Parse extracts flags and returns remaining args

func (*FlagSet) ResetUnknownFlags added in v0.2.0

func (fs *FlagSet) ResetUnknownFlags()

ResetUnknownFlags clears the list of unknown flags

func (*FlagSet) Validate

func (fs *FlagSet) Validate() (err error)

Validate validates all flag values using their defined validation rules

type FlagType

type FlagType int

FlagType represents the type of a command flag

const (
	UnknownFlagType FlagType = iota
	StringFlag
	BoolFlag
	IntFlag
	Int64Flag
)

type GlobalOptions added in v0.3.0

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

func GetGlobalOptions added in v0.3.0

func GetGlobalOptions() *GlobalOptions

func NewGlobalOptions added in v0.3.0

func NewGlobalOptions(args GlobalOptionsArgs) (*GlobalOptions, error)

NewGlobalOptions creates a new GlobalOptions instance from raw values. This is useful when loading options from configuration files or other sources. Any nil values will use the corresponding defaults.

func ParseGlobalOptions added in v0.3.0

func ParseGlobalOptions(osArgs []string) (_ *GlobalOptions, _ []string, err error)

ParseGlobalOptions converts raw options into GlobalOptions.

Expects os.Args as input. Strips program name and defaults to ["help"] if no args.

func (*GlobalOptions) DryRun added in v0.3.0

func (o *GlobalOptions) DryRun() bool

func (*GlobalOptions) Force added in v0.3.0

func (o *GlobalOptions) Force() bool

func (*GlobalOptions) Options added in v0.3.0

func (o *GlobalOptions) Options()

func (*GlobalOptions) Quiet added in v0.3.0

func (o *GlobalOptions) Quiet() bool

func (*GlobalOptions) Timeout added in v0.3.0

func (o *GlobalOptions) Timeout() time.Duration

func (*GlobalOptions) Verbosity added in v0.3.0

func (o *GlobalOptions) Verbosity() Verbosity

type GlobalOptionsArgs added in v0.3.0

type GlobalOptionsArgs struct {
	Quiet     *bool
	Verbosity *int
	Timeout   *int
	DryRun    *bool
	Force     *bool
}

type GlobalOptionsGetter added in v0.3.0

type GlobalOptionsGetter interface {
	GlobalOptions() *GlobalOptions
}

type InitializerArgs

type InitializerArgs struct {
	Writer Writer
}

type InitializerFunc

type InitializerFunc func(InitializerArgs) error

type KV

type KV interface {
	Key() string
	Value() any
}

KV represents a key/value metadata pair. Keys are preserved in insertion order, and values may be of any type.

func ErrMeta

func ErrMeta(err error) []KV

ErrMeta returns the key/value pairs stored on a doterr entry. If err is a doterr entry, returns its metadata. If err is a joined error (has Unwrap() []error), scans immediate children left-to-right and returns metadata from the first doterr entry found. Otherwise returns nil. The returned slice preserves insertion order and is a copy.

type NULL

type NULL = struct{}

type Options

type Options interface {
	Options()
}

type SubCmdRow added in v0.2.0

type SubCmdRow struct {
	Name string
	Desc string
}

type TopCmdRow

type TopCmdRow struct {
	Display string // e.g. "serve [sub]" padded in template
	Desc    string
	Order   int // Display order (0=last, 1+=ordered)
}

type Usage

type Usage struct {
	appinfo.AppInfo
	CLIWriter   Writer
	TopCmdRows  []TopCmdRow
	GlobalFlags []FlagRow
	Examples    []Example
}

func BuildUsage

func BuildUsage(args UsageArgs) Usage

BuildUsage Build the data for the template (auto + optional custom examples)

type UsageArgs

type UsageArgs struct {
	appinfo.AppInfo
	Writer Writer
}

type ValidationFunc

type ValidationFunc func(value any) error

ValidationFunc validates a flag value and returns an error if invalid

type Verbosity

type Verbosity int
const (
	NoVerbosity Verbosity = iota
	LowVerbosity
	MediumVerbosity
	HighVerbosity
)

func ParseVerbosity

func ParseVerbosity(verbosity int) (v Verbosity, err error)

type Writer

type Writer interface {
	Printf(string, ...any)
	Errorf(string, ...any)
	Loud() Writer
	V2() Writer
	V3() Writer
	Writer() io.Writer
	ErrWriter() io.Writer
}

Writer defines the interface for user-facing writer

func GetWriter

func GetWriter() Writer

GetWriter returns the current writer writer

func Loud

func Loud() Writer

Loud returns a Writer that ignores Quiet setting

func NewWriter

func NewWriter(args *WriterArgs) Writer

NewWriter creates a console writer writer

type WriterArgs

type WriterArgs struct {
	Quiet     bool
	Verbosity Verbosity
}

type WriterLogger

type WriterLogger struct {
	Writer
	*slog.Logger
	// contains filtered or unexported fields
}

func NewWriterLogger

func NewWriterLogger(writer Writer, logger *slog.Logger) WriterLogger

func (WriterLogger) ErrorError

func (wl WriterLogger) ErrorError(msg string, args ...any) (err error)

func (WriterLogger) Info

func (wl WriterLogger) Info(format string, args ...any)

func (WriterLogger) InfoLoud

func (wl WriterLogger) InfoLoud(msg string, args ...any)

func (WriterLogger) InfoPrint

func (wl WriterLogger) InfoPrint(msg string, args ...any)

func (WriterLogger) LogError added in v0.2.1

func (wl WriterLogger) LogError(msg string, attrs ...any)

func (WriterLogger) Printf

func (wl WriterLogger) Printf(format string, args ...any)

func (WriterLogger) V2

func (wl WriterLogger) V2() WriterLogger

func (WriterLogger) V3

func (wl WriterLogger) V3() WriterLogger

func (WriterLogger) WarnError

func (wl WriterLogger) WarnError(msg string, args ...any)

func (WriterLogger) WriteErrorf added in v0.2.1

func (wl WriterLogger) WriteErrorf(format string, args ...any)