NAME

Getopt::Pad - Object::Pad based POSIX compatible options processor

SYNOPSIS

use Getopt::Pad;

my $opt = GetOptions(
	options => {
		'owner|o' => {
			type     => 's',
			required => 1,
			help     => 'Target owner',
		},
	},
	args => [
		{ type => 'url', short => 'source-url', required => 1, help => 'Source URL' },
	],
	description => 'Migrate one Git repository into Forgejo.',
);

say $opt->owner;
say $opt->sourceUrl;

Called with --help, this script prints:

# migrate [options] source-url
# Migrate one Git repository into Forgejo.

## Arguments
   <source-url>                [REQ] Source URL [URL]

## Completion
   --create-completions <>     Print a completion script for this shell to
                               STDOUT and exit
                                   Valid   = [ bash, zsh ]

## Options
   --owner <>                  [REQ] Target owner

DESCRIPTION

Getopt::Pad parses command line options declared as a typed spec, validates them, and returns a runtime-generated Object::Pad object with one camelCase reader per option and positional argument. Parsing itself is delegated to Getopt::Long, configured with bundling, no_ignore_case and no_auto_abbrev (no long-option abbreviation).

Any parse or validation failure prints the specific error to STDERR, followed by the help text of the command level the error occurred on, and exits with status 2. Mistakes in the spec itself are programmer errors and die instead, with a message pointing at the GetOptions call rather than into the library. --help prints the generated usage text to STDOUT and exits 0.

FUNCTIONS

GetOptions(%spec)

Parses @ARGV (or the argv arrayref, see below) against the spec and returns a result object. Recognized top-level keys:

options => \%options

Maps option keys to option specs. The key is the option's name, optionally with pipe-separated aliases ('owner|o'). The first name defines the camelCase reader (work-dir becomes workDir). Each option spec accepts:

  • type - a type name, see "TYPES". Defaults to a plain flag.

  • required - the option must be given (mutually exclusive with default).

  • default - the value used when the option is absent. It is validated like user input when the spec is built.

  • valid - the allowed values: an arrayref, or a coderef returning such an arrayref when called (with no arguments) - for lists that are only known at run time, such as ids read from a database. The coderef runs when a value has to be checked and when the shell asks for completions (see "SHELL COMPLETION"). The help output lists only a static arrayref.

  • lazyValid - a coderef called with one value, returning true when it is acceptable. Use it for constraints that cannot be listed. It runs after valid.

  • group - the help section this option is rendered under (default Options).

  • help - the help text.

  • multiple - the option may be repeated. The reader returns an arrayref.

  • hidden - accept the option but leave it out of the help output.

Type-specific keys are accepted alongside: mustExist (file, dir), min / max (int, float).

args => \@args

Positional arguments, consumed in order. Each entry accepts short (the name, mandatory, defines the reader), type (default string), required, help, and - on the last entry only - multiple to slurp all remaining positionals into an arrayref. Required args must precede optional ones.

commands => \%commands

Nested subcommands: each value is a hashref with the same keys as the top-level spec (except config, version and argv). The first bare word on the command line selects a command. Options before it belong to the outer level, everything after it to the inner level. A level may declare args or commands, never both.

When a level has commands, naming one is mandatory unless the level sets commandRequired => 0. The result's command and subcommand readers then return undef.

description => $text

One-line description shown in the help header.

examples => \@examples

Hashrefs with text and args, rendered in the help's Examples section.

config => \%config

Enables config file support and the automatic --config option, which is listed in the help output under a Config group (unlike --help and --version, which stay hidden). Keys: format (mandatory, yaml or json, see "EXTENDING"), paths (arrayref, loaded in order when autoload is on - later files override earlier ones), defaultPath (loaded by a bare --config), autoload (default 1). An explicit --config PATH replaces the autoload chain. Precedence is always: command line over config file over spec default. Config files can only set top-level options. A subcommand's options cannot come from a config file. ~ in paths expands to $HOME.

A bare --config takes the next word as its path unless that word starts with a dash, so it also swallows a following positional or command name. Write --config= to load defaultPath in that position.

