Getopt...torial(3pm)
π¦ Getopt...torial(3pm) π¦
okay bestie, so this is Getopt::Yath::Tutorial β out of User Contributed Perl Documentation, living in the 3pm section, and yes the page title is literally truncated to Getopt...torial(3pm) like someone hit backspace at the worst possible moment π we move.
β¨ NAME β¨
Getopt::Yath::Tutorial β A step-by-step guide to using Getopt::Yath. periodt.
π DESCRIPTION π
so this tutorial is literally holding your hand through using Getopt::Yath to handle command-line options in your Perl scripts and your modules. it starts cute and simple and then builds all the way up to the advanced stuff. we're going on a journey bestie, grab a snack π
π YOUR FIRST SCRIPT π
here's the simplest useful script you can write with Getopt::Yath. look at her, she's iconic:
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Yath;
option_group {group => 'my', category => 'My Options'} => sub {
option name => (
type => 'Scalar',
description => 'Your name',
);
};
my $state = parse_options(\@ARGV);
my $settings = $state->settings;
print "Hello, " . ($settings->my->name // 'World') . "!\n";
now run it, no cap:
$ perl hello.pl --name Bob
Hello, Bob!
$ perl hello.pl
Hello, World!
What just happened? π
okay so use Getopt::Yath exports a whole lil squad of functions straight into your package:
optionβ defines a single command-line option, with a name and a spec. one option, one baddie.option_groupβ sets shared attributes (group,category,prefix, etc.) for a whole block of options. it's giving "inheritance but make it cute".parse_optionsβ takes an arrayref of command-line arguments and hands you back a state hashref withsettings, the remaining args, skipped items, and more.include_optionsβ imports option definitions from another module that also usesGetopt::Yath. sharing is caring πoption_post_processβ registers a callback that runs after every option has been parsed, so you can do cross-option validation or lil fixups.optionsβ returns theGetopt::Yath::Instanceobject that holds all your defined options. this is the one you use for generating help and documentation output.category_sort_mapβ controls the display order of option categories in help output. aesthetics matter.
every single option needs a group (that's the key it shows up under in settings) and a type (that's how it parses arguments). the category is just a human-readable label for help output. three things, don't forget 'em.
π OPTION GROUPS π
option_group lets you set shared attributes for a whole block of options so you are not out here repeating yourself like a broken record. the way that DRY principle ate π
option_group {group => 'server', category => 'Server Options'} => sub {
option host => (
type => 'Scalar',
default => 'localhost',
description => 'Hostname to bind to',
);
option port => (
type => 'Scalar',
short => 'p',
default => 8080,
description => 'Port to listen on',
);
option verbose => (
type => 'Bool',
short => 'v',
description => 'Enable verbose output',
);
};
my $state = parse_options(\@ARGV);
my $settings = $state->settings;
printf "Starting server on %s:%s\n",
$settings->server->host,
$settings->server->port;
every option declared inside that block inherits group => 'server' and category => 'Server Options'. and you grab the parsed values with $settings->server->host, $settings->server->port, etc. easy.
π₯ OPTION TYPES π₯
Getopt::Yath ships a whole rich set of option types. here they are, going from simplest to most specialized. buckle up π§
Bool
a simple on/off flag. no argument needed, she doesn't need anything from you.
option verbose => (
type => 'Bool',
short => 'v',
default => 0,
description => 'Enable verbose output',
);
usage:
--verbose # turns it on (1)
-v # same thing
--no-verbose # turns it off (0)
Scalar
takes exactly one value. one. not two. one.
option output => (
type => 'Scalar',
short => 'o',
description => 'Output file path',
);
usage:
--output results.txt
--output=results.txt
-o results.txt
-o=results.txt
-oresults.txt # short form allows value directly after flag
--no-output # clears the value (sets to undef)
Count
an incrementing counter. every use bumps the value by one. yes, it's the -vvv girlie.
option verbosity => (
type => 'Count',
short => 'v',
initialize => 0,
description => 'Increase verbosity level',
);
usage:
-v # 1
-vvv # 3 (short flags can be stacked)
--verbosity # 1
-v -v -v # 3
-v=5 # explicitly set to 5
--no-verbosity # reset to 0
List
collects multiple values into an arrayref. you can specify it multiple times, she's generous like that.
option include => (
type => 'List',
short => 'I',
description => 'Add a directory to the include path',
);
usage:
-I lib -I blib/lib
--include lib --include blib/lib
--no-include # empties the list
Lists also accept JSON arrays:
--include '["lib","blib/lib"]'
Splitting values
use split_on to allow comma-separated (or whatever other delimiter you vibe with) values:
option tags => (
type => 'List',
split_on => ',',
description => 'Comma-separated list of tags',
);
# --tags foo,bar,baz => ['foo', 'bar', 'baz']
Map
collects key=value pairs into a hashref. can be specified multiple times.
option env_var => (
type => 'Map',
short => 'E',
alt => ['env-var'],
description => 'Set an environment variable',
);
usage:
-E HOME=/tmp -E USER=test
--env-var HOME=/tmp
--env-var '{"HOME":"/tmp","USER":"test"}' # JSON also works
--no-env-var # empties the hash
like List, Map supports split_on for multiple pairs in one argument, plus key_on to change the key/value delimiter (default is =).
Auto
a scalar option with an autofill value. --opt uses the autofill value, --opt=val uses the value you gave it. it does not support --opt val (the space-separated form), because with no required argument the next token is ambiguous. don't try it bestie, it's giving chaos.
option coverage => (
type => 'Auto',
autofill => '-silent,1',
description => 'Enable coverage; optionally provide Devel::Cover args',
long_examples => ['', '=-silent,1'],
);
usage:
--coverage # uses autofill: "-silent,1"
--coverage=-db,cov # uses the provided value
--no-coverage # clears
AutoList
combines Auto and List. --opt adds the autofill values to the list, --opt=val adds a specific value.
option plugins => (
type => 'AutoList',
autofill => sub { qw/Foo Bar/ },
description => 'Plugins to load (defaults to Foo and Bar)',
);
usage:
--plugins # adds 'Foo' and 'Bar'
--plugins=Baz # adds 'Baz'
--no-plugins # empties the list
AutoMap
combines Auto and Map. --opt adds the autofill key/value pairs, --opt=key=val adds a specific pair.
option defaults => (
type => 'AutoMap',
autofill => sub { timeout => 30, retries => 3 },
description => 'Default settings',
);
usage:
--defaults # adds {timeout => 30, retries => 3}
--defaults=color=red # adds {color => 'red'}
--no-defaults # empties the hash
PathList
like List, but values containing wildcards get expanded using glob(). glow up β¨
option test_files => (
type => 'PathList',
split_on => ',',
description => 'Test files to run (globs allowed)',
);
usage:
--test-files 't/*.t'
--test-files t/foo.t,t/bar.t
--test-files 'lib/**/*.pm'
AutoPathList
combines AutoList and PathList. --opt adds autofill paths, --opt=glob expands the glob.
option dev_libs => (
type => 'AutoPathList',
short => 'D',
name => 'dev-lib',
autofill => sub { 'lib', 'blib/lib', 'blib/arch' },
description => 'Add dev library paths (default: lib, blib/lib, blib/arch)',
);
usage:
-D # adds lib, blib/lib, blib/arch
-D=lib # adds just lib
-D='lib/*' # adds all matches
BoolMap
matches a pattern of options dynamically, building a hash of boolean values. requires a pattern attribute with a capture group. very dynamic, very main character.
option features => (
type => 'BoolMap',
pattern => qr/feature-(.+)/,
description => 'Toggle features on or off',
);
usage:
--feature-color # {color => 1}
--feature-unicode # {unicode => 1}
--no-feature-color # {color => 0}
the pattern gets embedded into qr/^--(no-)?$pattern$/ automatically. each captured key gets a true or false value depending on the --no- prefix. iconic behaviour honestly.
okay that's part 1 of this saga π more parts coming, don't go anywhere bestie π
β¨ COMMON OPTION ATTRIBUTES β¨
Okay bestie, these attributes are the universal ones β they work across all option types. Like, every single one. Slay. π
short
A single-character alias for the option, because sometimes you just don't have the energy to type the whole thing:
option jobs => (
type => 'Scalar',
short => 'j',
...
);
# -j4 --jobs 4 --jobs=4 -j 4
alt and alt_no
Alternate long names, bestie. Extra little nicknames for your option:
option use_stream => (
type => 'Bool',
alt => ['stream'], # --stream also works
alt_no => ['TAP'], # --TAP is equivalent to --no-use-stream
description => 'Use streaming format instead of TAP',
);
prefix
Adds a prefix to the option name. Highkey a lifesaver inside option groups:
option_group {group => 'db', category => 'Database', prefix => 'db'} => sub {
option host => (
type => 'Scalar',
description => 'Database host',
);
option port => (
type => 'Scalar',
default => 5432,
description => 'Database port',
);
};
# --db-host myhost --db-port 3306
The values are still accessed as "$settings->db->host" and "$settings->db->port" β the prefix only affects the command-line form. It's giving cosmetic, and honestly? We respect it. π
field and name
By default, the option title decides both the field name (underscores) and the CLI name (dashes). But you can override either one:
option test_args => (
type => 'List',
field => 'args', # $settings->tests->args (not test_args)
alt => ['test-arg'],
...
);
default
The value used when the option isn't given on the command line and isn't set via an environment variable:
option timeout => (
type => 'Scalar',
default => 60,
description => 'Timeout in seconds',
);
# Can also be a coderef:
option search => (
type => 'PathList',
default => sub { './t', './t2' },
...
);
initialize
Sets an initial value before parsing even begins. How is this different from "default", you ask? Great question, bestie. "default" gets applied after parsing if the option was never set. "initialize" sets a starting value that command-line arguments can then modify. Subtle, iconic, powerful. π¦
option verbosity => (
type => 'Count',
initialize => 0,
...
);
π ENVIRONMENT VARIABLES π
from_env_vars
Read an option's initial value straight from environment variables. The first one that's actually defined wins:
option verbose => (
type => 'Bool',
from_env_vars => ['MYAPP_VERBOSE', 'VERBOSE'],
description => 'Enable verbose output',
);
Slap a "!" in front of a variable to negate its value:
option quiet => (
type => 'Bool',
from_env_vars => ['!VERBOSE'], # quiet = true when VERBOSE is false
description => 'Suppress output',
);
set_env_vars
Sets environment variables after parsing is complete. Note the vibe check: only Bool, Scalar, Count, and Auto types get this privilege π
option cover => (
type => 'Auto',
autofill => '-silent,1',
from_env_vars => ['T2_DEVEL_COVER'],
set_env_vars => ['T2_DEVEL_COVER'],
description => 'Enable Devel::Cover',
);
The "!" prefix works here too, for inverted env vars. As it should. π
clear_env_vars
Clears specified environment variables after parsing:
option token => (
type => 'Scalar',
from_env_vars => ['AUTH_TOKEN'],
clear_env_vars => ['AUTH_TOKEN'], # don't leak to child processes
description => 'Auth token (cleared from env after reading)',
);
π NORMALIZE AND TRIGGER π
normalize
Transform a value as it's parsed. Glow-up for your strings:
option module => (
type => 'Scalar',
normalize => sub {
my ($val) = @_;
$val =~ s/-/::/g; # Allow Foo-Bar instead of Foo::Bar
return $val;
},
description => 'Module name',
);
# --module Foo-Bar => "Foo::Bar"
For List and Map types, normalize gets called on each value individually. One at a time, no shortcuts. π
trigger
A callback that fires whenever the option is set or cleared from the command line. And listen β triggers do not fire for defaults, autofill, or initialization. They're not here for that drama:
option input_file => (
type => 'Scalar',
trigger => sub {
my $opt = shift;
my %params = @_;
return unless $params{action} eq 'set';
my ($file) = @{$params{val}};
die "File not found: $file\n" unless -f $file;
},
description => 'Input file',
);
The %params hash contains:
action => 'set' or 'clear'
val => \@values (arrayref, even for scalars)
ref => \$field_ref
state => $parse_state
options => $instance
settings => $settings
group => $group
πΈ ALLOWED VALUES πΈ
Restrict what values an option will accept. Boundaries, bestie. We love to see them:
option format => (
type => 'Scalar',
allowed_values => [qw/json csv xml/],
description => 'Output format',
);
# --format json # ok
# --format yaml # dies: Invalid value
"allowed_values" can be an arrayref, a regex, or a coderef:
allowed_values => qr/^\d+$/, # must be numeric
allowed_values => sub { $_[1] > 0 }, # must be positive
Use "allowed_values_text" to customise the help message:
allowed_values_text => 'json, csv, or xml',
π PARSING OPTIONS π
The "parse_options" function is the moment β this is how you process command-line arguments. It hands back a state hashref with all the parsed data. Everything. The whole vibe. π₯
Basic parsing
my $state = parse_options(\@ARGV);
my $settings = $state->settings; # Getopt::Yath::Settings object
my $remains = $state->remains; # args after a stop token
my $skipped = $state->skipped; # skipped non-options
my $stop = $state->stop; # what token stopped parsing
my $env = $state->env; # env vars that were/would be set
my $cleared = $state->cleared; # options cleared via --no-opt
my $modules = $state->modules; # modules whose options were used
Stops
Use "stops" to stop parsing at certain tokens. Most commonly used for "--":
my $state = parse_options(\@ARGV,
stops => ['--'],
);
# perl script.pl --verbose -- --not-an-option foo bar
# $state->stop = '--'
# $state->remains = ['--not-an-option', 'foo', 'bar']
And you can define multiple stop tokens, because why limit yourself. "::" is commonly used as a separator when you're passing arguments through to tests:
my $state = parse_options(\@ARGV,
stops => ['--', '::'],
);
# perl script.pl --verbose :: --some-test-arg
# $state->stop = '::'
# $state->remains = ['--some-test-arg']
Handling non-options
By default, hitting a non-option (anything not starting with "-") throws an error. Rude of it, honestly. But you can change this:
# Skip non-options (collect them)
my $state = parse_options(\@ARGV,
skip_non_opts => 1,
);
my @files = @{$state->skipped};
# Stop at first non-option
my $state = parse_options(\@ARGV,
stop_at_non_opts => 1,
);
# $state->stop = the non-option that caused the stop
# $state->remains = everything after it
Handling invalid options
Same energy, but for unrecognised options:
# Skip invalid options
my $state = parse_options(\@ARGV,
skip_invalid_opts => 1,
);
# Stop at invalid options
my $state = parse_options(\@ARGV,
stop_at_invalid_opts => 1,
);
Suppressing env var side effects
Stop parse_options from touching %ENV at all:
my $state = parse_options(\@ARGV,
no_set_env => 1,
);
# $state->env still shows what would have been set
Argument groups
Argument groups let you collect arguments between delimiter tokens into an arrayref. So useful for passing structured groups of values:
my $state = parse_options(\@ARGV,
groups => { ':{' => '}:' },
);
# perl script.pl --opt :{ arg1 arg2 arg3 }:
# The :{ ... }: group is collected as ['arg1', 'arg2', 'arg3']
Groups can be used as option values or as standalone arguments β standalone ones end up in "skipped" when "skip_non_opts" is enabled. π
π GENERATING HELP OUTPUT π
CLI help
sub show_help {
print options()->docs('cli');
}
This gives you formatted terminal output with ANSI colors (when available), all organised by category. Chef's kiss. π
POD documentation
sub show_pod {
print options()->docs('pod', head => 2);
}
This produces POD markup that's suitable for embedding in your module's documentation. The "head" parameter controls the heading level (like, e.g., "=head2").
Controlling category order
By default categories get sorted alphabetically. Use "category_sort_map" when you want to control the order yourself:
category_sort_map(
'General Options' => 1,
'Server Options' => 2,
'Debug Options' => 3,
);
Lower values appear first. Simple as. β
π REUSABLE OPTION LIBRARIES π
okay so here's the glow-up: you can define your options in a module and then just include them somewhere else. bestie, that's how you build composable option sets, and it's genuinely iconic. π«Ά
β¨ Defining an option library β¨
package My::Options::Database;
use strict;
use warnings;
use Getopt::Yath;
option_group {group => 'db', category => 'Database Options'} => sub {
option host => (
type => 'Scalar',
default => 'localhost',
description => 'Database hostname',
);
option port => (
type => 'Scalar',
default => 5432,
description => 'Database port',
);
option name => (
type => 'Scalar',
description => 'Database name',
);
};
1;
πΈ Including options from another module πΈ
package My::App;
use strict;
use warnings;
use Getopt::Yath;
include_options('My::Options::Database');
option_group {group => 'app', category => 'App Options'} => sub {
option debug => (
type => 'Bool',
description => 'Debug mode',
);
};
my $state = parse_options(\@ARGV);
# $state->settings->db->host, ->db->port, etc.
# $state->settings->app->debug
π Selective inclusion π
and you don't have to take the whole library, bestie β you can cherry-pick:
include_options(
'My::Options::Database' => [qw/host port/],
);
Only the "host" and "port" options will be included; "name" will be excluded. bye felicia name, no shade π
π« POST-PROCESSING π«
option_post_process registers a callback that runs after all options have been parsed but before environment variables are set. lowkey perfect for validation that depends on multiple options at once:
option_post_process sub {
my ($instance, $state) = @_;
my $settings = $state->settings;
my $server = $settings->server;
if ($server->ssl && !$server->cert_file) {
die "--ssl requires --cert-file\n";
}
};
β Weighted post-processors β
the first argument is a weight that controls execution order (lower runs first, default is 0). the way that just β¨ works β¨:
option_post_process 10 => sub {
my ($instance, $state) = @_;
# Runs after weight-0 post-processors
};
π¦ Conditional post-processors π¦
an optional "applicable" coderef controls whether the post-processor even runs:
option_post_process 0 => sub { ... if server group exists ... } => sub {
my ($instance, $state) = @_;
...
};
and if you use this inside an "option_group" block, the group's "applicable" is inherited automatically. free real estate, no cap. π₯
π DYNAMIC OPTIONS WITH mod_adds_options π
some options specify a module name whose options should be dynamically loaded when the option is used. it's giving main character energy tbh:
option runner_class => (
type => 'Scalar',
name => 'runner',
field => 'class',
default => 'My::Runner',
mod_adds_options => 1,
normalize => sub { fqmod($_[0], 'My::Runner') },
description => 'Runner class to use',
);
When "--runner=My::Custom::Runner" is specified, the module is loaded, and if it has an options() method, those options are included into the current instance. this is how plugins and extensions get to contribute their own command-line options β obsessed. π
π APPLICABILITY π
options can be conditionally shown/hidden based on runtime state, which is so valid of them:
option reloader => (
type => 'Auto',
autofill => 'My::Reloader',
applicable => sub {
my ($opt, $options, $settings) = @_;
return $settings->runner->preloads && @{$settings->runner->preloads};
},
description => 'Module reloader (only available with preloads)',
);
When "applicable" returns false, the option is excluded from parsing and documentation. ghosted, literally. π»
π THE SETTINGS OBJECT π
the "$state->settings" value is a Getopt::Yath::Settings object. groups are accessed as methods, so it's just vibes:
$settings->server->host;
$settings->db->port;
Useful methods:
# Check if a group exists
if ($settings->check_group('server')) { ... }
# Safe access with a default
my $val = $settings->maybe('server', 'host', 'localhost');
π COMPLETE EXAMPLE π
here is a complete script demonstrating many features together β it's the full fit, head to toe, and it ate fr fr π:
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Yath;
option_group {group => 'app', category => 'Application Options'} => sub {
option verbose => (
type => 'Count',
short => 'v',
initialize => 0,
from_env_vars => ['MYAPP_VERBOSE'],
set_env_vars => ['MYAPP_VERBOSE'],
description => 'Increase verbosity (-v, -vv, -vvv)',
);
option config => (
type => 'Scalar',
short => 'c',
description => 'Path to config file',
trigger => sub {
my $opt = shift;
my %params = @_;
return unless $params{action} eq 'set';
my ($file) = @{$params{val}};
die "Config file not found: $file\n" unless -f $file;
},
);
option output_format => (
type => 'Scalar',
short => 'f',
default => 'json',
allowed_values => [qw/json csv xml/],
description => 'Output format',
);
option tags => (
type => 'List',
short => 't',
split_on => ',',
description => 'Tags to apply (comma-separated)',
);
option env_vars => (
type => 'Map',
short => 'E',
description => 'Extra environment variables (KEY=VAL)',
);
option dry_run => (
type => 'Bool',
short => 'n',
default => 0,
description => 'Dry run, do not make changes',
);
};
option_group {group => 'files', category => 'File Options'} => sub {
option include => (
type => 'PathList',
short => 'I',
description => 'Files to include (globs allowed)',
);
};
category_sort_map(
'Application Options' => 1,
'File Options' => 2,
);
option_post_process sub {
my ($instance, $state) = @_;
my $settings = $state->settings;
if ($settings->app->dry_run && $settings->app->verbose < 1) {
warn "Note: --dry-run without --verbose; enabling verbose=1\n";
$settings->app->option(verbose => 1);
}
};
# Show help if requested
if (grep { $_ eq '--help' || $_ eq '-h' } @ARGV) {
print options()->docs('cli');
exit 0;
}
my $state = parse_options(\@ARGV,
stops => ['--', '::'],
skip_non_opts => 1,
);
my $settings = $state->settings;
printf "Verbosity: %d\n", $settings->app->verbose;
printf "Format: %s\n", $settings->app->output_format;
printf "Dry run: %s\n", $settings->app->dry_run ? 'yes' : 'no';
if (my $tags = $settings->app->tags) {
printf "Tags: %s\n", join(', ', @$tags) if @$tags;
}
if (my @files = @{$state->skipped}) {
printf "Files: %s\n", join(', ', @files);
}
if ($state->stop) {
printf "Stopped at: %s\n", $state->stop;
printf "Remaining: %s\n", join(' ', @{$state->remains});
}
π₯ ADVANCED: MULTI-STAGE PARSING WITH SUBCOMMANDS π₯
okay bestie so like. tons of tools out there do the whole "script [GLOBAL OPTIONS] subcommand [COMMAND OPTIONS]" thing?? like the script has its own little pile of options, then there's a subcommand name, and then the subcommand got its OWN options too. it's very main character of them. π
and Getopt::Yath is fully here for it, via multi-stage parsing: you parse the global options first (and stop dead at the subcommand), then you load the subcommand and parse its options out of whatever arguments are left over. two stages, two vibes, one clean result. β¨
Step 1: Define global options
first up, make a package for your script-level options. your global era fr fr.
package My::CLI;
use strict;
use warnings;
use Getopt::Yath;
option_group {group => 'global', category => 'Global Options'} => sub {
option verbose => (
type => 'Count',
short => 'v',
initialize => 0,
description => 'Increase verbosity',
);
option config => (
type => 'Scalar',
short => 'c',
description => 'Path to config file',
);
};
Step 2: Define subcommand option packages
now every subcommand gets its own separate package with its own options. independent queens, no sharing. π
package My::CLI::Command::deploy;
use strict;
use warnings;
use Getopt::Yath;
option_group {group => 'deploy', category => 'Deploy Options'} => sub {
option target => (
type => 'Scalar',
short => 't',
description => 'Deployment target (staging, production)',
);
option dry_run => (
type => 'Bool',
short => 'n',
default => 0,
description => 'Perform a dry run',
);
};
sub run {
my ($class, $settings) = @_;
printf "Deploying to %s (dry_run=%s, verbose=%d)\n",
$settings->deploy->target // 'default',
$settings->deploy->dry_run ? 'yes' : 'no',
$settings->global->verbose;
}
package My::CLI::Command::test;
use strict;
use warnings;
use Getopt::Yath;
option_group {group => 'test', category => 'Test Options'} => sub {
option jobs => (
type => 'Scalar',
short => 'j',
default => 1,
description => 'Number of parallel test jobs',
);
option tags => (
type => 'List',
split_on => ',',
description => 'Filter tests by tag',
);
};
sub run {
my ($class, $settings) = @_;
printf "Running tests with %d jobs (verbose=%d)\n",
$settings->test->jobs,
$settings->global->verbose;
}
Step 3: Two-stage dispatch
and now the moment of truth: parse global options first and STOP at the subcommand name. then load the subcommand module, fold its options in, and parse what's left using the combined option set. pass that settings object allll the way through so both stages are literally sharing state. it's giving teamwork. π«Ά
package main;
my %commands = (
deploy => 'My::CLI::Command::deploy',
test => 'My::CLI::Command::test',
);
# Stage 1: Parse global options, stop at the subcommand name
my $stage1 = My::CLI::parse_options(
\@ARGV,
stop_at_non_opts => 1,
);
my $command_name = $stage1->stop
or die "Usage: my-cli [global options] <command> [command options]\n";
my $command_class = $commands{$command_name}
or die "Unknown command: $command_name\n";
# Stage 2: Include command options, parse remaining args
# Carry forward settings so global values are preserved
My::CLI::include_options($command_class);
my $stage2 = My::CLI::parse_options(
$stage1->remains,
settings => $stage1->settings,
);
# Dispatch β settings object has both global and command groups
$command_class->run($stage2->settings);
Example invocations
look at her go π
$ my-cli -vv deploy --target production --dry-run
Deploying to production (dry_run=yes, verbose=2)
$ my-cli --config app.yml test -j8 --tags smoke,unit
Running tests with 8 jobs (verbose=0)
so the key techniques here β the stuff you actually gotta remember β are:
stop_at_non_optsin Stage 1 stops parsing at the subcommand name, placing it in"$state->stop"and the rest in"$state->remains".include_optionsmerges the subcommand's options into the script's option instance before Stage 2.settingsis passed from Stage 1 to Stage 2 so the global values are preserved and both stages write into the same settings object.
π SEE ALSO π
- Getopt::Yath β Main module documentation and API reference
- Getopt::Yath::State β The parse result object returned by
parse_options - Getopt::Yath::Option β Base option class and all available attributes
- Getopt::Yath::Settings β The parsed settings object
- Getopt::Yath::Instance β Internal option instance (advanced usage)
π¦ SOURCE π¦
the source code repository for Getopt-Yath can be found at <http://github.com/Test-More/Getopt-Yath/>.
π MAINTAINERS π
Chad Granum <exodist@cpan.org>
β¨ AUTHORS β¨
Chad Granum <exodist@cpan.org>
π COPYRIGHT π
Copyright Chad Granum <exodist7@gmail.com>.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
See <http://dev.perl.org/licenses/>
and that's a wrap bestie π this whole page is from perl v5.40.1, dated 2026-05-10, and it goes by the name Getopt...torial(3pm). go forth and parse, you absolute icon. no cap. π
π¦
π¦ where this page comes from
- ubuntu stonking: libgetopt-yath-perl 2.000011-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms