optargs

package module
v0.0.0-...-c671094 Latest Latest
Warning

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

Go to latest
Published: Jan 11, 2026 License: MIT Imports: 8 Imported by: 0

README

OptArgs

Build Coverage

This is a Go library for parsing optional arguments from the command line.

This package aims to be a clean, small, and infinately extensible library for parsing the optional arguments on the CLI.

At the heart of this design is a strict mandate on what CLI arguments should look like. This tool defers to definitions set down by the POSIX getopt() definitions, as well as the extended getopt() and getopt_long() syntax provided by GNU.

This means that this tool is 100% compatible with the interpretation of the CLI from a POSIX/GNU perspective, and is capable of reproducing any optional argument pairing that would normally be capable with getopt() and getopt_long(). This also means that this tool does not dictate any sort of rules about what a good UI should look like for the end user. Developers are free to come up with whatever CLI they feel fits their user base.

This tool deviates from getopt() and getopt_long in a few aspects:

  1. While this library considers interpretation of the CLI as standardized by POSIX/GNU, the library does not attempt to implement getopt() or getopt_long().

  2. This is not a C/C++ library, but a Go library, and a different approach is taken to achieving the same level of user interaction.

  3. This library supports chaining parsers to allow for implementing sub-commands.

CLI Syntax

OptArgs considers the following syntax/rules as the strict CLI policy for all options:

  • Encountering a double-hypen (--) with no argument text shall terminate any further option parsing after removing the -- from the argument list.

  • A PosixlyCorrect flag can be set which will terminate all parsing whenever an unknown non-option/command is encountered. This effectively prevents the CLI from supporting adding options to the end of the CLI. Example: myCommand <file> --help will not work if PosixlyCorrect is set.

  • Irrespective of the PosixlyCorrect flag, all arguments for a sub-command must appear somewhere after the sub-command in the argument list.

Note: The POSIXLY_CORRECT environment variable will influence this behavior, as is required by the POSIX standard.

Allowed Patterns
  • --[[:alnum:]_-]+: E.g. --1, --two, --a, --bee
  • --[[:alnum:]_-]+=<arg>: E.g. --foo=bar, --2fa=1234
  • --[[:alnum:]_-]+ <arg>: E.g. --bar boo, --data '{"key": "value"}'
Short Options
  • All short options start with a single hyphen. (-)
  • All short options are case-sensitive by default.
  • Arguments to short options are space delimited, that means they only may appear as the next element in the argument list.
  • Short options may be compacted such that all nearby short options are grouped together proceeded with a single hypen (-).
  • An optional argument may be passed to compacted options. In doing so the optional argument is assumed to belong to the last option in the compacted list which accepts an optional argument.

During argument compaction, any optional argument does not need to belong to the last option, but rather the last option which supports an optional argument.

Example from tar: tar -tfv file.tar, where the -f option is passed file.tar.

Long Options
  • All long-options start with a double hyphen. (--)
  • All long options are case-insensitive by default.
  • Arguments to long options can be part of the same argument separated by an equal sign (=), or can appear as the next argument in the list; should the next argument not begin with a hyphen (-).
  • Long arguments may comprise of a single character, e.g. --f. Such arguments are still parsed according to the long-option syntax.
  • Long arguments do not support argument compaction.

Design

The fundemental design of the library is fairly straight forward and relativelty simple. The entire parser is designed to be orthogonal in how it handles short-options, short-only options, long-options, long-only options, and sub-commands. This means that there is no special exception for how an option of any type is handled when compared to how a sub-command is handled.

The core system comprises of:

  1. The CLI parser: This component only handles parsing the CLI into a parse list and shifting "non-optional" arguments to the end of the parse list. The parser also normalizes the CLI. I.e. It expands compacted short options, as well as splitting long-options which are delimited by an equal sign. During expansion of compacted short-options, should an optional argument be found, the parser performs a look-behind to find the last short-option which accepts an argument.

  2. Iterating the parse list and executing any function which is subscribed to a particular option, or non-option.

