Rad Args is a Go library for CLI argument parsing that provides flexible positional and flag-based argument handling with support for subcommands, constraints, and various data types. It is designed to be user-friendly by default, with clear help messages and exit-on-error behavior.
- Cmd: Central structure representing a command or subcommand. It holds flag definitions and subcommands.
- Flags: Named arguments that can be passed positionally or as flags.
- Subcommands: Nested commands with their own argument sets.
- Global Flags: Flags inherited by all subcommands.
- ParseOpt: A functional option for configuring parsing behavior.
- BoolFlag: Boolean values (true/false).
- StringFlag: String values with optional enum/regex constraints.
- IntFlag: Integer values with optional min/max constraints.
- Int64Flag: Int64 values with optional min/max constraints.
- Float64Flag: Float values with optional min/max constraints.
- BoolSliceFlag: Array of bools.
- StringSliceFlag: Array of strings.
- IntSliceFlag: Array of integers.
- Int64SliceFlag: Array of int64s.
- Float64SliceFlag: Array of float64s.
All flags support:
- Name: Primary identifier (required).
- Short: Single character short flag (optional).
- Usage: Help text description.
- Default: Default value when not specified.
- Optional: Whether the flag is required (default: false).
- Hidden: When true, the flag is omitted from all help output (default: false).
- HiddenInShortHelp: When true, the flag is omitted from short help (
-h) but still shown in long help (--help) (default: false). - PositionalOnly: Flag can only be passed positionally.
- FlagOnly: Flag can only be passed as a named flag.
- Excludes: Flags that cannot be used together with this flag. Works one-way like
Requires- only the flag declaring the exclusion needs to specify the relationship. - Requires: Flags that must be present when this flag is used.
When a flag excludes another flag, and both flags are required, the exclusion takes precedence over the requirement. This means:
- If flag A excludes flag B, and both are required, providing flag A makes flag B no longer required.
- If neither flag is provided, both flags will be reported as missing required arguments.
- If both flags are provided, an exclusion error will be raised.
Example:
// Both flags are required and mutually exclusive
fileFlag, _ := NewString("file").SetExcludes([]string{"url"}).Register(cmd)
urlFlag, _ := NewString("url").SetExcludes([]string{"file"}).Register(cmd)
// Valid: Only file provided (url is no longer required due to exclusion)
cmd.ParseOrError([]string{"--file", "input.txt"})
// Valid: Only url provided (file is no longer required due to exclusion)
cmd.ParseOrError([]string{"--url", "https://example.com"})
// Invalid: Both provided (exclusion violation)
cmd.ParseOrError([]string{"--file", "input.txt", "--url", "https://example.com"})
// Invalid: Neither provided (both missing required arguments)
cmd.ParseOrError([]string{})- EnumConstraint (string): Restricts value to a specific set.
- RegexConstraint (string): Restricts value to match a regex pattern.
- Min/Max (numeric): Restricts value to a minimum or maximum.
- Separator: Character to split a single argument into multiple values.
- Variadic: Consume multiple consecutive arguments until the next flag.
The library provides two primary methods for parsing arguments:
// Parses args, printing usage and exiting on error.
func (c *Cmd) ParseOrExit(args []string, opts ...ParseOpt)
// Parses args, returning a ParseError on failure.
func (c *Cmd) ParseOrError(args []string, opts ...ParseOpt) *ParseErrorParsing behavior can be customized using functional options (ParseOpt):
- WithIgnoreUnknown(bool): If
true, unknown flags and arguments are collected (retrievable viaGetUnknownArgs()) instead of causing a parsing error.
- All flags can be passed positionally unless marked as
FlagOnly. - Unless explicitly marked as
PositionalOnly, all flags are also available as named flags (e.g.,--flag-name). - Positional assignment is left-to-right based on registration order.
- If a flag is set via a named flag, it's skipped in positional assignment.
- Named flags override positional values.
- Later occurrences override earlier ones.
- Activated when any IntFlag has a short name.
- Applies per-command (including inherited global flags).
- In this mode, standalone negative numbers are treated as short flags.
- To pass negative integers, use
--flag=-5syntax.
- Multiple occurrences:
--flag value1 --flag value2→["value1", "value2"] - Separator:
--flag "value1,value2"with separator "," →["value1", "value2"] - Variadic:
--flag value1 value2→["value1", "value2"](stops at next flag) - Combined: Variadic + separator processes both mechanisms.
- Multiple bool shorts can be clustered:
-abcis equivalent to-a -b -c. - A non-bool flag can terminate a cluster:
-abc value(wherecis non-bool).
cmd := NewCmd("mycmd")
subCmd := NewCmd("subcmd")
invoked, err := cmd.RegisterCmd(subCmd)- Subcommands must appear immediately after the parent command.
- The first positional argument matching a subcommand name invokes that subcommand.
- If no match is found, parsing continues with the parent command's arguments.
- Registered with
WithGlobal(true)option. - Automatically inherited by all subcommands.
- Global flags preserve their state across subcommand parsing.
- SetDescription(string): Sets a description for the command, shown at the top of the help text.
- SetCustomUsage(func(isLongHelp bool)): Overrides the entire usage generation logic.
- SetHelpEnabled(bool): Disables the automatic registration of
-h/--helpflags if set tofalse. - SetHidden(bool): When true, the command is omitted from the parent's help output entirely (both
-hand--help) and from shell completion. The command remains fully invocable by name. - SetHiddenInShortHelp(bool): When true, the command is omitted from short help (
-h) but still listed in long help (--help). It remains available in shell completion. - SetAutoHelpOnNoArgs(bool): When enabled, automatically shows help (equivalent to
-h) if no arguments are provided and the command has required flags. This provides a user-friendly experience when users run a command without arguments to see what options are available.
When SetAutoHelpOnNoArgs(true) is enabled, the following behavior applies:
- Trigger condition: No arguments provided AND command has required flags
- Action: Show short help (
-hequivalent) and exit with code 0 - Scope: Works for both main commands and subcommands
- Precedence: Takes precedence over "missing required arguments" errors
Example:
cmd := NewCmd("deploy")
cmd.SetAutoHelpOnNoArgs(true)
// Required flags
NewString("environment").Register(cmd)
NewString("version").Register(cmd)
// Optional flags
NewBool("force").SetOptional(true).Register(cmd)
// Running with no args shows help instead of error
cmd.ParseOrError([]string{}) // Shows help, exits 0
// Running with some args works normally
cmd.ParseOrError([]string{"--environment", "prod", "--version", "1.2.3"}) // Parses normallyThe library provides two parsing methods with different error handling behaviors:
ParseOrExit: Prints errors and usage tostderr, then exits with appropriate code (0 for help, 1 for errors)ParseOrError: Returns errors for programmatic handling, never callsexit()
For testability, the library uses interface-based dependency injection for exit functionality and stderr writing, allowing for clean test mocking without race conditions.
The library distinguishes between two categories of errors with different presentation:
These are bugs in the code using the Ra library, not user input mistakes. They show only the error message (no usage):
- Constraint validation errors: Constraints referencing undefined flags (e.g.,
SetRequires([]string{"nonexistent"})) - Unsupported flag type errors: Internal programming bugs where a new flag type wasn't fully implemented
Programming errors return a *ProgrammingError type that can be detected with errors.As().
These are mistakes in command-line input by end users. They show error message + usage:
- Unknown flag
- Missing required flag value
- Type conversion errors
- Constraint violations (enum, regex, min/max)
- Missing required arguments
- Relational constraint violations (requires/excludes logic)
A special exported error constant returned when help/usage is displayed:
var HelpInvokedErr = errors.New("help invoked")When returned:
-hor--helpflags are used- Auto-help triggers due to no arguments + required flags
Usage:
err := cmd.ParseOrError(args)
if err == ra.HelpInvokedErr {
// Help was shown, handle appropriately
return
} else if err != nil {
// Handle actual parsing error
log.Fatal(err)
}Behavior:
ParseOrErrorreturnsHelpInvokedErrwithout exitingParseOrExitdetects help invocation, outputs help text, and exits with code 0
This provides clean separation between help invocation and actual parsing errors.
- Configured(name): Returns
trueonly if the user explicitly provided the flag. - GetUnknownArgs(): Returns unrecognized arguments when
WithIgnoreUnknown(true)is used. - used: A per-command boolean indicating if a subcommand was invoked.
- If a flag has no default and is not marked
Optional, parsing errors if it's not provided. - If a default is set,
Configured(name)returnsfalsewhen the default value is used. - Optional flags without defaults get the zero value of their type.
// Register with a returned pointer
flagPtr, err := NewString("name").Register(cmd)
// Register with an existing pointer
err := NewString("name").RegisterWithPtr(cmd, existingPtr)
// Register as a global flag
err := NewString("name").RegisterWithPtr(cmd, ptr, WithGlobal(true))- Flag names must be unique within a command.
- Default values must satisfy any defined constraints (enum, regex, min/max).
- Constraint violations return an error during registration.
- Global flag registration propagates to subcommands.
- Generic
Flag[T]andSliceFlag[T]structures ensure type safety. - Registration validates that flag names are unique.
When helpEnabled is true (the default), two help flags are automatically registered:
-h: Triggers the "short help" output.--help: Triggers the "long help" output.
The only difference between short and long help is that flags and commands marked HiddenInShortHelp are excluded from the short help output but included in the long help output.
Flags appear in usage output in the order they were registered, not alphabetically. This preserves the logical ordering that developers choose when defining their CLI interface.
The generated usage string follows a structured format:
<description>
Usage:
<synopsis>
Script args:
<flags...>
Global options:
<global flags...>
Example Output:
A rad-powered recreation of 'um', with the help of 'tldr'.
Allows you to check the tldr for commands, but then also
add your own notes and customize the notes in their own
entries.
Usage:
hm <task> [OPTIONS]
Script args:
--task str
-e, --edit
-l, --list Lists stored entries. Exits after.
--reconfigure Enable to reconfigure hm.
Global options:
-d, --debug Enables debug output. Intended for Rad script developers.
--color str Control output colorization. Valid values: [auto, always, never]. (default auto)
-q, --quiet Suppresses some output.
--confirm-shell Confirm all shell commands before running them.
-h, --help Print usage string.
The following methods can be used to manually generate usage strings:
GenerateShortUsage() stringGenerateLongUsage() string
Ra also provides methods to generate individual sections of the usage output, allowing for custom composition:
Description Section:
GenerateDescription() string- Returns the command description with trailing newlines (empty string if no description)
Synopsis Section:
GenerateSynopsis(isLongHelp bool) string- Returns the command synopsis lineGenerateShortSynopsis() string- Convenience method equivalent toGenerateSynopsis(false)GenerateLongSynopsis() string- Convenience method equivalent toGenerateSynopsis(true)
Commands Section:
GenerateCommandsSection(isLongHelp bool) string- Returns the subcommands section with header (empty string if no subcommands)GenerateShortCommandsSection() string- Convenience method equivalent toGenerateCommandsSection(false)GenerateLongCommandsSection() string- Convenience method equivalent toGenerateCommandsSection(true)
Arguments Section:
GenerateArgumentsSection(isLongHelp bool) string- Returns the script-level arguments section with header (empty string if no visible arguments)GenerateShortArgumentsSection() string- Convenience method equivalent toGenerateArgumentsSection(false)GenerateLongArgumentsSection() string- Convenience method equivalent toGenerateArgumentsSection(true)
Global Options Section:
GenerateGlobalOptionsSection(isLongHelp bool) string- Returns the global options section with header (empty string if no visible global options)GenerateShortGlobalOptionsSection() string- Convenience method equivalent toGenerateGlobalOptionsSection(false)GenerateLongGlobalOptionsSection() string- Convenience method equivalent toGenerateGlobalOptionsSection(true)
Example Usage:
// Generate only the synopsis
synopsis := cmd.GenerateShortSynopsis()
fmt.Println(synopsis) // Output: myapp <input> [OPTIONS]
// Create custom usage by combining sections
var customUsage strings.Builder
customUsage.WriteString("My Custom Usage:\n")
customUsage.WriteString(cmd.GenerateShortSynopsis())
customUsage.WriteString("\n")
customUsage.WriteString(cmd.GenerateShortArgumentsSection())Note: Individual chunk methods respect the same visibility rules as the full usage generation (hidden flags, short vs. long help, etc.).
- Override via
SetCustomUsage(func(isLongHelp bool)). The boolean parameter indicates whether long help (--help) was requested. - Inside the custom function, you can call
GenerateShortUsage/GenerateLongUsageto build upon the default output. The*Cmdinstance must be captured in a closure by the user if it's needed.
- Not designed for concurrent access to same Cmd instance.
- Not designed for repeated parsing of same Cmd instance.
- Create a new Cmd instance for each parse operation.
- Use separate Cmd instances for concurrent parsing.
- The library must behave deterministically.