Config files are structured by group: each top-level key is a group name (as used in the option specs, with ungrouped options under Options) containing a mapping of option names to values:

Target:
  owner: dave
Options:
  log-level: debug

Config values run through the same checks as command line values. A value without content (YAML ~ or an empty entry, JSON null) is an error, as is a list or mapping for an option without multiple. A multiple option takes a list or a single value. Flag and bool options accept only true/false, 1 and 0, counters only non-negative integers.

A --create-default-config PATH option is added as well: it writes a config file prefilled with the spec's default values to PATH, refusing to overwrite an existing file, and exits 0.

version => $string

Printed by the automatic --version option. It defaults to the calling script's $main::VERSION.

argv => \@words

Parse these words instead of @ARGV. @ARGV itself is never modified.

Every level gets an automatic --help option and the root level an automatic --version and --create-completions SHELL option, the latter listed under a Completion group. See "SHELL COMPLETION".

RESULT OBJECT

A successful parse returns an instance of a freshly generated Object::Pad class with one :reader per option and arg, plus:

  • command - the selected subcommand name (or undef)

  • subcommand - the nested result object (or undef)

  • help - prints the usage text and exits 0

  • version - prints the version and exits 0

An option that was never given (no command line value, no config value, no default) reads as undef. A multiple option or slurpy arg that was given reads as an arrayref.

Option and arg names whose reader would collide with something every result object already answers to are rejected when the spec is built: its methods (command, subcommand, help, version, new and what Object::Pad and UNIVERSAL provide, such as can or BUILDARGS), its constructor params, and the names Perl calls by itself (DESTROY, AUTOLOAD). The result class answers that question from its own method table, so the list is never copied. Duplicate names or aliases across the options of one level are rejected the same way.

SHELL COMPLETION

--create-completions bash or --create-completions zsh prints a completion script for the program to STDOUT and exits 0. Redirect it to wherever your shell picks completions up from, or source it:

tool --create-completions bash > ~/.local/share/bash-completion/completions/tool
tool --create-completions zsh  > ~/.zsh/completions/_tool

The script is a thin shim: every time the user presses tab, it runs the program again with the environment variable GETOPT_PAD_COMPLETE set to the shell name, GETOPT_PAD_COMPLETE_INDEX set to the index of the word under the cursor, and the words typed so far as arguments. GetOptions notices the variable, prints the candidates and exits 0 instead of parsing, so nothing after the GetOptions call runs, and the script never needs to be regenerated when the spec changes. Anything the program does before it calls GetOptions runs on every tab, so keep the call early.

Completed are the command names of the current level, the option spellings the help lists (hidden options are left out, negatable ones also as --no-name), the values an option's valid list allows - a coderef is called on every tab, so ids fetched from a database complete to the ids that exist right now - and, for options and positional args of the file and dir types, the shell's own path completion.

TYPES

Types validate and coerce values and annotate the help output.

!      bool boolean       negatable flag (--name / --no-name)
+      counter count      counting flag (-vvv)
s      string str         plain string
i      int integer        integer; min/max
f      float num number   number; min/max
file                      file path; mustExist
dir    directory          directory path; mustExist
url    uri                URL of the form scheme://...
flag                      plain non-negatable flag (the default)

EXTENDING

Both extension seams follow the same pattern: subclass an abstract base class, name the extension through a NAMES constant, and register the class. Runnable versions of the examples below live in the distribution's examples/ directory.

Custom option types

A type subclasses Getopt::Pad::Type and is registered with Getopt::Pad::Type::registerType($class). Every name it lists must be free: a name another type already holds (built-in or custom) makes the registration die, so nothing can silently replace s or int. The contract:

NAMES (constant, required)

Arrayref of the names this type answers to in an option's type key, matched case insensitively (e.g. ['i', 'int', 'integer']).

glSuffix (method, required)

The Getopt::Long suffix ("gl" is short for Getopt::Long) appended to the option names when the Getopt::Long option specification is built - for an option declared as 'owner|o' with a '=s' suffix, Getopt::Long is handed owner|o=s. Valid return values:

''      plain flag, no value (--name)
'!'     negatable flag (--name / --no-name)
'+'     counting flag (-vvv)
'=s'    the option takes a value