The subscription system supports adding a func to be called whenever a matching non-optional argument is encountered. This function maintains a map of non-optional names to func pairings which can be extended using the AddCmd() method. The outer parser will stop processing the CLI list whenever a sub-command is encountered. Instead, a sub-command parser is passed a copy of the parent parser which is inspected whenever a optional argument is encountered which is unknown to the sub-command. Calling the parent parser is done per option and not for the remaining argument list. This way all options are always global, by default, to all child commands in the command tree.

Features

There are a number of interesting features that manifest from the design approach that OptArgs has taken as the design makes it relatively straight forward to support arbitrary use cases.

Non-Argument Options

Boolean flags that don't require explicit values. When present, they toggle to true.

# Enable verbose mode
./myapp --verbose

# Multiple boolean flags
./myapp --verbose --debug --force

📁 Code Example

Short-Only Options

Single-character flags that provide convenient shortcuts for frequently used options.

# Short form
./myapp -o output.txt -v -h localhost

# Mixed short and long
./myapp -v --output=result.txt

📁 Code Example

Short-Option Compaction

POSIX-style compaction allows multiple short options to be combined into a single argument. When an optional argument is provided, it belongs to the last option in the compacted sequence that accepts an argument.

# Basic compaction (equivalent to -v -f -x)
./myapp -vfx

# Compaction with argument to last option
./myapp -vfo output.txt        # -v -f -o output.txt

# Compaction with optional argument attached
./myapp -vf123                 # -v -f=123 (if -f accepts optional arg)

# Complex compaction with mixed argument types
./myapp -abc123 input.txt      # -a -b -c=123 input.txt

# Real-world example (tar-style)
tar -xzf archive.tar.gz        # -x -z -f=archive.tar.gz
tar -xzvf archive.tar.gz       # -x -z -v -f=archive.tar.gz

# With optional arguments
./myapp -vvf5 --output=result  # -v -v -f=5 --output=result

📁 Code Example

Long-Only Options

Descriptive multi-character flags for clarity and self-documentation.

# Long descriptive flags
./myapp --config-file=/etc/myapp/config.json
./myapp --database-url=postgres://localhost/mydb
./myapp --max-connections=100

# With equals syntax
./myapp --output-format=json --log-level=debug
Count Options

Flags that can be repeated to increase a counter value, useful for verbosity levels.

# Increase verbosity level
./myapp -v                    # verbosity = 1
./myapp -vv                   # verbosity = 2
./myapp -vvv                  # verbosity = 3

# Long form repetition
./myapp --verbose --verbose --verbose

# Mixed usage
./myapp -v --verbose -v       # verbosity = 3

📁 Code Example

Boolean Toggle Options

Boolean flags with enhanced syntax supporting explicit true/false values and negation.

# Simple boolean (sets to true)
./myapp --debug

# Explicit values
./myapp --debug=true
./myapp --debug=false

# Various boolean formats
./myapp --enabled=1           # true
./myapp --enabled=0           # false
./myapp --enabled=yes         # true
./myapp --enabled=no          # false
Boolean w/ Inversion Options

Boolean flags that support negation syntax for intuitive toggling.

# Enable colors (default behavior)
./myapp --colors

# Disable colors using negation
./myapp --no-colors

# Other negation examples
./myapp --no-cache --no-verify --no-interactive
N-Way Toggle Options

Enumerated options that cycle through multiple states or accept specific values.

# Set log level
./myapp --log-level=debug
./myapp --log-level=info
./myapp --log-level=warn
./myapp --log-level=error

# Output format selection
./myapp --format=json
./myapp --format=yaml
./myapp --format=table

# Compression level
./myapp --compression=none
./myapp --compression=fast
./myapp --compression=high
Many-to-One Options

Multiple flags that can set the same destination variable, useful for aliases.

# All of these show help
./myapp --help
./myapp -h
./myapp --usage

# Version information aliases
./myapp --version
./myapp -V
./myapp --ver

# Multiple ways to set the same config
./myapp --config=file.json
./myapp --configuration=file.json
./myapp -c file.json
Complex Data Structure Options

Flags that parse complex data structures like slices, maps, or custom types.

# String slices (comma-separated)
./myapp --tag=web,api,database

# String slices (repeated flags)
./myapp --tag=web --tag=api --tag=database

# Integer slices
./myapp --port=8080,8081,8082
./myapp --port=8080 --port=8081 --port=8082

# Key-value pairs (environment variables)
./myapp --env=DEBUG=true --env=PORT=8080 --env=HOST=localhost

# Mixed complex structures
./myapp --tag=web,api --port=8080,8081 --env=NODE_ENV=production

📁 Code Example

Destination Variable Options

Direct binding of flag values to variables for automatic population.

# All flags automatically populate their respective variables
./myapp --host=api.example.com --port=443 --timeout=30s --verbose --retries=5

# Mixed short and long forms
./myapp -h api.example.com -p 443 --timeout=1m -v --retries=3

# Using equals syntax
./myapp --host=localhost --port=8080 --timeout=10s
Option Inheritance

Flags that inherit values from parent contexts or configuration files.

# Global flags affect all subcommands
./myapp --verbose server --port=8080
./myapp --config=/etc/myapp.conf client --url=http://localhost

# Subcommand-specific flags
./myapp server --port=8080 --host=0.0.0.0
./myapp client --url=http://api.example.com --timeout=30s
Option Overloading

Multiple definitions of the same flag name with different behaviors based on context.

# Config as file path
./myapp --config=/path/to/config.json

# Config as inline JSON
./myapp --config='{"database": {"host": "localhost"}}'

# Context determines interpretation
./myapp --config=production.yml     # file
./myapp --config='key: value'       # inline YAML
Sub-Command Directories

Organized command structures with hierarchical flag inheritance.

# Global flags before subcommand
./myapp --verbose --config=/etc/myapp.conf server --port=8080

# Subcommand with its own flags
./myapp server --port=8080 --host=0.0.0.0 --workers=4

# Client subcommand
./myapp client --url=http://localhost:8080 --timeout=30s

# Nested subcommands
./myapp database migrate --dry-run --verbose
./myapp database backup --output=/backups/db.sql
Automatic Help Text

Built-in help generation with customizable formatting and usage information.

# Show help
./myapp --help
./myapp -h

# Subcommand help
./myapp server --help
./myapp client --help

# Example output:
# MyApp - A sample application
#
# Usage: myapp [OPTIONS] COMMAND
#
# Options:
#   -h, --host hostname     Server hostname (default "localhost")
#   -p, --port port         Server port number (default 8080)
#       --timeout duration  Connection timeout (default 30s)
#   -v, --verbose          Enable verbose output
#
# Commands:
#   server    Start the server
#   client    Run as client
#
# Examples:
#   myapp --host=api.example.com server --port=443
#   myapp client --url=http://localhost:8080
Advanced GNU/POSIX Features

OptArgs supports sophisticated GNU getopt_long() features including special characters in option names and longest matching patterns.

# Special characters in option names
./myapp --system7:verbose=detailed
./myapp --config=env production
./myapp --db:host=primary=db1.example.com
./myapp --app:level=debug=trace

# Longest matching (enable-bobadufoo wins over enable-bob)
./myapp --enable-bobadufoo=advanced

# Complex nested syntax
./myapp --system7:path=bindir=/usr/local/bin
./myapp --cache:url=redis=redis://localhost:6379

# Multiple special characters
./myapp --app:config=env:prod=live-settings

📁 Code Example

Real-World Usage Examples
# Web server with comprehensive configuration
./myapp server \
  --host=0.0.0.0 \
  --port=8080 \
  --workers=4 \
  --timeout=30s \
  --log-level=info \
  --tag=web,api,production \
  --env=NODE_ENV=production \
  --env=DEBUG=false \
  --no-cache

# Database operations
./myapp db migrate \
  --config=/etc/myapp/db.conf \
  --dry-run \
  --verbose \
  --timeout=5m

# Batch processing with multiple inputs
./myapp process \
  --input=/data/file1.json \
  --input=/data/file2.json \
  --output-format=csv \
  --workers=8 \
  --tag=batch,processing \
  --env=MEMORY_LIMIT=4GB

# Development mode with debugging
./myapp dev \
  --verbose --verbose --verbose \
  --debug \
  --hot-reload \
  --port=3000 \
  --env=NODE_ENV=development \
  --no-minify

Documentation

Overview

Package optargs provides a collection of CLI parsing utilities in order to aid in the development of command line applications.

POSIX/GNU GetOpt

At the heart of the optargs package is a Go implementation of the GNU glibc versions the getopt(3), getopt_long(3), and getopt_long_only(3) functions.

Leveraging GNU/POSIX conventions as the backend parser option means that the parser has a very large degree of flexibility without restricting application design choices.

For example, POSIX/GNU allows for the following:

  • short-only options.
  • long-only options.
  • long and short options that do not require a value. I.e. it should be possoble to pass `--foo` and specify that it never takes a value, and any attempt to pass it a value should be ignored or or result in an error.
  • short-options of any character that is a valid `isgraph()` character; with the exception of `-`, `:` and `;`. This means that the following options are valid: -=, -+, -{, -}, -^, -!, -@, etc.
  • short-option compaction: `-abc` is the equivalent of `-a -b -c`
  • short-option compaction with optional args: `-abarg` is the equivalent of `-a -b arg`
  • arguments to short options that begin with `-`: `-a -1` should pass `-1` as an argument to `-a`
  • long-arguments that include any `isgraph()` character in the name, this includes allowing `=` in the name of the argument. For example, `--foo=bar=boo` should map `foo=bar` as the Flag, and `boo` as the value to the flag. This potentially also allows for curious long-arg syntax sych as: `--command:arg=value`.
  • many-to-one flag mappings. For example, the GNU `ls` command supports `--format=<format>` where each possible `<format>` options is also supported by a unique short-option. For example: `--format=across` = `-x`, `--format=commas` = `-m`, `--format=horizontal` = `-x`, `--format=long` = `-l`, etc.
  • The GNU `-W` flag which allows short-options to behave like an undefined long-option. E.g. `-W foo` should be interpretted as if `--foo` was passed to the application.
  • long-options that may look similar, but behave differently, from short options. E.g. `-c` and `--c` are allowed to behave differently.

It is always possible to implement a Flag handler which imposes opinionated rules atop a non-opinionated parser, but it is not possible to write a less opinionated Flag handler atop an opinionated parser. To that end, the [optarg] parsers do not make any judgements outside of strictly adhering to the POSIX/GNU conventions. Applications are free to implement their own argument handler to best-fit their application's needs.

Flags()

Optargs supports traditional Go style flags which act as convenience methods around GetOpt, GetOptLong, and GetOptLongOnly with the aim of fully supporting drop-in replacements commonly used CLI tooling, such as:

While these packages are quite useful, they have some fundamental limitations and quirks that come from their design choices which aim to be overcome by optargs and in the case of spf13/pflag, those quirks ultimately percolate up to the user, such as spf13/pflag's boolean flags. Or putting arbitrary restrictions on applications, such as suporting long-only options, but not allowing short-only options. Or not supporting true non-option flags. I.e. many (all?) of the existing Go flag packages only allow an argument to a flag to be optional or required and are not capable of handling flags that never require an argument.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ArgType

type ArgType int
const (
	NoArgument ArgType = iota
	RequiredArgument
	OptionalArgument
)

type CommandRegistry

type CommandRegistry map[string]*Parser

CommandRegistry manages subcommands for a parser using a simple map

func NewCommandRegistry

func NewCommandRegistry() CommandRegistry

NewCommandRegistry creates a new command registry

func (CommandRegistry) AddAlias

func (cr CommandRegistry) AddAlias(alias, existingCommand string) error

AddAlias creates an alias for an existing command

func (CommandRegistry) AddCmd

func (cr CommandRegistry) AddCmd(name string, parser *Parser) *Parser

AddCmd registers a new subcommand with the parser Returns the registered parser for chaining

func (CommandRegistry) ExecuteCommand

func (cr CommandRegistry) ExecuteCommand(name string, args []string) (*Parser, error)

ExecuteCommand finds and executes a command

func (CommandRegistry) ExecuteCommandCaseInsensitive

func (cr CommandRegistry) ExecuteCommandCaseInsensitive(name string, args []string, caseIgnore bool) (*Parser, error)

ExecuteCommandCaseInsensitive finds and executes a command with case insensitive matching

func (CommandRegistry) GetAliases

func (cr CommandRegistry) GetAliases(targetParser *Parser) []string

GetAliases returns all aliases for a given parser

func (CommandRegistry) GetCommand

func (cr CommandRegistry) GetCommand(name string) (*Parser, bool)

GetCommand retrieves a parser by command name

func (CommandRegistry) GetCommandCaseInsensitive

func (cr CommandRegistry) GetCommandCaseInsensitive(name string, caseIgnore bool) (*Parser, bool)

GetCommandCaseInsensitive retrieves a parser by command name with case insensitive matching

func (CommandRegistry) HasCommands

func (cr CommandRegistry) HasCommands() bool

HasCommands returns true if any commands are registered

func (CommandRegistry) ListCommands

func (cr CommandRegistry) ListCommands() map[string]*Parser

ListCommands returns all command mappings

type Flag

type Flag struct {
	Name   string
	HasArg ArgType
}

type Option

type Option struct {
	Name   string
	HasArg bool
	Arg    string
}

type ParseMode

type ParseMode int
const (
	ParseDefault ParseMode = iota
	ParseNonOpts
	ParsePosixlyCorrect
)

type Parser

type Parser struct {
	Args []string

	// Command support - simple map of command name to parser
	Commands CommandRegistry
	// contains filtered or unexported fields
}

func GetOpt

func GetOpt(args []string, optstring string) (*Parser, error)

func GetOptLong

func GetOptLong(args []string, optstring string, longopts []Flag) (*Parser, error)

func GetOptLongOnly

func GetOptLongOnly(args []string, optstring string, longopts []Flag) (*Parser, error)

func NewParser

func NewParser(config ParserConfig, shortOpts map[byte]*Flag, longOpts map[string]*Flag, args []string, parent *Parser) (*Parser, error)

func NewParserWithCaseInsensitiveCommands

func NewParserWithCaseInsensitiveCommands(shortOpts map[byte]*Flag, longOpts map[string]*Flag, args []string, parent *Parser) (*Parser, error)

NewParserWithCaseInsensitiveCommands creates a new parser with case insensitive command matching enabled

func (*Parser) AddAlias

func (p *Parser) AddAlias(alias, existingCommand string) error

AddAlias creates an alias for an existing command

func (*Parser) AddCmd

func (p *Parser) AddCmd(name string, parser *Parser) *Parser

AddCmd registers a new subcommand with this parser

func (*Parser) ExecuteCommand

func (p *Parser) ExecuteCommand(name string, args []string) (*Parser, error)

ExecuteCommand finds and executes a command

func (*Parser) GetAliases

func (p *Parser) GetAliases(targetParser *Parser) []string

GetAliases returns all aliases for a given parser

func (*Parser) GetCommand

func (p *Parser) GetCommand(name string) (*Parser, bool)

GetCommand retrieves a parser by command name

func (*Parser) HasCommands

func (p *Parser) HasCommands() bool

HasCommands returns true if any commands are registered

func (*Parser) ListCommands

func (p *Parser) ListCommands() map[string]*Parser

ListCommands returns all command mappings

func (*Parser) Options

func (p *Parser) Options() iter.Seq2[Option, error]

type ParserConfig

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

Jump to

Keyboard shortcuts

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