Value-taking types should return '=s' even for numbers - never '=i' or '=f' - and do their own checking in check, so every invalid value produces a Getopt::Pad error message instead of a Getopt::Long one. The built-in Int and Float types work exactly this way. The suffix is the only Getopt::Long spelling a type contributes: the base class derives takesValue and negatable from it and assembles the full option specification in glSpec. Overriding those is rarely useful.

check($value) (method, optional)

Return undef when the value is acceptable, otherwise a short problem description without the option name - the caller prefixes it with the option or argument the value came from. Runs for command line, config file and default values alike. Lists, mappings and null config values are rejected before check is called, so a config value always arrives as a single scalar (or a JSON boolean object).

coerce($value) (method, optional)

Return the value to store after a successful check. The default returns it unchanged. Numeric types use this to turn the string into a number.

label (method, optional)

A short tag rendered at the end of the help text, e.g. URL renders as [URL]. Return undef (the default) for none.

constraintNotes (method, optional)

A list of annotations rendered before the help text, e.g. 'has to exist' renders as [has to exist]. The list is empty by default.

completes (method, optional)

Which of the shell's own completions a value of this type gets when the user presses tab: 'files', 'dirs', or undef (the default) for none. The file and dir types use it.

SPEC_KEYS (constant, optional)

Arrayref of extra option-spec keys this type consumes. The type module takes them out of the option or arg spec and passes them to the type's constructor as named parameters, and any key left over is reported as unknown. This is how mustExist, min and max reach the built-in types.

checkSpecKeys(%keys) (class method, optional)

Checks the SPEC_KEYS values before the type is constructed. Return undef when they are acceptable, otherwise a short problem description without the option name. It is reported as a spec error naming the option or arg. The numeric types use this to reject a min that is not a number or larger than max.

A complete type accepting only even integers:

use Object::Pad;
use Getopt::Pad::Type;

class My::Type::Even :isa(Getopt::Pad::Type) {
	use constant NAMES => ['even'];

	method glSuffix() { return '=s' }

	method coerce($value) { return $value + 0 }

	method check($value) {
		return "'$value' is not an integer" if $value !~ /^-?\d+$/;
		return "$value is not an even number" if $value % 2;
		return undef;
	}
}

Getopt::Pad::Type::registerType('My::Type::Even');

# afterwards, in any spec:
options => { workers => { type => 'even' } }

Custom config formats

A format subclasses Getopt::Pad::Config::Format and is registered with Getopt::Pad::Config::Format::registerFormat($class). It provides a NAMES constant (like types, and with the same rule that a name held by another format cannot be taken over) and one method: parse($text), which turns the file's contents into a hashref of group names, each holding a hashref of option names to values. Getopt::Pad reads and writes the files itself as UTF-8: parse receives the decoded text as a Perl character string and never opens a file, and encoding is not the format's concern. On parse problems it should simply die. The message is reported to the user as a config error naming the file. A format may additionally provide dump($data), returning such a structure serialized as text (again a character string, not UTF-8 octets) - without it, --create-default-config refuses to write files in that format. A TOML format via TOML::Tiny:

use Object::Pad;
use Getopt::Pad::Config::Format;

class My::Format::Toml :isa(Getopt::Pad::Config::Format) {
	use Carp qw(croak);
	use Feature::Compat::Try;

	use constant NAMES => ['toml'];

	method parse($text) {
		try { require TOML::Tiny }
		catch ($error) { croak "config format 'toml' requires the TOML::Tiny module" }

		return TOML::Tiny::from_toml($text);
	}
}

Getopt::Pad::Config::Format::registerFormat('My::Format::Toml');

# afterwards, in any spec:
config => { format => 'toml', paths => ['~/.mytool.toml'] }

EXAMPLES

The distribution ships runnable examples in examples/. Each one parses your arguments (its header comment lists invocations to try) and prints an overview of the readers on the result object, indenting nested subcommand results:

SEE ALSO

Getopt::Long, Object::Pad

AUTHOR

davenonymous <perl@davenonymous.com>

COPYRIGHT AND LICENSE

Copyright 2026 davenonymous

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.